有時候我們會需要編寫一些比較小型的 Playbook,例如每次遇到重大漏洞時,用來修補漏洞的 Playbook。這樣的 Playbook 通常 tasks
不多,有時甚至只要 1 ~ 2 個 tasks
即可搞定。但假設你同時管理了 Ubuntu 及 CentOS,兩者分別是用 apt 與 yum 來管理套件,這導致可能需要為不同的 OS 各寫一個 Playbook。
有沒有辦法可以只編寫一個 Playbook,同時將 apt 與 yum 的 task 都寫進去,但能動態的判斷每次執行 Playbook 時該啟用哪一個 task 呢?
答案當然是可以,這需要用到 Playbook 中的 when
來達成。
基本上 when
就像是一般編寫程式會用到的 if 判斷式一樣,你可以告知 Ansible 在哪些狀況之下才要執行某個 task
,藉此我們就能將多種不同情況的 tasks 寫在同一個 playbook.yml 之中,讓 Ansible 來依據狀況執行後續的動作。
回到前面提到的案例:
「編寫一個 Playbook 裡面包含了 Ubuntu 的 apt 及 CentOS 的 yum,當要執行 Playbook 時可以彈性的讓它自動執行正確的 tasks
。」
目前比較常看到有幾種寫法:
- 利用 Variables 自行控制
- 透過 register 自動註冊新的 Variables
- 透過 gather facts 取得遠端主機資訊
以下一一說明這幾種寫法。
利用 Variables 自行控制
這種寫法很簡單,也很單純,基本上就是手動控制每一次執行 Playbook 是要執行 apt 還是 yum,Playbook 的 tasks
多半會長成下面的模樣。
tasks:
- apt: name=openssl state=latest
when: remote_os == 'Ubuntu'
- yum: name=openssl state=latest
when: remote_os == 'CentOS'
因此在執行 Playbook 的時候,使用者要自行透過 -e
告知 Ansible 這一次的 remote_os 是哪一種,藉此控制會執行哪一些 tasks
。
ansible-playbook playbook.yml -e remote_os=Ubuntu
ansible-playbook playbook.yml -e remote_os=CentOS
當然如果你的主機多半都是 Ubuntu,你也可以在 Playbook 中先設定 remote_os 的預設值為 Ubuntu。
vars:
remote_os: Ubuntu
tasks:
- apt: name=openssl state=latest
when: remote_os == 'Ubuntu'
- yum: name=openssl state=latest
when: remote_os == 'CentOS'
當執行
ansible-playbook playbook.yml
預設就是會透過 apt 來更新套件。
反之如果是針對 CentOS,就要按下面的指令執行,透過 -e
將 remote_os 用 CentOS 來覆蓋。
ansible-playbook playbook.yml -e remote_os=CentOS
第一種寫法透過 Variables 來自行控制,其實也是一個好方法,唯一的缺點就是不夠自動化,難道不能讓 Ansible 自己知道這一次的 remote_os 是哪一種嗎?讓我們繼續看下去。
透過 register 自動註冊新的 Variables
Ansible 的 Playbook 除了 when
之外,還提供了 register
這個好用的功能。它的功能很單純且強大,它即是讓你可以在 task 中將 task 的執行結果註冊成一個新的 Variables。例如:
tasks:
- command: lsb_release -i -s
register: remote_os
我們透過 task 讓 Ansible 執行 command module,執行 lsb_release -i -s
這個指令,此指令可以幫助我們取得遠端主機的 OS 資訊。所以當 Ansible 執行上面的 task 時,就會自動將 command 執行的結果註冊成 remote_os,於是在後面的 task,就可以透過 when
來根據 remote_os 的內容來決定要不要執行 task。修改後的 playbook.yml 可能會如下:
tasks:
- command: lsb_release -i -s
register: remote_os
- apt: name=openssl state=latest
when: remote_os.stdout == 'Ubuntu'
- yum: name=openssl state=latest
when: remote_os.stdout == 'CentOS'
為何上面的 when
不是直接寫 remote_os == ‘Ubuntu’,中間多了 .stdout
?
這是因為透過 register
註冊的 Variables 不只是包含此 task 回傳的內容,還會包含 Ansible 執行此 task 時的其他資訊,這些資訊會全部用 json 格式一併存入 Variables。若透過 debug
查看此範例中被 register
的 remote_os,它全部會是類似下面的 json 資料。
{
"changed": true,
"cmd": [
"lsb_release",
"-i",
"-s"
],
"delta": "0:00:00.063334",
"end": "2016-03-06 21:07:30.171046",
"rc": 0,
"start": "2016-03-06 21:07:30.107712",
"stderr": "",
"stdout": "Ubuntu",
"stdout_lines": [
"Ubuntu"
],
"warnings": []
}
而我們想要用來判斷 OS 版本的資訊被放在 "stdout": "Ubuntu"
,因此 when
需要寫成 when: remote_os.stdout == 'Ubuntu'
。
由此可得知第二種寫法的原理,首先透過第一個 task 將 Linux OS 版本資訊註冊為 Variables,再讓後續的 task 用 when
搭配此 Variables 作出判斷,以此達到全自動化、不需人工判斷這次執行 Playbook 的主機 OS 為何。
透過 gather facts 取得遠端主機資訊
最後介紹第三種寫法,基本上原理與第二種相同,差別只在於 Variables 該如何取得。
其實 Ansible 在執行的時候,它同時也會取得許多有用的主機資訊,官方文件稱這些為 Facts。
我們先透過下面的指令查看到底 Ansible 在背後取得了哪一些有用的資訊。
ansible your_hosts -m setup
有興趣者可以自行嘗試,它會列出一大堆給你,我就只列舉與本案例有關的項目。
"ansible_facts": {
"ansible_distribution": "Ubuntu",
"ansible_distribution_major_version": "14",
"ansible_distribution_release": "trusty",
"ansible_distribution_version": "14.04",
"ansible_lsb": {
"codename": "trusty",
"description": "Ubuntu 14.04.4 LTS",
"id": "Ubuntu",
"major_release": "14",
"release": "14.04"
},
"ansible_os_family": "Debian",
"ansible_pkg_mgr": "apt",
}
由此可以發現,Ansible 已經有收集了 Linux OS 的版本資訊,所以我們根本不需要自己寫 task 判斷 OS 及自己 register
,只要直接取用 Ansible 已收集的資訊即可,於是 playbook.yml 可修改如下。
gather_facts: true
tasks:
- apt: name=openssl state=latest
when: ansible_distribution == 'Ubuntu'
- yum: name=openssl state=latest
when: ansible_distribution == 'CentOS'
第一行 gather_facts: true
是告知 Ansible 一定要幫我收集資訊,接著我可以直接在 when
中直接使用 ansible_distribution
作為判斷依據。
在 ansible.cfg
中,預設值為一定會收集 Facts
,若你不喜歡你也可以修改 ansible.cfg
,改為預設不主動收集 Facts
。ansible.cfg
預設會放在 /etc/ansible/ansible.cfg,編輯 ansible.cfg
找到 gathering
= 即可設定。
結語
透過這次的案例及以上三種寫法,我們看了 Ansible Playbook 的幾項功能:
- when 用來當做判斷式使用,根據判斷結果決定要不要執行此 task
- Variables
可搭配
when
使用,另外有一個特點是,在指令列中用-e
注入的 Variables 會覆蓋 playbook.yml 裡面的 Variables。 - register
你可以將 task 的結果註冊成新的 Variables,因此如果 Playbook 中有更複雜的邏輯需要處理,其實你可以用自己擅長的程式語言寫成小程式,讓小程式將判斷結果用 json 輸出。將此程式放在遠端主機上,讓 Ansible 透過
tasks
執行它,即可將判斷結果註冊為 Variables 供後面的 task 使用。 - gather_facts Ansible 自己已經取得了很多有用的主機資訊,再寫 Playbook 之前,不妨先查看一下,搞不好你需要的 Variables,Ansible 早已幫你預備好了。
以上就是這次分享的內容,如果你有看到更好的案例或有更好的寫法,也歡迎分享給我參考與學習,教學相長,一起成長嘍!
參考資料: