現代 IT 人一定要知道的 Ansible 自動化組態技巧
18. 如何在 Playbooks 使用 loops?
在 Shell Script 裡,我們會使用 for 和 while 等迴圈 (loop) 來簡化重複的程式碼,而在 Ansible 我們也可用 loop 來簡化重複的任務 (Tasks)。以下凍仁將介紹常見的 loop 語法。
▲ 圖片來源:http://screencastsolutions.ca/looping-for-continuous-play/ 。
標準迴圈 (Standard Loops)
首先讓我們以簡單的重複印出 3 筆訊息為例。
Shell Script
先複習一下 Shell Script 的寫法。
建立 for loop 的 Script。
#!/bin/bash $ vi bash_loop.sh for X in 0 1 2; do echo Loop $X done
- 在第 3 行,我們用了
for
,並代入了 0, 1, 2 三個值到$X
變數。 - 在第 4 行時,則用了
echo
,印出訊息和$X
變數。
- 在第 3 行,我們用了
執行 Script:可以看到底下跑了 3 次的 loop。
$ ./bash_loop.sh Loop 0 Loop 1 Loop 2
Ansible Playbooks
我們需透過 item
和 with_items
來使用 Ansible 的 loop,其 item
為預設名,一般情況下不可修改。
在 Ansible 2.1 新增了 Loop Control 的語法,可透過
loop_control
和loop_var
來自訂 item 的名字,這在多重 loop 等較複雜的環境下會有很大的幫助。1
建立 loop 的 playbook。
$ vi playbook_loop.yml --- - name: a basic loop with playbook hosts: localhost tasks: - name: print loop message debug: msg: "Loop {{ item }}" with_items: - 0 - 1 - 2
註:
raw
和endraw
是為了相容 GitBook 所增加的語法,您可能會在某平台上看到它,請忽略之。- 在第 7, 8 行裡,我們用了
debug
module 來印出訊息,並定義item
。 - 在第 9 ~ 12 行裡,則用了
with_items
將 0, 1, 2 的值傳入item
。
- 在第 7, 8 行裡,我們用了
執行 Playbook:可以看到
print loop message
task 跑了 3 次的 loop。$ ansible-playbook playbook_loop.yml PLAY [a basic loop with playbook] ********************************************* TASK [setup] ******************************************************************* ok: [localhost] TASK [print loop message] ****************************************************** ok: [localhost] => (item=0) => { "item": 0, "msg": "Loop 0" } ok: [localhost] => (item=1) => { "item": 1, "msg": "Loop 1" } ok: [localhost] => (item=2) => { "item": 2, "msg": "Loop 2" } PLAY RECAP ********************************************************************* localhost : ok=2 changed=0 unreachable=0failed=0
凍仁常用此手法來安裝多個套件,接著以建立
chusiang/ansible-jupyter
Docker image 的 setup_jupyter.yml 為例。 2$ vi setup_jupyter_yml 01 --- 02 - hosts: localhost 03 04 vars: 05 # Same package on GNU/Linux. 06 same_packages: 07 - bash 08 - bash-completion 09 - ca-certificates 10 - curl 11 - git 12 - openssl 13 14 # Alpine Linux. 15 apk_packages: 16 - openssh-client 17 - vim 18 19 # Debian, Ubuntu. 20 apt_packages: "{{ apk_packages }}" 21 ... 22 23 tasks: 24 # General Linux. 25 - name: install same packages 26 package: name={{ item }} state=present 27 with_items: "{{ same_packages }}" 28 when: 29 - same_packages is defined 30 - ansible_pkg_mgr != "portage" 31 32 # Alpine Linux. 33 - name: install apk packages 34 apk: name={{ item }} state=present 35 with_items: "{{ apk_packages }}" 36 when: 37 - apk_packages is defined 38 - ansible_pkg_mgr == "apk" 39 40 # Debian, Ubuntu. 41 - name: install apt packages 42 apt: name={{ item }} state=present 43 with_items: "{{ apt_packages }}" 44 when: 45 - apt_packages is defined 46 - ansible_pkg_mgr == "apt" 47 ...
註:
raw
和endraw
是為了相容 GitBook 所增加的語法,您可能會在某平台上看到它,請忽略之。- 在第 6, 15, 20 行裡,分別宣告
same_packages
,apk_packages
和apt_packages
變數,並傳入了幾個套件名稱。 - 在第 26, 27, 34, 35, 42, 43 行裡,定義了
item
,並將same_packages
變數傳入。換句話說就是install same packages
task 會安裝same_packages
定義的所有套件。 - 由於此例中 apk, apt 的套件名稱皆相同,故在第 20 行用了
apt_packages: ""
的手法讓apt_packages = apk_packages
。
- 在第 6, 15, 20 行裡,分別宣告
進階迴圈 (Advanced Loops)
如有數個變數需求,可用 item.first
, item.second
類似屬性的方式定義 items。
建立擁有兩個 item 屬性的 loop 的 playbook。
$ vi playbook_loop_adv1.yml --- - name: a advanced loop with playbook hosts: localhost tasks: - name: print loop message debug: msg: "Loop {{ item.num }}: {{ item.str }}" with_items: - { num: '0', str: 'automate' } - { num: '1', str: 'with' } - { num: '2', str: 'ansible' }
註:
raw
和endraw
是為了相容 GitBook 所增加的語法,您可能會在某平台上看到它,請忽略之。執行 Playbook:這次除了跑 3 次 loop 以外,還代入
num
和str
屬性的 items。$ ansible-playbook playbook_loop_adv1.yml PLAY [a advanced loop with playbook] ******************************************* TASK [setup] ******************************************************************* ok: [localhost] TASK [print loop message] ****************************************************** ok: [localhost] => (item={u'num': u'0', u'str': u'automate'}) => { "item": { "num": "0", "str": "automate" }, "msg": "Loop 0: automate" } ok: [localhost] => (item={u'num': u'1', u'str': u'with'}) => { "item": { "num": "1", "str": "with" }, "msg": "Loop 1: with" } ok: [localhost] => (item={u'num': u'2', u'str': u'ansible'}) => { "item": { "num": "2", "str": "ansible" }, "msg": "Loop 2: ansible" } PLAY RECAP ********************************************************************* localhost : ok=2 changed=0 unreachable=0 failed=0
這部份在新增多個使用者、多個軟連結 (soft link) 時都會用到。
$ vi playbook_loop_adv2.yml --- - name: a advanced loop with playbook hosts: localhost tasks: - name: create multiple soft link file: src: "~/vcs/4.docs/automate-with-ansible/lab/ch18/{{ item.src }}" dest: "/tmp/{{ item.dest }}" state: link with_items: - { src: 'playbook_loop.yml', dest: 'loop0.yml' } - { src: 'playbook_loop_adv1.yml', dest: 'loop1.yml' } - { src: 'playbook_loop_adv2.yml', dest: 'loop2.yml' } # vim: ft=ansible :
註:
raw
和endraw
是為了相容 GitBook 所增加的語法,您可能會在某平台上看到它,請忽略之。- 第 8 行的
src
的絕對路徑會因環境而有變動,還請特別留意一下。
- 第 8 行的
後語
以個人經驗而言,掌握這兩個技巧就可以解決大多的迴圈需求!若想深入了解這部份,請研讀官方的 Loops | Ansible Documentation 文件 3。
相關連結
1. 更多 Loop Control 的介紹請參考 http://docs.ansible.com/ansible/playbooks_loops.html#loop-control。 ↩
2. 在友人的提醒下補了行號以利閱讀,若想複製該範例,請直接上 GitHub 取用。 ↩
3. 凍仁從 Ansible 1.9 開始踏入 Ansible 的世界,在 Ansible 2.0 之後新增的 loop 語法凍仁至今 (2016.12.18) 還未完全使用過。 ↩