Ansible :一個配置管理和IT自動化工具

luashin發表於2016-03-10
   今天來談談ansible,一個由 Python 編寫的強大的配置管理解決方案。儘管市面上已經有很多可供選擇的配置管理解決方案,但他們各有優劣,而ansible的特點就在於它的簡潔。讓ansible在主流的配置管理系統中與眾不同的一點便是,它並不需要你在想要配置的每個節點上安裝自己的元件。同時提供的一個優點在於,如果需要的話,你可以在不止一個地方控制你的整個基礎架構。最後一點是它的正確性,或許這裡有些爭議,但是我認為在大多數時候這仍然可以作為它的一個優點。說得足夠多了,讓我們來著手在 RHEL/CentOS 和基於 Debian/Ubuntu 的系統中安裝和配置Ansible。

Ansible批次搭建LAMP環境

準備工作
    發行版:RHEL/CentOS/Debian/Ubuntu Linux
    Jinja2:Python 的一個對設計師友好的現代模板語言
    PyYAML:Python 的一個 YAML 編碼/反編碼函式庫
    paramiko:純 Python 編寫的 SSHv2 協議函式庫 (譯者注:原文對函式庫名有拼寫錯誤)
    httplib2:一個功能全面的 HTTP 客戶端函式庫
    本文中列出的絕大部分操作已經假設你將在 bash 或者其他任何現代的 shell 中以 root 使用者執行。

Ansible如何工作?
   Ansible 工具並不使用守護程式,它也不需要任何額外的自定義安全架構,因此它的部署可以說是十分容易。你需要的全部東西便是SSH 客戶端和伺服器了。
    +-----------------++---------------+
    |安裝了Ansible的| SSH |檔案伺服器1|
    |Linux/Unix工作站|<------------------>|資料庫伺服器2|在本地或遠端
    +-----------------+模組|代理伺服器3|資料中心的
    192.168.1.100+---------------+Unix/Linux伺服器
其中:
    192.168.1.100 - 在你本地的工作站或伺服器上安裝 Ansible。
    檔案伺服器1到代理伺服器3 - 使用 192.168.1.100 和 Ansible 來自動管理所有的伺服器。
    SSH - 在 192.168.1.100 和本地/遠端的伺服器之間設定 SSH 金鑰。

Ansible安裝教程
   ansible的安裝輕而易舉,許多發行版的第三方軟體倉庫中都有現成的軟體包,可以直接安裝。其他簡單的安裝方法包括使用pip安裝它,或者從 github 裡獲取最新的版本。若想使用你的軟體包管理器安裝,在基於RHEL/CentOS Linux的系統裡你很可能需要EPEL倉庫。
在基於RHEL/CentOS Linux的系統中安裝ansible

輸入如下 yum 命令:
$ sudo yum install ansible

在基於Debian/Ubuntu Linux的系統中安裝ansible
輸入如下 apt-get 命令:
$ sudo apt-get install software-properties-common
$ sudo apt-add-repository ppa:ansible/ansible
$ sudo apt-get update
$ sudo apt-get install ansible

使用pip安裝ansible
   pip命令是一個安裝和管理 Python 軟體包的工具,比如它能管理 Python Package Index 中的那些軟體包。如下方式在 Linux 和類 Unix 系統中通用:
$ sudo pip install ansible

從原始碼安裝最新版本的ansible
可以透過如下命令從 github 中安裝最新版本:
$ cd ~
$ git clone git://github.com/ansible/ansible.git
$ cd ./ansible
$ source ./hacking/env-setup

當你從一個git checkout中執行ansible的時候,請記住你每次用它之前都需要設定你的環境,或者你可以把這個設定過程加入你的bash rc檔案中:
# 加入 BASH RC
$ echo "export ANSIBLE_HOSTS=~/ansible_hosts">>~/.bashrc
$ echo "source ~/ansible/hacking/env-setup">>~/.bashrc

