看完 task,接下來來看一下 handler,大概可以翻成處理器,不過還是用原文吧。若 task A 執行後要觸發 task B,就用 handler 來處理。那麼 task A 什麼時候會執行呢?待會會有一個 condition,可以在 task 中設定什麼樣的條件發生可以執行,或者前面看到 command 模組的 creates 參數可以用某個檔案是否存在來決定要不要執行,不過這裡要說的是另一種情況,叫做等冪性 (idempotent),這是 Ansible 的重要性質,無論 playbook 執行多少次,都能確保主機的狀態是一致的。這也是組態工具的一個重要概念喔,重點並不是你做了什麼,而是它的結果(最終狀態)是什麼。我的理解是這樣,在為了確保等冪性的狀況下,並不是所有的 task 都會被執行,如果一個 task 在執行前,主機的狀態會和執行後相同,已經與預期的結果一致,那就不會執行。例如某個 task 是要安裝一個套件,如果這個套件已經安裝,它就不會執行,或者是要複製一個檔案,但目的檔案已經存在,且內容和來源檔案相同,那它也不會複製。所以並不是所有 task 都會被執行。
回到 handler,如果 task A 執行後要觸發 task B,可以在 task A 最後加上 notify,參數是它要觸發的 handler 的 name。handler 和一般 task 沒什麼不同,只是它要被列在 handlers 這個 directive 下,而 handlers 和 tasks 是在同一階層的。文件中的範例如下:
tasks:
- name: template configuration file
template:
src: template.j2
dest: /etc/foo.conf
notify:
- restart memcached
- restart apache
handlers:
- name: restart memcached
service:
name: memcached
state: restarted
- name: restart apache
service:
name: apache
state: restarted
當 "template configuration file" 這個 task 被執行後,會「通知」handlers 中 restart memcached 和 restart apache 這兩個 task 執行。文件說,handler 只會被觸發一次,即使有很多 task 都來通知它也一樣。
另外有一個 listen 的用法,它有點像是 notify 和 handler 的中介者,範例如下:
handlers:
- name: restart memcached
service:
name: memcached
state: restarted
listen: "restart web services"
- name: restart apache
service:
name: apache
state:restarted
listen: "restart web services"
tasks:
- name: restart everything
command: echo "this task will restart the web services"
notify: "restart web services"
"restart everything" 這個 task 會通知 "restart web services",然後 restart memcached 和 restart apache 這兩個 handler task 會 listen "restart web services",所以 restart everything 執行後就會通知 restart memcached 和 restart apache。
在繼續介紹 loop 和 conditional 之前,先說明 Ansible 的變數,因為 loop 和 conditional 的使用都跟變數有關。文件中關於變數的介紹在 https://docs.ansible.com/ansible/latest/user_guide/playbooks_variables.html,篇幅很長,範圍很雜,例子很多,但我覺得缺乏一些統整的範例,所以看完之後還是不太知道實際上該如何使用…。我們還是來看看文件中的說明吧。
Ansible 中的變數大致上可分為三種,一種是自行定義的變數,可以定義在 inventory、playbook 或者外部檔案再引入使用,第二種是 Ansible 搜集而來的機器資訊,稱為 fact,像是 IP、作業系統種類、版本等,第三種用來儲存在遠端機器執行的指令結果,稱為 register。變數名稱可以使用英數字及底線,但開頭一定要是英文字符。
在 inventory 中定義的變數已經介紹過,如果要在 playbook 中定義變數,要放在 vars 這個 directive 之下,例如:
- hosts: webservers
vars:
http_port: 80
register 在前面講到 command 模組有介紹過。
- name: return motd to registered var
command: cat /etc/motd
register: mymotd
fact 的部分,最常用到的應該是 ansible_facts,它的結構類似 map,以下節錄文件中 ansible_facts 的部分內容:
{
"ansible_all_ipv4_addresses": [
"REDACTED IP ADDRESS"
],
"ansible_all_ipv6_addresses": [
"REDACTED IPV6 ADDRESS"
],
"ansible_architecture": "x86_64",
"ansible_bios_date": "09/20/2012",
"ansible_bios_version": "6.00",
"ansible_cmdline": {
"BOOT_IMAGE": "/boot/vmlinuz-3.5.0-23-generic",
"quiet": true,
"ro": true,
"root": "UUID=4195bff4-e157-4e41-8701-e93f0aec9e22",
"splash": true
},
(以下省略)
知道了要如何定義變數,那要怎麼使用呢?文件中說變數代換,背後用的是 Jinja2 的樣版功能引擊,可以用 {{ }} 這種雙大括號的方式引用,例如:
template: src=foo.cfg.j2 dest={{ remote_install_path }}/foo.cfg
所以昨天的範例中,在 j2 樣版中的 {{ ansible_managed }} 就是要引用 ansible_mananged 這個變數,它的值是一個字串 "Ansible managed"。
有時候會想要在一個 task 中重覆作某件事,例如新增若干個使用者、安裝數個套件,這個時候就可以使用 loop。我們直接來看看範例:
- name: add several users
user:
name: "{{ item }}"
state: present
groups: "wheel"
loop:
- testuser1
- testuser2
- name: add several users
user:
name: "{{ item.name }}"
state: present
groups: "{{ item.groups }}"
loop:
- { name: 'testuser1', groups: 'wheel' }
- { name: 'testuser2', groups: 'root' }
在 loop 中可以陣列的方式表示要重覆的項目,而 {{ item }} 是這個陣列中迭代用的變數。雖然 YAML 字串不需要雙引號包住,但若值有使用到 {{ }} 來表示變數,要用雙引號包住,以避免 YAML 解析時出錯。文件中還有很多 loop 的範例及用法,請自行參考。
conditional 是條件的意思,在程式語言中差不多是 if 的功能。文件中這一部分在討論 play、task、variable(變數)之間的相依狀況,以及 inventory 中 group 根據某些 host 的條件而變化。感覺起來有點複雜,因為文件中這一節把 conditional、loops、register、variable 什麼的都混在一起了,所以它的範例有的看起來有點複雜。
基本上 conditioanl 是由 when 這個 directive 來負責條件的引入,這個還蠻直覺的,就是 when 後面的狀況成立時就執行這個 task,請看下面的範例:
tasks:
- name: "shut down Debian flavored systems"
command: /sbin/shutdown -t now
when: ansible_facts['os_family'] == "Debian"
# note that all variables can be directly in conditionals without double curly braces
tasks:
- name: "shut down CentOS 6 and Debian 7 systems"
command: /sbin/shutdown -t now
when: (ansible_facts['distribution'] == "CentOS" and ansible_facts['distribution_major_version'] == "6") or
(ansible_facts['distribution'] == "Debian" and ansible_facts['distribution_major_version'] == "7")
這裡用到 andible_facts。文件上還有一些範例,例如配合 Jinja2 的 test 或 filter 作為 when 的判斷條件,這部分請再參考文件。接下來是 conditional 和 loop 互相搭配的範例:
tasks:
- command: echo {{ item }}
loop: [ 0, 2, 4, 6, 8, 10 ]
when: item > 5
關於 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
今天主要介紹了 inventory 和 playbook 的設置方式,以及變數的使用,因為可供設定的內容非常多,所以只是簡單地看一下文件中的說明,大概知道有這些東西就可以了,等到真正要使用的時候再回來查閱吧。