
今天我來談談 ansible,一個由 python 編寫的強大的配置管了解決方案。盡管市面上已經有很多可供選擇的配置管了解決方案,但他們各有優劣,而 ansible 的特點就在于它的簡潔。讓 ansible 在主流的配置管理系統中與衆不同的一點便是,它并不需要你在想要配置的每個節點上安裝自己的元件。同時提供的一個優點在于,如果需要的話,你可以在不止一個地方控制你的整個基礎架構。最後一點是它的正确性,或許這裡有些争議,但是我認為在大多數時候這仍然可以作為它的一個優點。說得足夠多了,讓我們來着手在 rhel/centos 和基于 debian/ubuntu 的系統中安裝和配置 ansible。
<a target="_blank"></a>
發行版:rhel/centos/debian/ubuntu linux
jinja2:python 的一個對設計師友好的現代模闆語言
pyyaml:python 的一個 yaml 編碼/反編碼函數庫
paramiko:純 python 編寫的 sshv2 協定函數庫 (譯者注:原文對函數庫名有拼寫錯誤)
httplib2:一個功能全面的 http 用戶端函數庫
本文中列出的絕大部分操作已經假設你将在 bash 或者其他任何現代的 shell 中以 root 使用者執行。
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 密鑰。
$ sudo yum install ansible
$ sudo apt-get install software-properties-common
$ sudo apt-add-repository ppa:ansible/ansible
$ sudo apt-get update
$ sudo apt-get install ansible
$ sudo pip install 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 all -m ping --ask-pass -c paramiko
于是我們有了一份配置,以及一些基礎的其他東西。現在讓我們來做一些實用的事情。ansible 的強大很大程度上展現在 playbooks 上,後者基本上就是一些寫好的 ansible 腳本(大部分來說),不過在制作一個 playbook 之前,我們将先從一些一句話腳本開始。現在讓我們建立和配置 ssh 公鑰認證,以便省去 -c 和 --ask-pass 選項:
$ ssh-keygen -t rsa
樣例輸出:
generating public/private rsa key pair.
enter file in which to save the key (/home/mike/.ssh/id_rsa):
enter passphrase (empty for no passphrase):
enter same passphrase again:
your identification has been saved in /home/mike/.ssh/id_rsa.
your public 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 [email protected]
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
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
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
"key": "ssh-rsa aaaab3nzac1yc2eaaaadaqabaaabaqcq+z8/usprxk0acapyp0tgylm2mkbmeshepuod7p5do1qqthak+9gwdojjavy0youdi+c+autkjvuus+vgb8+i+8mfnu5cvkizzipmjzvrzmhhrdnud7gueanustejfi1pud3na2ixhl4a6s9a/4g2mkyf7qqszi4z5dduduxd9yhmo9yt48/asojlhicyfssswom8ux1unyehqgpdivonvfskkusnsvzbvl3bxzhkhjxz8rmibgiubjdbukwzqnsjkolpwyn76btxmcdvm07o7vnchpf0cmwefm3pxkpbq/ubxyg2mgocgkirgotj8ujc/daadbuuxg92/u01vneb [email protected]",
"key_options": null,
"keyfile": "/home/mike/.ssh/authorized_keys",
"manage_dir": false,
"mode": "0600",
"path": "/home/mike/.ssh/authorized_keys",
"uid": 1000,
"unique": false,
"user": "mike"
現在這些密鑰已經設定好了。我們來試着随便跑一個指令,比如 hostname,希望我們不會被提示要輸入密碼
$ ansible all -m shell -a "hostname" -u root
成功!!!現在我們可以用 root 來執行指令,并且不會被輸入密碼的提示幹擾了。我們現在可以輕易地配置任何在 ansible hosts 檔案中的主機了。讓我們把 /tmp 中的公鑰檔案删除:
$ ansible all -m file -a "dest=/tmp/id_rsa.pub state=absent" -u root
"path": "/tmp/id_rsa.pub",
"state": "absent"
下面我們來做一些更複雜的事情,我要确定一些軟體包已經安裝了,并且已經是最新的版本:
$ ansible all -m zypper -a "name=apache2 state=latest" -u root
"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
- 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] **************************************
play [rhelbased] **************************************************************
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 裡:
remote_user: mike
sudo: yes
- authorized_key: user=root key="{{ lookup('file', '/home/mike/.ssh/id_rsa.pub') }}" path=/root/.ssh/authorized_keys manage_dir=no
remote_user: mdonlon
除此之外還有很多可以做的事情,比如在啟動的時候把公鑰配置好,或者引入其他的流程來讓你按需配置一些機器。不過隻要 ssh 被配置成接受密碼登陸,這些幾乎可以用在所有的流程中。在你準備開始寫太多 playbook 之前,另一個值得考慮的事情是,代碼管理可以有效節省你的時間。機器需要不斷變化,然而你并不需要在每次機器發生變化時都重新寫一個 playbook,隻需要更新相關的部分并送出這些修改。與此相關的另一個好處是,如同我之前所述,你可以從不同的地方管理你的整個基礎結構。你隻需要将你的 playbook 倉庫 git clone 到新的機器上,就完成了管理所有東西的全部設定流程。
我知道很多使用者經常使用 pastebin 這樣的服務,以及很多公司基于顯而易見的理由配置了他們内部使用的類似東西。最近,我遇到了一個叫做 showterm 的程式,巧合之下我被一個客戶要求配置它用于内部使用。這裡我不打算贅述這個應用程式的細節,不過如果你感興趣的話,你可以使用 google 搜尋 showterm。作為一個合理的現實中的例子,我将會試圖配置一個 showterm 伺服器,并且配置使用它所需要的用戶端應用程式。在這個過程中我們還需要一個資料庫伺服器。現在我們從配置用戶端開始:
- hosts: showtermclients
- 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
- 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=https://github.com/conradirwin/showterm.io dest=/root/showterm
- name: initdb
command: service postgresql initdb
creates=/var/lib/pgsql/data/postgresql.conf
- name: start postgresql and 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
sudo_user: postgres
vars:
dbname: showterm
dbuser: showterm
dbpassword: showtermpassword
- name: create db
postgresql_db: name={{dbname}}
- name: create user with all priv
postgresql_user: db={{dbname}} name={{dbuser}} password={{dbpassword}} priv=all
- name: database.yml
template: src=database.yml dest=/root/showterm/config/database.yml
- name: run bundle install
shell: bundle install
args:
chdir: /root/showterm
- name: run rake db tasks
shell: 'bundle exec rake db:create db:migrate db:seed'
- name: apache config
template: src=showterm.conf dest=/etc/httpd/conf.d/showterm.conf
還湊合。請注意,從某種意義上來說這是一個任意選擇的程式,然而我們現在已經可以持續地在任意數量的機器上部署它了,這便是配置管理的好處。此外,在大多數情況下這裡的定義文法幾乎是不言而喻的,wiki 頁面也就不需要加入太多細節了。當然在我的觀點裡,一個有太多細節的 wiki 頁面絕不會是一件壞事。
我們并沒有涉及到這裡所有的細節。ansible 有許多選項可以用來配置你的系統。你可以在你的 hosts 檔案中内嵌變量,而 ansible 将會把它們應用到遠端節點。如:
10.50.1.33 http_port=443
10.50.1.47 http_port=80 ansible_ssh_user=mdonlon
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
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
ultrabook:/etc/ansible # cat host_vars/10.50.1.47
http_port:80
ansible_ssh_user: mdonlon
組織 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 簡單的設定過程,在我看來這是它最重要的一個要點。請注意,因為我試圖展示做一件事的不同方式,是以并不是前文中所有的例子都是适用于你的個别環境或者對于普遍情況的最佳實踐。
原文釋出時間:2014-11-13
本文來自雲栖合作夥伴“linux中國”