ansible的hosts檔案包括了一系列它能操作的主機。預設情況下 ansible 透過路徑 /etc/ansible/hosts 查詢hosts檔案,不過這個行為也是可以更改的,這樣當你想操作不止一個ansible或者針對不同的資料中心的不同客戶操作的時候也是很方便的。你可以透過命令列引數 -i 指定 hosts 檔案:
$ ansible all -m shell -a "hostname" --ask-pass -i /etc/some/other/dir/ansible_hosts

不過我更傾向於使用一個環境變數,這可以在你想要透過 source 一個不同的檔案來切換工作目標的時候起到作用。這裡的環境變數是 $ANSIBLE_HOSTS,可以這樣設定:
$ export ANSIBLE_HOSTS=~/ansible_hosts

一旦所有需要的元件都已經安裝完畢,而且你也準備好了你的 hosts 檔案,你就可以來試一試它了。為了快速測試,這裡我把 127.0.0.1 寫到了 ansible 的 hosts 檔案裡:
$ echo "127.0.0.1">~/ansible_hosts

現在來測試一個簡單的 ping:
$ ansible all -m ping

或者提示ssh密碼:
$ ansible all -m ping --ask-pass

在剛開始的設定中遇到過幾次問題,因此這裡強烈推薦為 ansible 設定 SSH 公鑰認證。不過在剛剛的測試中我們使用了 --ask-pass,在一些機器上你會需要安裝 sshpass 或者像這樣指定 -c paramiko:
$ ansible all -m ping --ask-pass -c paramiko
當然你也可以安裝sshpass,然而sshpass並不總是在標準的倉庫中提供,因此paramiko可能更為簡單。

設定SSH公鑰認證

於是有了一份配置,以及一些基礎的其他東西。現在讓我們來做一些實用的事情。ansible 的強大很大程度上體現在 playbooks 上,後者基本上就是一些寫好的 ansible 指令碼(大部分來說),不過在製作一個 playbook 之前,我們將先從一些一句話指令碼開始。現在讓我們建立和配置 SSH 公鑰認證,以便省去 -c 和 --ask-pass 選項:
$ ssh-keygen -t rsa

樣例輸出:
    Generatingpublic/private rsa key pair.
    Enter file in which to save the key (/home/mike/.ssh/id_rsa):
    Enter passphrase (empty forno passphrase):
    Enter same passphrase again:
    Your identification has been saved in/home/mike/.ssh/id_rsa.
    Yourpublic key has been saved in/home/mike/.ssh/id_rsa.pub.
    The key fingerprint is:
    94:a0:19:02:ba:25:23:7f:ee:6c:fb:e8:38:b4:f2:42 mike@ultrabook.linuxdork.com
    The key's randomart image is:
    +--[ RSA 2048]----+
    |... . . |
    |. . + . . |
    |= . o o |
    |.* . |
    |. . . S |
    | E.o |
    |.. .. |
    |o o+.. |
    | +o+*o. |
    +-----------------+

現在顯然有很多種方式來把它放到遠端主機上應該的位置。不過既然我們正在使用 ansible,就用它來完成這個操作吧:
$ ansible all -m copy -a "src=/home/mike/.ssh/id_rsa.pub dest=/tmp/id_rsa.pub" --ask-pass -c paramiko

樣例輸出:
    SSH password:
    127.0.0.1| success >>{
    "changed":true,
    "dest":"/tmp/id_rsa.pub",
    "gid":100,
    "group":"users",
    "md5sum":"bafd3fce6b8a33cf1de415af432774b4",
    "mode":"0644",
    "owner":"mike",
    "size":410,
    "src":"/home/mike/.ansible/tmp/ansible-tmp-1407008170.46-208759459189201/source",
    "state":"file",
    "uid":1000
    }

下一步,把公鑰檔案新增到遠端伺服器裡。輸入:
$ ansible all -m shell -a "cat /tmp/id_rsa.pub >> /root/.ssh/authorized_keys" --ask-pass -c paramiko

