在工作中有用到ansible用於自動部署和環境配置,這里整理了一份很詳盡的使用指南,如果有用到的可以看看。關於使用ansible自動部署一個網站和docker化,將在下一篇文章中介紹,敬請期待。文章內容主要翻譯整理自ansible官方網站推薦的
Ansible-Up and Running
一書。
1 為什么選擇Ansible
來源:ansible一詞源於科幻小說,是一種超光速通信設備。
Ansible is the simplest way to automate apps and IT infrastructure。
750+模塊,19000+ github stars。
配置管理、應用部署等。配置管理工具有Chef, Puppet, Salt等,應用部署(將代碼編譯或打包然后傳輸到服務器部署並啟動服務)工具有Capistrano,Fabric等,ansible集兩者於一身,操作很簡單但是功能強大。此外,還可以對多個服務器進行服務編排,支持openstack,amazon ec2, docker等。
ansible使用了一個DSL(domain-specific language)描述服務器狀態。執行的文件稱為playbook,文件格式為yaml。ansible簡約而不簡單。比起puppet的繁瑣的配置和復雜語法( Puppet基礎篇4-安裝、配置並使用Puppet | Puppet運維自動化經驗分享 ),簡直是一股清流。 圖2描述了ansible執行過程,執行了兩個task和一個handler,先是使用了一個apt模塊在web1,web2,web3上面執行了安裝nginx的任務,再是用template模塊拷貝了配置文件。另外,執行了一個notify nginx的handler重啟了nginx。
執行流程:
1. 創建一個python腳本用於安裝nginx包。 2. 拷貝python腳本到web1,web2,web3。 3. 分別在web1,web2,web3上執行該腳本。 4. 等待腳本在所有服務器上執行完畢。 5. 接着執行下一個task。
注意的幾點:
- 1.在各個服務器執行腳本的過程是並行的,有個forks參數可以指定,默認是5,即一次可以在5個服務器上並行執行腳本。
- 2.要在所有的服務器都執行完第一個task后才會接着執行第二個task。(新版本新增了異步參數,一個服務器在執行完了它的任務后可以不等其他服務器執行完直接執行下一個task)。
- 3.ansible執行任務順序與playbook中的順序一致。
優勢:
- 語法易讀。yaml->json好比markdown->html。ansible的playbook可以被稱之為可以執行的README。
- 遠程主機不需要安裝任何東西。(這有點誇大了,python2.5+(python2.4+simplejson模塊)和ssh是必須的,當然這現在已經是Linux服務器標配了)
- push-based。如chef和puppet是pull-based,先將文件修改推送到中心服務器,其他服務器的agent定期拉取新的配置管理腳本並在本機執行。而在ansible是push-based的,先在中心服務器修改playbook,執行該playbook,ansible會連接到各個服務器並執行模塊改變服務器狀態。push-based的好處就是只在需要的時候才操控去改變目標服務器狀態。如果你更傾向於pull-based模式,可以用ansible-pull。
- ansible可以很方便的scaled down,單機跟多機沒有什么區別。 Simple things should be simple, complex things should be possible 。
- 很輕量級的抽象。不像puppet之類的工具,有很高的抽象,比如有package這個概念,用於不用區分服務器版本來安裝模塊。但是在ansible中,提供的是apt和yum模塊,由你自己采用,不要再額外學一些抽象的語法,簡化你的學習成本。也有人覺得這是ansible的缺點,優缺點與否,各有評判。
2 安裝配置
2.1 安裝
pip install ansible
依賴環境:python
2.2 配置
配置ansible.cfg文件,ansible配置文件尋找路徑:
1. File specified by the ANSIBLE_CONFIG environment variable 2. ./ansible.cfg (ansible.cfg in the current directory) 3. ~/.ansible.cfg (.ansible.cfg in your home directory) 4. /etc/ansible/ansible.cfg
ansible.cfg配置文件實例
[defaults] hostfile=/etc/ansible/hosts private_key_file = /Users/ssj/.ssh/id_rsa_ansible remote_user = ssj remote_port = 22 host_key_checking = False
注意,如果是在服務器上,不要放置private key,可以通過ssh forward。
2.3 測試
簡單執行命令測試是否成功 ( -vvvv可以看到更多細節信息),”changed”:false表示執行ping模塊沒有改變服務器狀態,”ping”:pong表示模塊執行后輸出結果為pong。你也可以將ping模塊改成command,加上參數執行指定命令。比如 ansible testserver -m command -a uptime
,當然,command是默認模塊,因此還可以簡化為 ansible testserver -a uptime
。
#hosts
[testserver] 127.0.0.1 #run command,-i hosts可以省去。 ssj@ssj-mbp ~/ansible $ ansible testserver -i hosts -m ping 127.0.0.1 | SUCCESS => { "changed": false, "ping": "pong" }
3 實體關系圖
- playbook包含很多個play
- play中包含name,tasks,hosts,vars,handles屬性。
- tasks中包含各個真正執行的module,如apt,copy,file, git, svn,service,command,notify,mysql等。具體的模塊參數和使用文檔在這里
4 一個例子
--- - name: Configure webserver with nginx and tls hosts: webservers sudo: True vars: key_file: /etc/nginx/ssl/nginx.key cert_file: /etc/nginx/ssl/nginx.crt conf_file: /etc/nginx/sites-available/default server_name: localhost tasks: - name: Install nginx apt: name=nginx update_cache=yes cache_valid_time=3600 - name: create directories for TLS certificates file: path=/etc/nginx/ssl state=directory - name: copy TLS key copy: src=files/nginx.key dest={{ key_file }} owner=root mode=0600 notify: restart nginx - name: copy TLS certificate copy: src=files/nginx.crt dest={{ cert_file }} notify: restart nginx - name: copy nginx config file template: src=templates/nginx.conf.j2 dest={{ conf_file }} notify: restart nginx - name: enable configuration file: dest=/etc/nginx/sites-enabled/default src={{ conf_file }} state=link notify: restart nginx - name: copy index.html template: src=templates/index.html.j2 dest=/usr/share/nginx/html/index.html mode=0644 handlers: - name: restart nginx service: name=nginx state=restarted
- 可以看到用到了apt,file,copy,template,notify,service等模塊。
- 注意幾個語法點:
YAML truthy
true, True, TRUE, yes, Yes, YES, on, On, ON, y, Y YAML falsey false, False, FALSE, no, No, NO, off, Off, OFF, n, N
- true和yes,on或者1都是一樣的意思,一般在模塊參數里面用yes和no,true和false在playbook中其他地方。
- 另外,比如下面的模塊參數分行寫,可以在第一行寫 > , 后面幾行跟參數來實現。
- 注意notify是嚴格按照它在play中定義的順序執行的,而不是notify調用的順序執行的。比如下面的playbook,盡管先notify的是
handler test2
,實際執行時時按照play中handlers定義的順序,也就是先執行handler test1
。
#!/usr/bin/env ansible-playbook - name: test handlers hosts: webservers tasks: - name: assure file exist file: > path=/tmp/test.conf state=touch owner=ssj mode=0644 - name: task1 command: date notify: handler test2 - name: task2 command: echo 'task2' notify: handler test1 handlers: - name: handler test1 command: echo 'handler test1' - name: handler test2 command: echo 'handler test2'
5 更多細節
5.1 inventory格式和配置
inventory格式
[webserver] 127.0.0.1 dbserver1 ansible_ssh_host=127.0.0.1 ansible_ssh_port=22 color=red dbserver2 ansible_ssh_host=127.0.0.1 ansible_ssh_port=2200 color=green www.example.com [dbserver] #group dbserver1 dbserver2 [forum:children] #groups of groups webserver dbserver
可以用分組的方式,可以直接用域名(www.example.com),也可以用別名(如testserver2)+變量指定ssh的ip地址和端口,比如ansible_ssh_host和color變量。命令 ansible testserver2 -a date
,通常我們要控制多台服務器,於是可以分組服務器,要在所有服務器執行可以用all。 ansible all -a date
。
inventory除了可以指定主機的變量如上面的color之外,還可以將變量分組,也可以對主機變量單獨存儲到一個文件中,格式如下,注意如果host_vars中和group_vars中有相同變量,則以host_vars中的為准。host_vars變量只能本主機使用,group_vars是本group都可以使用。
############################
group_vars/dbserver ----------------------- db: user: bbsdbuser password: bbsdbpasswd port: 3306 name: bbs replica: host: slavedb port: 3307 host_vars/dbserver1 --------------------- db: user:server1dbuser password: server1password master: true ############################ ssj@ssj-mbp ~/ansible $ ansible dbserver1 -i hosts -a 'echo {{db.user}}' #host_vars優先級高 dbserver1 | SUCCESS | rc=0 >> server1dbuser ---------------------------------------------------------- ssj@ssj-mbp ~/ansible $ ansible dbserver2 -i hosts -a 'echo {{db.master}}' #dbserver2所在的組變量文件沒有db.master變量,報錯。 dbserver2 | FAILED | rc=0 >> the field 'args' has an invalid value, which appears to include a variable that is undefined. The error was: 'dict object' has no attribute 'master'
甚至支持:
[web] web[1:20].example.com web-[a-t].example.com
inventory文件還支持動態的,通過 -i inventory
可以指定目錄或者文件,這樣目錄下面可以放一個python腳本,用來動態獲取主機列表。python腳本要可執行,同時實現下面兩個命令:
• --host=<hostname> for showing host details • --list for listing groups
最后,還可以通過add_hosts模塊在運行時增加host配置,使用group_by模塊在運行時創建group。比如通過 ansible_distribution來根據操作系統創建不同的組,再分別安裝軟件。
- name: group hosts by distribution hosts: myhosts gather_facts: True tasks: - name: create groups based on distro group_by: key={{ ansible_distribution }} - name: do something to Ubuntu hosts hosts: Ubuntu tasks: - name: install htop apt: name=htop # ... - name: do something else to CentOS hosts hosts: CentOS tasks: - name: install htop yum: name=htop # ...
inventory默認配置
幾個參數解釋下:
- ansible_connection: ssh連接方式,默認是smart,也就是看本地機器是否安裝了ssh客戶端且支持ControlPersist特性。如果支持,則使用本地的ssh客戶端,如果不支持,則使用一個基於python的ssh客戶端庫paramiko。 - ansible_shell_type: ansible認為的遠程服務器執行script的shell,默認認為是/bin/sh,當然也可以設置為csh,fish等,如果服務器支持的話。 - ansible_python_interpreter: 服務器python解釋器的路徑。如果你的服務器python解釋器不在這個目錄,這要修改該配置。 - ansible_*_interpreter: 如果用的是一個自定義的模塊,不是python的,比如ruby,則設置該值指定解釋器路徑(比如/usr/bin/ruby)。
5.2 變量和Facts
變量
變量可以在play中通過vars來指定,也可以通過var_file指定一個文件,文件中存儲變量。如之前的nginx的playbook可以改成這樣:
vars_files: - nginx.yml ##nginx.yml文件內容 key_file: /etc/nginx/ssl/nginx.key cert_file: /etc/nginx/ssl/nginx.crt conf_file: /etc/nginx/sites-available/default server_name: localhost
可以在play中使用debug模塊打印變量的值,注意debug支持的參數有var,msg等,var中的變量不要使用 {{}}包裹。
#!/usr/bin/env ansible-playbook - name: test name hosts: webserver vars: myvar: testmyvar tasks: - debug: var=myvar - name: capture output of id command command: id -un register: login - debug: var=login
使用register來注冊一個變量后面使用,register注冊的變量在這個playbook的其他play中也是可以使用的,不局限於這一個play。比如command模塊的輸出如下所示,可以通過login.stdout得到用戶名。注意不同模塊的輸出可能是不一樣的,同一個模塊在不同情況下也不一樣,比如apt模塊安裝nginx,如果機器已經安裝了nginx,則輸出里面change為false,而且不會有stdout,stderr和stdout_lines這些key。如果模塊執行出錯,則其他的host默認不會再執行,可以設置 ignore_erros:True
忽略模塊的錯誤。
其他指定變量的方式如 host_vars目錄,group_vars目錄等。
{ "changed": true, "cmd": [ "id", "-un" ], "delta": "0:00:00.007369", "end": "2016-11-17 15:09:49.061725", "rc": 0, "start": "2016-11-17 15:09:49.054356", "stderr": "", "stdout": "ssj", "stdout_lines": [ "ssj" ], "warnings": [] }
Facts
如果在playbook中配置了 gather_facts:True,則會看到真正的任務開始前,會先執行一個[setup]的模塊,用於收集服務器信息,包括cpu架構,操作系統類型,ip地址等信息。這些信息存儲在特定的變量中,我們稱之為facts。如果你的playbook中不需要這些信息,也可以設置gather_facts:False來加快playbook執行速度,收集服務器信息需要花費不少時間的。
通過命令 ansible webserver -m setup
可以看到ansible的gathter_facts的輸出內容,軟硬件信息都有。因為信息太多,還可以通過在setup模塊加上參數filter來篩選你需要的內容,如果只需要網絡信息,可以這樣: ansible webserver -m setup -a 'filter=ansible_eth*’
,其中ansible_facts這個key是固定的。
127.0.0.1 | SUCCESS => { "ansible_facts": { "ansible_eth0": { "active": true, "device": "eth0", "ipv4": { "address": "xxx.xxx.xxx.xxx", "broadcast": "xxx.xxx.xxx.xxx", "netmask": "xxx.xxx.xxx.xxx.xxx", "network": "xxx.xxx.xxx.xxx" }, "macaddress": "xx.xx.xx.xx.xx.xx", "module": "bnx2", "mtu": 1500, "pciid": "0000:10:00.0", "promisc": false, "type": "ether" }, }, "changed": false }
可以在定義本地facts,在 /etc/ansible/facts.d/
目錄新建example.fact文件,內容如下:
[book] title=Ansible: Up and Running author=Lorin Hochstein publisher=O'Reilly Media
然后在運行playbook的時候就可以通過 ansible_local讀取這些變量了。
另外,還可以通過 set_fact
模塊設置變量,比如之前得到了一個命令的輸出,register到一個變量,然后把我們需要的變量提取出來用set_fact存儲到另外一個變量中,簡化了變量的引用。
- name: test name hosts: webserver gather_facts: True tasks: - name: print ansible_local debug: var=ansible_local - name: capture output of id command command: id -un register: login ignore_errors: True - set_fact: loginuser={{ login.stdout }} - name: show login user debug: var=loginuser
內置變量
命令行傳遞變量
還可以在運行playbook的時候在命令行傳遞變量。如果要傳遞一個包含變量的文件,可以用 $ ansible-playbook greet.yml -e @greetvars.yml
。
- name: pass a message on the command line hosts: localhost vars: greeting: "you didn't specify a message" tasks: - name: output a message debug: msg="{{ greeting }}" $ ansible-playbook greet.yml -e greeting=hiya
變量優先級
ansible在很多地方可以設置變量,盡量不要重名。優先級由高到低如下:
- 命令行的參數, 上面的 -e greeting=‘hello’
。
- host, group中的變量,不管是在inventory中還是yaml文件中定義的。
- Facts變量
- role目錄下的 defaults/main.yml
。
5.3 playbook要點
實用模塊
- 如果想在控制機器而不是遠程機器運行命令,可以用local_action。
- 如果機器沒有啟動起來,需要先等待機器啟動再執行play,用wait_for模塊。
- name: Deploy mezzanine hosts: web gather_facts: False # vars & vars_files section not shown here tasks: - name: wait for ssh server to be running local_action: wait_for port=22 host="{{ inventory_hostname }}" search_regex=OpenSSH - name: gather facts setup:
- 如果不想一次在所有的hosts都執行,可以設置serial參數來設置每次執行幾個host,比如升級服務器,我們不想影響服務,會一個一個跑。可以設置max_fail_percentage來指定最大失敗的比率,比如設置為25%,則如果有4台機器,有2台任務執行失敗則終止整個play,其他任務不再執行。
- name: upgrade packages on servers behind load balancer hosts: myhosts serial: 1 max_fail_percentage: 25 tasks: # tasks go here
###加密數據
一些數據如db密碼等不能直接提交到代碼庫,可以提交一個加密的版本。加密文件可以用ansible-vault工具。運行playbook的時候加上參數
ansible-vault encrypt secrets.yml ansible-vault create secrets.yml ansible-vault view secrets.yml $ ansible-playbook test.yml --ask-vault-pass $ ansible-playbook mezzanine --vault-password-file ~/password.txt
###hosts格式
可以用冒號:表示合並服務器組,:& 求交集等。
指定運行的hosts可以在命令行加上 —limit
。ansible-playbook -l 'staging:&database' playbook.yml
ansible-playbook —limit 'staging:&database' playbook.yml
###Filters
-
filter可以用在很多方面,比如默認值filter。如果database_host沒有定義,則HOST的值設置為localhost。
"HOST": "{{ database_host | default('localhost') }}"
-
針對任務返回值的filter。可以的取值有 failed,changed,skipped,success等。
failed_when: result|failed
-
文件路徑的filter。
vars: homepage: /usr/share/nginx/html/index.html tasks: - name: copy home page copy: src=files/{{ homepage | basename }} dest={{ homepage }}
-
自定義filter。
寫一個自定義的filter,放在項目的 filter_plugins 目錄下即可。下面是一個用於字符串分割的filter模塊,使用時使用filter語法即可。from ansible import errors def split_string(string, seperator=' '): try: return string.split(seperator) except Exception, e: raise errors.AnsibleFilterError('split plugin error: %s, string=%s' % str(e),str(string) ) class FilterModule(object): def filters(self): return { 'split' : split_string, }
###lookups
查找變量可以通過lookup實現,支持file,redis,pipe,cvsfile等多種格式。(redis的需要安裝python的redis模塊)
###復雜循環
- with_items
- with_lines
- with_fileglob
- with_dict
- ...
###debug你的playbook
檢查語法:ansible-playbook --syntax-check playbook.yml 查看host列表:ansible-playbook --list-hosts playbook.yml 查看task列表:ansible-playbook --list-tasks playbook.yml 檢查模式(不會運行): ansible-playbook --check playbook.yml diff模式(查看文件變化): ansible-playbook --check --diff playbook.yml 從指定的task開始運行:ansible-playbook --start-at-task="install packages" playbook.yml 逐個task運行,運行前需要你確認:ansible-playbook --step playbook.yml 指定tags:ansible-playbook --tags=foo,bar playbook.yml 跳過tags:ansible-playbook --skip-tags=baz,quux playbook.yml
#6 角色(Roles)
##6.1 角色基本結構
roles可以簡化playbook編寫,讓playbook更清晰和方便復用。一個名為database的role的目錄結構如下,這些目錄都是可選的,如果你的角色沒有任何handler,則不需要handlers目錄。roles的查找路徑默認是/etc/ansible/roles
,也可以在 /etc/ansible/ansible.cfg
的roles_path
中設置。
roles/database/tasks/main.yml Tasks roles/database/files/ Holds files to be uploaded to hosts roles/database/templates/ Holds Jinja2 template files roles/database/handlers/main.yml Handlers roles/database/vars/main.yml Variables that shouldn’t be overridden roles/database/defaults/main.yml Default variables that can be overridden roles/database/meta/main.yml Dependency information about a role
##6.2 pre_tasks和post_tasks
在角色執行任務前做一些前置工作,任務執行完后做一些后置處理。
- name: test hosts: dbserver vars_files: - secrets.yml pre_tasks: - name: update the apt cache apt: update_cache=yes roles: - role: database post_tasks: - name: send email command: xxx
6.3 依賴角色
如果怕遺漏一些任務,比如設置ntp之類的,可以使用依賴角色的功能。這樣在執行你的角色任務時會先執行依賴角色。
roles/database/meta/main.yml dependencies: - { role: ntp, ntp_server=ntp.ubuntu.com }
##6.4 Ansible Galaxy
可以通過ansible galaxy工具方便的創建一個角色目錄。
ansible-galaxy init -p playbooks/roles database
如果不用-p指定路徑,那么默認是會在當前目錄創建角色的目錄結構。創建后的目錄結構如下:
playbook/roles/database ├── README.md ├── defaults │ └── main.yml ├── files ├── handlers │ └── main.yml ├── meta │ └── main.yml ├── tasks │ └── main.yml ├── templates ├── tests │ ├── inventory │ └── test.yml └── vars └── main.yml
ansible galaxy還是一個開源的角色庫,你可以在其中下載到許多其他人寫好的角色代碼或者提交自己的角色代碼。角色倉庫的使用說明在這里。
##6.5 Ansible Tower
ansible tower是ansible公司提供的一套商用的web管理平台,也有試用版本,還沒有試用過,后續使用了再補充。
#7 加速你的ansible
##7.1 SSH ControlPersist
ControlMaster auto
ControlPath $HOME/.ansible/cp/ansible-ssh-%h-%p-%r ControlPersist 60s
##7.2 Pipelining(ansible的特性)
ansible通常執行的原理是在 ~/.ansible下面創建一個臨時目錄(通過ssh),然后通過sftp或者scp拷貝python腳本到臨時目錄,然后執行這個腳本代碼(再次通過ssh)。使用pipeline可以使得這些操作只要一個ssh連接去執行python腳本。即便是開啟了ControlPersist,這個性能提升也很可觀。在配置文件中加入pipelining=true
即可開啟。
需要注意的是,開啟pipeling要關閉服務器的requiretty功能。增加文件/etc/sudoers.d/disable-requiretty
,其中的內容為 Defaults:ansibleuser !requiretty
,ansibleuser為你的用戶名。
##7.3 Fact Cache
如果你不需要用到服務器信息,可以關閉獲取fact的功能,這樣也可以加快playbook的執行效率。配置文件中加入 gathering = explicit
即可,這樣你要獲取服務器信息,需要顯示的在play中指定。
如果要用到fact信息,可以使用fact緩存,這樣每個機器的fact信息只會獲取一次而不是每次都去獲取。fact緩存支持json,redis,memcached。如果用redis則需要在控制機上安裝python的redis模塊,自然redis也是要安裝的。
[defaults] gathering = smart # 24-hour timeout, adjust if needed fact_caching_timeout = 86400 # You must specify a fact caching implementation # JSON file implementation fact_caching = jsonfile //或者redis,memcached fact_caching_connection = /tmp/ansible_fact_cache
7.4 Parallelism
可以設置ANSIBLE_FORKS
環境變量或者在配置文件加上forks=n
來指定並行執行的host的數目。
7.5 關於異步
ansible的1.7版本開始增加了異步參數 async,也就是說執行一個時間很長的任務時,可以不用等待它結束,而是直接先執行后面的任務,在后續的play中定時檢查任務執行結果即可。
有幾點注意一下,一個是async參數,是指任務執行的超時時間,如果這個時間設置的比任務執行時間短,則任務會超時失敗。poll值為輪詢任務狀態的時間間隔,如果設置為0,表示啟動並忽略,也就是說設置為0才是真正的開始異步執行,也就是直接執行后面的task,而為了知道異步任務執行的結果,可以用async_status來實現。如果poll設置為非0值,則還是阻塞執行的,並非異步。
- hosts: dbserver tasks: - name: simulate long running op (15 sec), wait for up to 45 sec, poll every 5 sec command: /bin/sleep 15 async: 20 poll: 0 register: asynctest - name: check async status async_status: jid="{{ asynctest.ansible_job_id }}" register: job_result until: job_result.finished retries: 30 delay: 2
#8 創建自定義模塊
在某些情況下,可能ansible自帶的模塊不能滿足你的需求,需要自定義模塊。可以通過python或者bash來寫自定義模塊,符合ansible的模塊編寫標准即可,這里有很詳細的文檔。
#9 Docker
docker是目前很火爆的技術,它提供了一套遠程API供第三方程序調用,ansible的docker模塊就是使用了這套API對docker操作。ansible用在docker上主要有兩點:一是編排docker容器。通常一個系統需要很多個docker容器來支持,每個容器都運行一個服務。服務之間需要相互通信,同時你也要保證容器啟動的順序等,原生docker並沒有這些工具支持,ansible則是非常合適的一個選擇。二是創建鏡像。官方的方式是通過Dockerfile來創建鏡像,但是通過ansible來實現更加簡單方便。
基於docker的應用的生命周期是這樣的:
1. 在本地機器創建docker鏡像。 2. 將docker鏡像push到registry。 3. 遠程機器上將鏡像從registry上pull下來。 4. 在遠程機器上啟動容器。
使用ansible之后,則是下面這樣的:
1. 寫好用來創建docker鏡像的playbook。 2. 運行playbook來創建鏡像。 3. 將docker鏡像推送到registry。 4. 寫好一個拉取docker鏡像並啟動容器的playbook。 5. 執行playbook拉取和啟動容器。
參考 :https://ansible-tran.readthedocs.io/en/latest/docs/playbooks_intro.html#about-playbooks