一、Playbook的介紹
Playbook是Ansible的配置,部署,編排語言。他們可以被描述為一個需要希望遠程主機執行命令的方案,或者一組IT程序運行的命令集合。
當執行一些簡單的改動時ansible命令是非常有用的,然而它真的作用在於它的腳本能力。當對一台機器做環境初始化的時候往往需要不止做一件事情,這時使用playbook會更加適合。通過playbook你可以一次在多台機器執行多個指令。通過這種預先設計的配置保持了機器的配置統一,並很簡單的執行日常任務。
Playbook還開創了很多特性,它可以允許你傳輸某個命令的狀態到后面的指令,如你可以從一台機器的文件中抓取內容並附為變量,然后在另一台機器中使用,這使得你可以實現一些復雜的部署機制,這是ansible命令無法實現的。
二、YAML介紹
Ansible使用標准的YAML解析器,使用YAML文件語法即可書寫playbook。
YAML是一個可讀性高的用來表達資料序列的格式,YAML參考了其他多種語言,包括:XML、C語言、Python、Perl以及電子郵件格式RFC2822等。Clark Evans在2001首次發表了這種語言。
Playbook組成結構
Inventory #定義管理主機(清單文件)
Modules #定義模塊
Ad Hot Commands
Playbooks
Variables #變量元素,可傳遞給Tasks/Templates使用;
Tasks #任務元素,即調用模塊完成任務;
Templates #模板元素,可根據變量動態生成配置文件;
Hadlers #處理器元素,通常指在某條件滿足時觸發的操作;
Roles #角色元素
執行一個Playbook
使用Playbook時通過ansible-playbook命令使用,它的參數和ansible命令類似,如參數-k(–ask-pass) 和-K (–ask-sudo) 來詢問ssh密碼和sudo密碼,-u指定用戶,這些指令也可以通過規定的單元寫在playbook里。ansible-playbook的簡單使用方法:
ansible-playbook /etc/ansible/roles/sites.yml
也可以並行執行playbook,這里的示例是並行的運行playbook,並行的級別是10個進程:
ansible-playbook /etc/ansible/roles/sites.yml -f 10
playbook的寫法例子:
分別講解playbook語言的多個特性:
二、Playbook基礎組件
主機(hosts)和用戶(users)
Playbook中的每一個play的目的都是為了讓某個或某些主機以某個指定的用戶的身份執行任務,hosts用於指定要執行任務的主機,其可以是一個或者多個有冒號分割主機組,這和前面的ansible命令提到的hosts使用一樣的語法,remote_user則用於指定遠程主機上的執行任務的用戶,如上面實例中:
不過,remote_user也可以用於task中:
- hosts: web
remote_user: root
tasks:
- name: test connection
ping:
remote_user: yourname
也可以通過指定其通過sudo的方式遠程主機上執行任務,其可用於play全局或某task,此外,甚至可以在sudo時使用sudo_user指定sudo時切換的用戶。
- hosts: web
remote_user: root
sudo:yes
sudo_user:yourname
如果需要在使用sudo時指定密碼,可在運行ansible-playbook命令時加上--ask-sudo-pass(-K).如果使用sudo時,playbook疑似被掛起,可能是在sudo prompt處被卡主了,這時可執行Control-C殺死卡住的任務,重新運行一次。
Tasks列表
play的主體部分是task列表,task列表中的各任務按次序逐個在hosts中指定的主機上執行,即在所有主機上完成第一個任務后再開始第二個任務,在運行playbook時(從上到下執行),如果一個hosts執行task失敗,這個host將會從整個playbook的rotation中移除,如果發生執行失敗的情況,請修正playbook中的錯誤,然后重新執行即可。
Task的目的是使用指定的參數執行模塊,而在模塊參數中可以使用變量,模塊執行是冪等的,這意味着多次執行是安全的,因為其結果一致。
對於command 模塊和shell 模塊,重復執行playbook,實際上是重復運行同樣的命令。如果執行的命令類似於‘chmod’或者‘setsebool’這種命令,這沒有任何問題,也可以使用一個叫做‘creates’的flag使得這兩個module變得具有‘冪等’特性(不是必要的。)
每一個task必須有一個名稱name,這樣在運行playbook時,從其輸出的任務信息中可以很好的辨別出是屬於哪一個task的,如果沒有定義name,‘action’的值將會用作輸出信息中的標記特定的task。
定義一個task,以前有一種格式:‘action:module options’ (可能在一些老的playbook中還能見到),現在推薦使用常見的格式:“module:options”,本文檔使用的就是這種格式。
- hosts: web
vars: #定義變量
http_port: 80
max_client: 200 #定義變量是使用兩個花括號括起來,中間最好有空格,{{ http_port }}
remote_user: root
task:
- name: ensure apache is at the latest version
yum: name=httpd state=latest
意思是:針對web組執行一個task使用root用戶執行,而這個task任務是“ensure apache is at the latest version”,然后調用yum模塊進行httpd的檢測,這里的模塊參數的含義都跟前面說的常用模塊介紹時是一樣的,詳情查看前面的常用模塊介紹
在眾多模塊中,只有command和shell模塊僅需要給定一個列表無需使用key-value格式,例如:
tasks:
- name: disable selinux
command: /sbin/setenforce 0
在使用command和shell模塊時,我們需要關心返回碼信息,如果有一條命令,它的成功執行的返回碼不是0(ansible會終止運行),我們可以使用如下方式替代,強制返回成功:
tasks:
- name: run this command and ignore the result
shell: /usr/sbin/somecommand || /bin/true
或者使用ignore_error來忽略錯誤信息:
tasks:
- name: run this command and ignore the result
shell: /usr/bin/somecommand
ignore_errors: True
變量(variables)和模板(template)
在Playbook中可以直接定義變量(當然前面我們也在inventory中添加變量),如下所示:
- hosts: web
vars:
- package:httpd
其中vars是固定格式,而package就是變量名,httpd就是變量值。
定義變量名的時候注意一些規范,變量名可以是字母、下划線、數字,但必須是以字母開頭,變量名包含中線、空格、點和以數字開頭的變量名都是不規范的。
對於定義好的變量可以在task或者template中使用,但一般playbook中定義的變量都是給模板使用的,Ansible使用jijia2模板框架,這個后面講,現在只需要知道變量可以在task和template中使用即可,如下在task中調用變量:
tasks:
- name: ensure apache is at the lastest version
yum: name={{ package }} state=latest
在task或template中引用變量都是使用雙花括號,中間引入變量名即可,如:{{ VAR_NAME }}.
另外說明,在playbook中調用的變量不僅僅是在play中自定義的變量,也可以是ansible中所定義的所有變量,比如說在ansible中有一個setup模塊,就是用來獲取客戶端所有的信息,如內存、CPU、IP、主機名、磁盤等信息,這些信息都是key-value格式的,每當我們執行playbook時首先會需要執行setup模塊,也就意味着task或template中引用setup模塊輸出的變量。如:ansible_all_ipv4_addresses都可以在task或者template使用{{}}去引用。
引用變量的方法除了上述的方式外,還可以在hosts中定義變量,此外還可以在命令行使用ansible-playbook -e 指定變量 playbook.yml,例如:
ansible-playbook -e http_port=80 ceshi.yml
最后可以在roles中定義變量
Handlers和notify
Handlers其實挺好理解的就是發生改變時執行提前定義好的操作。
上面我們提到過,模塊具有冪等性,所以當遠端系統被人改動時,可以重放playbook達到恢復的目的,playbook本身可以識別這種改動,並且有一個基本的event system(系統事件),可以響應這種改動。
(當發生改動時)'notify' 動作會在playbook的每一個task結束時被觸發,而且即使有多個不同的task通知改動的發生'notify' 動作只會觸發一次。
這里有一個例子,當一個文件的內容被改動時,重啟兩個services:
- name: write the apache config file
copy: src=/srv/httpd.conf dest=/etc/httpd/conf/httpd.conf
notify:
- restart httpd
"notify"下列出的即是handlers。
handlers也是一些task的列表,通過名字來引用,它們和一般的task並沒有什么區別,Handlers是由通知者進行notify,如果沒有被notify,handlers不會執行,不管有多少個通知者進行了notify,等到play中的所有task執行完成之后,handlers也只會被執行一次。
這里是一個handlers的示例:
handlers:
- name: restart apache #這里和notify保持一致
service: name=httpd state=restarted
下面給一個安裝服務然后使用Handlers的例子:
- hosts:web
remote_user:root
task:
- name: ensure apache is at the latest version
yum: name=httpd state=latest
- name: write the apache config file
copy: src=/srv/httpd.conf dest=/etc/httpd/conf/httpd.conf
- name: ensure apache is running
service: name=httpd state=started
這個play就做了三個task,第一是檢測httpd是否是最新版,如果不是就進行安裝,第二就是從服務器copy一份定義好的配置文件到目標主機;第三就是啟動httpd服務器。
首先需要在ansible服務器上提供一個/srv/httpd.conf配置文件,不然執行時就會報錯,然后使用ansible-playbook執行。
ansible-playbook sites.yml
如果修改了配置文件,是不是需要reload或restart后你的配置文件才能生效。但是上面的配置就算你改完 配置文件再次執行playbook,配置文件雖然過去了,但是ansible冪等性,配置文件是無法生效的,而handlers就是為了解決此類問題而生的,我們可以給copy文件的那個task定義一個handlers,一旦由配置文件改動就調用提前定義的handlers進行處理,如下:
- hosts: test
remote_user: root
tasks:
- name: ensure apache is at the latest version
yum: name=httpd state=latest
- name: write the apache config file
copy: src=/srv/httpd.conf dest=/etc/httpd/conf/httpd.conf
notify:
- restart httpd
- name: ensure apache is running
service: name=httpd state=started
handlers:
- name: restart httpd
service: name=httpd state=restarted
這個時候再次修改配置文件然后執行playbook,當執行到第二個任務后檢測到定義了notify,當整個play的task都執行完畢后,就會找handlers,匹配到名稱后就會執行對應的模塊。
注意:notify定義的restart httpd 必須與handlers定義的name名稱相同,不然會報:not found in any of the known handlers
Register
經常在playbook中,存儲某個命令的結果在變量中,以備日后訪問是很有用的,而‘register’就是用來決定把結果存儲在哪個變量中,結果參數可以用在模板中,動作條目,或者when語句,像下面的例子:
- name: test play
hosts: all
tasks:
- shell: cat /etc/motd
register: motd_contents
- shell: echo "motd contains the word hi"
when: motd_contents.stdout.find('hi') != -1
就像上面展示的那樣,這個注冊后的參數的內容為字符串’stdout’是可以訪問。這個注冊了以后的結果,如果像上面展示的,可以轉化為一個list(或者已經是一個list),就可以在任務中的”with_items”中使用,“stdout_lines”在對象中已經可以訪問了,當然如果你喜歡也可以調用“home_dirs.stdout.split()” , 也可以用其它字段切割:
- name: registered variable usage as a with_items list
hosts: all
tasks:
- name: retrieve the list of home directories
command: ls /home
register: home_dirs
- name: add home dirs to the backup spooler
file: path=/mnt/bkspool/{{ item }} src=/home/{{ item }} state=link
with_items: home_dirs.stdout_lines
# same as with_items: home_dirs.stdout.split()
提示與技巧:
在playbook執行輸出信息的底部,可以找到關於托管節點的信息。
其中:
ok:表示執行成功但沒做過任何變動的任務;
changed:表示執行成功但做過變動的任務;
unreachable:表示無法到達的任務;
failed:表示執行失敗的任務。
如果你想看到執行成功的模塊輸出信息,使用--verbose即可,(否則只有執行失敗的才會由輸出信息)
ansible-playbook paaybook.yml --verbose
如果安裝了cowsay軟件包,ansible playbook的輸出已經進行廣泛的升級,可以嘗試一下!
在執行一個playbook之前,想看看這個playbook的執行會影響到哪些hosts,你可以這樣做:
ansible-playbook paybook.yml --list-hosts
在執行一個playbook之前,想看看這個playbook是否有語法錯誤,可以這樣做:
ansible-playbook -C playbook.yml 或
ansible-playbook playbook.yml --check 這樣不會執行結果,只會查看playbook是否有語法錯誤。
三、Playbook的其它特性
條件測試
如果需要根據變量、facts(setup)或此前任務的執行結果來作為某task執行與否的前提時要用到條件測試,在Playbook中條件測試使用when子句,在task后添加when子句即可使用條件測試:when子句支持jinjia2表達式或句法,例如:
tasks:
- name: "shutdown Debian"
command: /sbin/shutdown -h now
when: ansible_os_family == "Debian"
或者多條件判斷:
tasks:
- name: “shutdown Centos6 system”
command: /sbin/shutdown -t now
when:
- ansible_distribution == "Centos"
- ansible_distribution_major_version == "6"
意思就是如果對方的系統是centos並且版本是6就進行關機處理,ansible_os_family這個變量是從facts中提取的。
也可以使用組名條件判斷:
tasks:
- name: "shut down CentOS 6 and Debian 7 systems"
command: /sbin/shutdown -t now
when: (ansible_distribution == "CentOS" and ansible_distribution_major_version == "6") or
(ansible_distribution == "Debian" and ansible_distribution_major_version == "7")
再如:
- host: web
remote_user: root
vars:
- http_port: 80
tasks:
- name: install package
yum: name=nginx
- name: copy template for centos7
template: src=/etc/ansible/httpd.conf dest=/etc/httpd/conf/httpd.conf #template一般在playbook.yml同級目錄中創建一個templates目錄,這樣的話在這里就可以直接寫文件template文件名不用再添加路徑,當然如果沒有template目錄的話這里也可以寫template文件所在的絕對路徑。
when: ansible_distribution_major_version == "7"
notify: restart service
- name: copy template for centos6
template: src=/etc/ansible/httpd.conf6.j2 dest=/etc/httpd/conf/httpd.conf
when: ansible_distribution_major_version == "6"
notify: restart service
- name: start service
service: name=httpd state=started enabled=yes
handlers:
- name: restart service
service: name=httpd state=restarted
另外也可以自定義變量,當值為某某時執行什么動作,如下:
- hosts: all
vars:
exist: "True"
tasks:
- name: creaet file
command: touch /tmp/test.txt
when: exist | match("True")
- name: delete file
command: rm -rf /tmp/test.txt
when: exist | match("False")
意思是,當exist變量值為True時就創建文件,當值為False時就刪除文件。
迭代:
當有需要重復性執行的任務時,可以使用迭代機制,其使用格式為:"將需要迭代的內容定義為item變量引用,並通過with_items語句指明迭代的元素列表即可",例如:
tasks:
- name: "Install Packages"
yum: name={{ item }} state=latest
with_items:
- httpd
- mysql-server
- php
事實上,with_items中可以使用元素還可以為hashes,例如添加用戶和組:
tasks:
- name: "Add users"
user: name={{ item.name }} state=present groups={{ item.groups }}
with_items:
- { name:'test1', groups:'wheel'}
- { name:'test2', groups:'root'}
其中引用變量時前綴item變量是固定的,而item后跟的鍵名就是在with_items中定義的字典鍵名。
迭代功能在做批量處理時非常有用,比如系統初始化時一般要安裝很多初始化的包就可以使用迭代了。
更多功能查看官網。
tags
在一個playbook中,我們一般會定義很多個task,如果我們只想執行某個task或多個task時就可以使用tags標簽功能了,格式如下:
cat ceshi.yml
- hosts: web
vars:
http_port: 80
max_clients: 200
remote_user: root
tasks:
- name: ensure apache is at the latest version
yum: name=httpd state=latest
- name: write the apache config file
template: src=/srv/httpd.j2 dest=/etc/httpd.conf
notify:
- restart apache
- name: ensure apache is running
service: name=httpd state=started
tags:
- hosts
handlers:
- name: restart apache
service: name=httpd state=restarted
為復制hosts文件定義了一個tags,tags為hosts。那么在執行此playbook時可通過ansible-playbook命令使用–tags選項能實現僅運行指定的tasks而非所有的,如下:
查看tags
事實上,不光可以為單個或多個task指定同一個tags。playbook還提供了一個特殊的tags為always。作用就是當使用always當tags的task時,無論執行哪一個tags時,定義有always的tags都會執行。
cat ceshi.yml
- hosts: web
vars:
http_port: 80
max_clients: 200
remote_user: root
tasks:
- name: ensure apache is at the latest version
yum: name=httpd state=latest
- name: write the apache config file
template: src=/srv/httpd.j2 dest=/etc/httpd.conf
tags:
- copy
notify:
- restart apache
- name: ensure apache is running
service: name=httpd state=started
tags:
- always
handlers:
- name: restart apache
service: name=httpd state=restarted
當你了解了Playbook的以上基礎知識后基本可以使用playbook來編排一些自動化任務了,上面介紹了playbook的Host、user、task、variables、template等基本組成元素的使用,以及條件測試、迭代、tags等特性,下面就開始介紹playbook中非常重要的一個概念roles,另外對於playbook的template元素,不光可以簡單的調用變量,並且支持各種操作符,在使用playbook時,模板也是一個不可或缺的功能。
ansible-playbook的shell模塊中的管道符‘|’的使用方法
ansibl下的
- name: Run expect to wait for a successful PXE boot via out-of-band CIMC
shell: | #在另一個解釋器下執行
set timeout 300
spawn ssh admin@{{ cimc_host }}
expect "password:"
send "{{ cimc_password }}\n"
expect "\n{{ cimc_name }}"
send "connect host\n"
expect "pxeboot.n12"
send "\n"
exit 0
args:
executable: /usr/bin/expect
delegate_to: localhost