樣例輸出:
    SSH password:
    127.0.0.1| FAILED | rc=1>>
    /bin/sh:/root/.ssh/authorized_keys:Permission denied

矮油,需要用root來執行這個命令,所以還是加上一個-u引數吧:
$ ansible all -m shell -a "cat /tmp/id_rsa.pub >> /root/.ssh/authorized_keys" --ask-pass -c paramiko -u root

樣例輸出:
    SSH password:
    127.0.0.1| success | rc=0>>

請注意,剛才這是想要演示透過 ansible 來傳輸檔案的操作。事實上 ansible 有一個更加方便的內建 SSH 金鑰管理支援:
$ ansible all -m authorized_key -a "user=mike key='{{ lookup('file', '/home/mike/.ssh/id_rsa.pub') }}' path=/home/mike/.ssh/authorized_keys manage_dir=no" --ask-pass -c paramiko

樣例輸出:
    SSH password:
    127.0.0.1| success >>{
    "changed":true,
    "gid":100,
    "group":"users",
    "key":"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCq+Z8/usprXk0aCAPyP0TGylm2MKbmEsHePUOd7p5DO1QQTHak+9gwdoJJavy0yoUdi+C+autKjvuuS+vGb8+I+8mFNu5CvKiZzIpMjZvrZMhHRdNud7GuEanusTEJfi1pUd3NA2iXhl4a6S9a/4G2mKyf7QQSzI4Z5ddudUXd9yHmo9Yt48/ASOJLHIcYfSsswOm8ux1UnyeHqgpdIVONVFsKKuSNSvZBVl3bXzhkhjxz8RMiBGIubJDBuKwZqNSJkOlPWYN76btxMCDVm07O7vNChpf0cmWEfM3pXKPBq/UBxyG2MgoCGkIRGOtJ8UjC/daadBUuxg92/u01VNEB mike@ultrabook.linuxdork.com",
    "key_options":null,
    "keyfile":"/home/mike/.ssh/authorized_keys",
    "manage_dir":false,
    "mode":"0600",
    "owner":"mike",
    "path":"/home/mike/.ssh/authorized_keys",
    "size":410,
    "state":"file",
    "uid":1000,
    "unique":false,
    "user":"mike"
    }

現在這些金鑰已經設定好了。我們來試著隨便跑一個命令,比如 hostname,希望我們不會被提示要輸入密碼
$ ansible all -m shell -a "hostname" -u root

樣例輸出:
    127.0.0.1| success | rc=0>>

成功!!!現在我們可以用 root 來執行命令,並且不會被輸入密碼的提示干擾了。我們現在可以輕易地配置任何在 ansible hosts 檔案中的主機了。讓我們把 /tmp 中的公鑰檔案刪除:
$ ansible all -m file -a "dest=/tmp/id_rsa.pub state=absent" -u root

樣例輸出:
    127.0.0.1| success >>{
    "changed":true,
    "path":"/tmp/id_rsa.pub",
    "state":"absent"
    }

下面我們來做一些更復雜的事情,我要確定一些軟體包已經安裝了,並且已經是最新的版本:
$ ansible all -m zypper -a "name=apache2 state=latest" -u root

樣例輸出:
    127.0.0.1| success >>{
    "changed":false,
    "name":"apache2",
    "state":"latest"
    }

很好,我們剛才放在 /tmp 中的公鑰檔案已經消失了,而且我們已經安裝好了最新版的 apache。下面我們來看看前面命令中的 -m zypper,一個讓 ansible 非常靈活,並且給了 playbooks 更多能力的功能。如果你不使用 openSUSE 或者 Suse enterprise 你可能還不熟悉 zypper, 它基本上就是 suse 世界中相當於 yum 的存在。在上面所有的例子中,我的 hosts 檔案中都只有一臺機器。除了最後一個命令外,其他所有命令都應該在任何標準的 *nix 系統和標準的 ssh 配置中使用,這造成了一個問題。如果我們想要同時管理多種不同的機器呢?這便是 playbooks 和 ansible 的可配置性閃閃發光的地方了。首先我們來少許修改一下我們的 hosts 檔案:
$ cat ~/ansible_hosts

