參考:http://www.zsythink.net/archives/2624
不過在開始介紹它們之前,我們先來描述一個工作場景。
當我們修改了某些程序的配置文件以后,有可能需要重啟應用程序,以便能夠使新的配置生效,那么,如果使用playbook來實現這個簡單的功能,該怎樣編寫playbook呢?
我們來試試,此處我們使用nginx作為示例,雖然nginx可以使用'nginx -s reload'命令重載配置,但是此處的示例中並不會使用這個命令,而是用nginx類比那些需要重啟生效的應用。
假設我們想要將nginx中的某個server的端口從8080改成8088,並且在修改配置以后重啟nginx,那么我們可以編寫如下劇本。
[root@node1 ansible]# cat nginx.yml
---
- hosts: test70
remote_user: root
tasks:
- name: Modify the configuration
lineinfile:
path=/etc/nginx/conf.d/test.zsythink.net.conf
regexp="listen(.*)8080(.*)"
line="listen\1 8088 \2"
backrefs=yes
backup=yes
- name: restart nginx
service:
name=nginx
state=restarted
配置文件解析
lineinfile: #調用的模塊是對行進行修改
path=/etc/nginx/conf.d/test.zsythink.net.conf #需要修改的目標文件
regexp="listen(.*)8080(.*)"#正則匹配,可以匹配到配置端口8080映射的哪一行
line="listen\1 8088 \2"#根據正則匹配到的替換,此處\1 \2分別代表上一行(.*)匹配到的內容寄
backrefs=yes #默認如果沒有匹配到則會把line加到最后一行,加次參數沒有匹配到則不作修改
backup=yes#修改前備份,會在當前操作的文件夾下創建一個加了時間信息的文件

原始配置文件如下,需要把端口修改成8088然后重啟nginx

上述play表示修改test70主機的/etc/nginx/conf.d/test.zsythink.net.conf配置文件,將監聽端口8080改為監聽端口8088,端口修改完成后,重啟服務。
在執行這個playbook之前,我們先來確認一下test70主機的8080端口是否被監聽

可以看到test70主機上的8080正常被監聽,那么現在我們來執行一下上述playbook,看一下執行效果
執行后可以看到,play中的兩個任務都被正常執行了,如下圖所示
ansible-playbook nginx.yml

這樣沒有任何問題,與我們預期的一樣,端口號從8080修改為8088,重啟了服務
那么,我們再來重復執行一遍上述playbook試試,看看會出現什么情況,重復執行效果如下
如上圖所示,當我們再次執行同樣的playbook時,由於配置文件中的端口號已經是8088,所以,任務"Modify the configuration"的狀態為OK(換句話說,這個任務並沒有在遠程主機進行任何實際操作),這是由於ansible的冪等性造成的(前文已經對冪等性做出了解釋,此處不再贅述),因為目標狀態與我們預期的狀態一致,所以ansible並沒有做任何改動,這是完全正常的,從上圖可以看出,任務"restart nginx"也正常的執行了,而且是"真正的"執行了,換句話說就是它的確重啟了對應的nginx服務,對遠程主機進行了實際的操作。
第二次運行劇本的過程似乎沒有什么問題,但是仔細想想,又有些不妥,因為我們重啟服務的目的是為了在修改配置文件以后使新的配置生效,而第二次運行劇本的這種情況下,我們並沒有真正修改服務器配置,因為服務器配置本來 就與我們預期的一致,但是,在沒有修改配置的情況下,仍然重啟了服務,這種重啟是不需要的,我們想要達到的效果是,如果配置文件發生了改變,則重啟服務,如果配置文件並沒有被真正的修改,則不對服務進行任何操作,這種情況下,我們該怎們辦呢?
handlers就是來解決這種問題的,此處我們先大概的描述一下handlers的概念,后面會給出示例,你可以把handlers理解成另一種tasks,handlers是另一種'任務列表',handlers中的任務會被tasks中的任務進行"調用",但是,被"調用"並不意味着一定會執行,只有當tasks中的任務"真正執行"以后(真正的進行實際操作,造成了實際的改變),handlers中被調用的任務才會執行,如果tasks中的任務並沒有做出任何實際的操作,那么handlers中的任務即使被'調用',也並不會執行。這樣說似乎不容易被理解,我們來寫一個小示例,示例如下。
[root@node1 ansible]# cat test2.yml
---
- hosts: test70
remote_user: root
tasks:
- name: Modify the configuration
lineinfile:
path=/etc/nginx/conf.d/test.zsythink.net.conf
regexp="listen(.*)8080(.*)"
line="listen\1 8088 \2"
backrefs=yes
backup=yes
notify:
restart nginx
handlers:
- name: restart nginx
service:
name=nginx
state=restarted
如上例所示,我們使用handlers關鍵字,指明哪些任務可以被'調用',之前說過,handlers是另一種任務列表,你可以把handlers理解成另外一種tasks,你可以理解成它們是'平級'的,所以,handlers與tasks是'對齊'的(縮進相同),上例中的handlers中只有一個任務,這個任務的名稱為"restart nginx",之前也說明過,handlers中的任務需要被tasks中的任務調用,那么上例中,"restart nginx"被哪個任務調用了呢?很明顯,"restart nginx"被"Modify the configuration"調用了,沒錯,如你所見,我們使用notify關鍵字'調用'handlers中的任務,或者說,通過notify關鍵字'通知'handlers中的任務,所以,綜上所述,上例中的play表示,如果"Modify the configuration"真正的修改了配置文件(實際的操作),那么則執行"restart nginx"任務,如果"Modify the configuration"並沒有進行任何實際的改動,則不執行"restart nginx" ,通常來說,任務執行后如果做出了實際的操作,任務執行后的狀態為changed(前文中解釋過changed狀態,此處不再贅述),所以,任務執行后的狀態為changed則會執行對應的handlers,這就是handlers的作用,聰明如你肯定已經明白了,動手執行一下上述playbook試試吧。