樣例輸出:
    [RHELBased]
    10.50.1.33
    10.50.1.47
     
    [SUSEBased]
    127.0.0.1

首先,我們建立了一些分組的伺服器,並且給了他們一些有意義的標籤。然後我們來建立一個為不同型別的伺服器執行不同操作的 playbook。你可能已經發現這個 yaml 的資料結構和我們之前執行的命令列語句中的相似性了。簡單來說,-m 是一個模組,而 -a 用來提供模組引數。在 YAML 表示中你可以先指定模組,然後插入一個冒號 :,最後指定引數。
    ---
    - hosts:SUSEBased
    remote_user: root
    tasks:
    - zypper: name=apache2 state=latest
    - hosts:RHELBased
    remote_user: root
    tasks:
    - yum: name=httpd state=latest

現在有一個簡單的playbook 了,可以這樣執行它:
$ ansible -playbook testPlaybook.yaml -f 10

樣例輸出:
    PLAY [SUSEBased]**************************************************************
     
    GATHERING FACTS ***************************************************************
    ok:[127.0.0.1]
     
    TASK:[zypper name=apache2 state=latest]**************************************
    ok:[127.0.0.1]
     
    PLAY [RHELBased]**************************************************************
     
    GATHERING FACTS ***************************************************************
    ok:[10.50.1.33]
    ok:[10.50.1.47]
     
    TASK:[yum name=httpd state=latest]*******************************************
    changed:[10.50.1.33]
    changed:[10.50.1.47]
     
    PLAY RECAP ********************************************************************
    10.50.1.33: ok=2 changed=1 unreachable=0 failed=0
    10.50.1.47: ok=2 changed=1 unreachable=0 failed=0
    127.0.0.1: ok=2 changed=0 unreachable=0 failed=0

注意,會看到ansible聯絡到的每一臺機器的輸出。-f 引數讓ansible在多臺主機上同時執行指令。除了指定全部主機,或者一個主機分組的名字以外,你還可以把匯入ssh公鑰的操作從命令列裡轉移到playbook中,這將在設定新主機的時候提供很大的方便,甚至讓新主機直接可以執行一個playbook。為了演示,我們把我們之前的公鑰例子放進一個playbook裡:
    ---
    - hosts:SUSEBased
    remote_user: mike
    sudo: yes
    tasks:
    - authorized_key: user=root key="{{ lookup('file', '/home/mike/.ssh/id_rsa.pub') }}" path=/root/.ssh/authorized_keys manage_dir=no
    - hosts:RHELBased
    remote_user: mdonlon
    sudo: yes
    tasks:
    - authorized_key: user=root key="{{ lookup('file', '/home/mike/.ssh/id_rsa.pub') }}" path=/root/.ssh/authorized_keys manage_dir=no

除此之外還有很多可以做的事情,比如在啟動的時候把公鑰配置好,或者引入其他的流程來讓你按需配置一些機器。不過只要SSH被配置成接受密碼登陸,這些幾乎可以用所有的流程中。在你準備開始寫太多playbook之前,另一個值得考慮的事情是,程式碼管理可以有效節省你的時間。機器需要不斷變化,然而你並不需要在每次機器發生變化時都重新寫一個playbook,只需要更新相關的部分並提交這些修改。與此相關的另一個好處是,如同我之前所述,你可以從不同的地方管理你的整個基礎結構。你只需要將你的playbook倉庫git clone到新的機器上,就完成了管理所有東西的全部設定流程。

現實中的ansible例子

很多使用者經常使用pastebin 這樣的服務,以及很多公司基於顯而易見的理由配置了他們內部使用的類似東西。最近,我遇到了一個叫做 showterm 的程式,巧合之下我被一個客戶要求配置它用於內部使用。這裡我不打算贅述這個應用程式的細節,不過如果你感興趣的話,你可以使用 Google 搜尋 showterm。作為一個合理的現實中的例子,我將會試圖配置一個 showterm 伺服器,並且配置使用它所需要的客戶端應用程式。在這個過程中我們還需要一個資料庫伺服器。現在我們從配置客戶端開始:
    ---
    - hosts: showtermClients
    remote_user: root
    tasks:
    - yum: name=rubygems state=latest
    - yum: name=ruby-devel state=latest
    - yum: name=gcc state=latest
    - gem: name=showterm state=latest user_install=no

這部分很簡單。下面是主伺服器:
    ---
    - hosts: showtermServers
    remote_user: root
    tasks:
    - name:ensure packages are installed
    yum: name={{item}} state=latest
    with_items:
    - postgresql
    - postgresql-server
    - postgresql-devel
    - python-psycopg2
    - git
    - ruby21
    - ruby21-passenger
    - name: showterm server from github
    git: repo= dest=/root/showterm
    - name:Initdb
    command: service postgresql initdb
    creates=/var/lib/pgsql/data/postgresql.conf
     
    - name:StartPostgreSQLand enable at boot
    service: name=postgresql
    enabled=yes
    state=started
    - gem: name=pg state=latest user_install=no
    handlers:
    - name: restart postgresql
    service: name=postgresql state=restarted
     
    - hosts: showtermServers
    remote_user: root
    sudo: yes
    sudo_user: postgres
    vars:
    dbname: showterm
    dbuser: showterm
    dbpassword: showtermpassword
    tasks:
    - name: create db
    postgresql_db: name={{dbname}}
     
    - name: create user with ALL priv
    postgresql_user: db={{dbname}} name={{dbuser}} password={{dbpassword}} priv=ALL
    - hosts: showtermServers
    remote_user: root
    tasks:
    - name: database.yml
    template: src=database.yml dest=/root/showterm/config/database.yml
    - hosts: showtermServers
    remote_user: root
    tasks:
    - name: run bundle install
    shell: bundle install
    args:
    chdir:/root/showterm
    - hosts: showtermServers
    remote_user: root
    tasks:
    - name: run rake db tasks
    shell:'bundle exec rake db:create db:migrate db:seed'
    args:
    chdir:/root/showterm
    - hosts: showtermServers
    remote_user: root
    tasks:
    - name: apache config
    template: src=showterm.conf dest=/etc/httpd/conf.d/showterm.conf

還湊合。請注意,從某種意義上來說這是一個任意選擇的程式,然而我們現在已經可以持續地在任意數量的機器上部署它了,這便是配置管理的好處。此外,在大多數情況下這裡的定義語法幾乎是不言而喻的,wiki 頁面也就不需要加入太多細節了。當然在我的觀點裡,一個有太多細節的 wiki 頁面絕不會是一件壞事。

擴充套件配置

我們並沒有涉及到這裡所有的細節。Ansible 有許多選項可以用來配置你的系統。你可以在你的 hosts 檔案中內嵌變數,而 ansible 將會把它們應用到遠端節點。如:
    [RHELBased]
    10.50.1.33 http_port=443
    10.50.1.47 http_port=80 ansible_ssh_user=mdonlon
     
    [SUSEBased]
    127.0.0.1 http_port=443