handlers是另一種任務列表,所以handlers中可以有多個任務,被tasks中不同的任務notify,示例如下
從上圖可以看出,handler執行的順序與handler在playbook中定義的順序是相同的,與"handler被notify"的順序無關。
如上圖所示,默認情況下,所有task執行完畢后,才會執行各個handler,並不是執行完某個task后,立即執行對應的handler,如果你想要在執行完某些task以后立即執行對應的handler,則需要使用meta模塊,示例如下
[root@node1 ansible]# cat test.yml
---
- hosts: test70
remote_user: root
tasks:
- name: make testfile1
file: path=/testdir/testfile1
state=directory
notify: ht2
- name: make testfile2
file: path=/testdir/testfile2
state=directory
notify: ht1
- meta: flush_handlers
- name: task3
file: path=/testdir/testfile3
state=touch
notify: handler3
handlers:
- name: ht1
file: path=/testdir/ht1
state=touch
- name: ht2
file: path=/testdir/ht2
state=touch
- name: handler3
file: path=/testdir/ht3
state=touch
如上例所示,我在task1與task2之后寫入了一個任務,我並沒有為這個任務指定name屬性,這個任務使用meta模塊,meta任務是一種特殊的任務,meta任務可以影響ansible的內部運行方式,上例中,meta任務的參數值為flush_handlers,"meta: flush_handlers"表示立即執行之前的task所對應handler,什么意思呢?意思就是,在當前meta任務之前,一共有兩個任務,task1與task2,它們都有對應的handler,當執行完task1與task2以后,立即執行對應的handler,而不是像默認情況那樣在所有任務都執行完畢以后才能執行各個handler,那么我們來實際運行一下上述劇本,運行結果如下
刪除剛剛測試生成的4個文件然后執行

正如上圖所示,meta任務之前的任務task1與task2在進行了實際操作以后,立即運行了對應的handler1與handler2,然后才運行了task3,在所有task都運行完畢后,又逐個將剩余的handler根據情況進行調用。
聰明如你一定想到了,如果想要每個task在實際操作后都立馬執行對應handlers,則可以在每個任務之后都添加一個meta任務,並將其值設置為flush_handlers 所以,我們可以依靠meta任務,讓handler的使用變得更加靈活,快動手試試吧。
我們還可以在一個task中一次性notify多個handler,怎樣才能一次性notify多個handler呢?你可能會嘗試將多個handler使用相同的name,但是這樣並不可行,因為當多個handler的name相同時,只有一個handler會被執行,所以,我們並不能通過這種方式notify多個handler,如果想要一次notify多個handler,則需要借助另一個關鍵字,它就是'listen',你可以把listen理解成"組名",我們可以把多個handler分成"組",當我們需要一次性notify多個handler時,只要將多個handler分為"一組",使用相同的"組名"即可,當notify對應的值為"組名"時,"組"內的所有handler都會被notify,這樣說可能還是不容易理解,我們來看個小示例,示例如下
[root@node1 ansible]# cat test3.yml
---
- hosts: test70
remote_user: root
tasks:
- name: task1
file: path=/testdir/testfile
state=touch
notify: handler group1
handlers:
- name: handler1
listen: handler group1
file: path=/testdir/ht1
state=touch
- name: handler2
listen: handler group1
file: path=/testdir/ht2
state=touch
如上例所示,handler1與handler2的listen的值都是handler group1,當task1中notify的值為handler group1時,handler1與handler2都會被notify,還是很方便的。
即兩個handler依賴一個task如果task有修改則會執行handler
執行效果如下