儘管這對於快速配置來說已經非常方便,你還可以將變數分成存放在 yaml 格式的多個檔案中。在你的 hosts 檔案路徑裡,你可以建立兩個子目錄 groupvars 和 hostvars。在這些路徑裡放置的任何檔案,只要能對得上一個主機分組的名字,或者你的 hosts 檔案中的一個主機名,它們都會在執行時被插入進來。所以前面的一個例子將會變成這樣:
    ultrabook:/etc/ansible # pwd
    /etc/ansible
    ultrabook:/etc/ansible # tree
    .
    ├── group_vars
    │├──RHELBased
    │└──SUSEBased
    ├── hosts
    └── host_vars
    ├──10.50.1.33
    └──10.50.1.47

    2 directories,5 files
    ultrabook:/etc/ansible # cat hosts
    [RHELBased]
    10.50.1.33
    10.50.1.47

    [SUSEBased]
    127.0.0.1
    ultrabook:/etc/ansible # cat group_vars/RHELBased
    ultrabook:/etc/ansible # cat group_vars/SUSEBased
    ---
    http_port:443
    ultrabook:/etc/ansible # cat host_vars/10.50.1.33
    ---
    http_port:443
    ultrabook:/etc/ansible # cat host_vars/10.50.1.47
    ---
    http_port:80
    ansible_ssh_user: mdonlon

改善Playbooks


組織 playbooks 也已經有很多種現成的方式。在前面的例子中我們用了一個單獨的檔案,因此這方面被大幅地簡化了。組織這些檔案的一個常用方式是建立角色。簡單來說,你將一個主檔案載入為你的 playbook,而它將會從其它檔案中匯入所有的資料,這些其他的檔案便是角色。舉例來說,如果你有了一個 wordpress 網站,你需要一個 web 前端,和一個資料庫。web 前端將包括一個 web 伺服器,應用程式程式碼,以及任何需要的模組。資料庫有時候執行在同一臺主機上,有時候執行在遠端的主機上,這時候角色就可以派上用場了。你建立一個目錄,並對每個角色建立對應的小 playbook。在這個例子中我們需要一個 apache 角色,mysql 角色,wordpress 角色,mod_php,以及 php 角色。最大的好處是,並不是每個角色都必須被應用到同一臺機器上。在這個例子中,mysql 可以被應用到一臺單獨的機器。這同樣為程式碼重用提供了可能,比如你的 apache 角色還可以被用在 python 和其他相似的 php 應用程式中。展示這些已經有些超出了本文的範疇,而且做一件事總是有很多不同的方式,我建議搜尋一些 ansible的playbook例子。有很多人在github上貢獻程式碼,當然還有其他一些網站。

模組

在ansible中,對於所有完成的工作,幕後的工作都是由模組主導的。Ansible 有一個非常豐富的內建模組倉庫,其中包括軟體包安裝,檔案傳輸,以及我們在本文中做的所有事情。但是對一部分人來說,這些並不能滿足他們的配置需求,ansible 也提供了方法讓你新增自己的模組。Ansible 的 API 有一個非常棒的事情是,它並沒有限制模組也必須用編寫它的語言 Python 來編寫,也就是說,你可以用任何語言來編寫模組。Ansible 模組透過傳遞 JSON 資料來工作,因此你只需要用想用的語言生成一段 JSON 資料。我很確定任何指令碼語言都可以做到這一點,因此你現在就可以開始寫點什麼了。在 Ansible 的網站上有很多的文件,包括模組的介面是如何工作的,以及 Github 上也有很多模組的例子。注意一些小眾的語言可能沒有很好的支援,不過那隻可能是因為沒有多少人在用這種語言貢獻程式碼。試著寫點什麼,然後把你的結果釋出出來吧!

總結

總的來說,雖然在配置管理方面已經有很多解決方案,我希望本文能顯示出ansible簡單的設定過程,在我看來這是它最重要的一個要點。請注意,因為我試圖展示做一件事的不同方式,所以並不是前文中所有的例子都是適用於你的個別環境或者對於普遍情況的最佳實踐。這裡有一些連結能讓你對ansible的瞭解進入下一個層次:
Ansible專案主頁.
Ansible專案文件.
多級環境與Ansible.

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/9034054/viewspace-2055135/,如需轉載,請註明出處,否則將追究法律責任。

相關文章