ansible playbook loop 翻譯
ansible源文檔地址
有時候你想多次重複一個任務。 在電腦編程中,這叫做循環。 常見的 Ansible 循環包括使用文件模組更改幾個文件和 / 或目錄的所有權,使用用戶模組創建多個用戶,並重複一個輪詢步驟,直到達到某個結果。 為創建循環提供了兩個關鍵字: loop
和 with_<lookup>
。
注意
- 我們增加在Ansible2.5版本中中加了
loop
。它還沒有完全取代with_<lookup>
, 但我們推薦在大多數場景下使用它。- 我們並捨棄
with_<lookup>
用法–在可預見的未來,這種語法仍然有效。- 我們正在尋求改進
loop
語法,關注這個頁面和changelog 來獲取更新。
對比 loop
and with_*
with_
關鍵字依賴Lookup Plugins插件–儘管item
也是一個lookup插件。loop
關鍵字等價於with_list
,是使用簡單遍歷時的最佳選擇。loop
關鍵字不再接收一個字元串作為輸入,查看 Ensuring list input for loop: query vs. lookup- 通常來說,任何包含在 從with_X遷移到loop中的
with_*
用法都可以替換成loop
。 - 需要注意的是,在將
with_items
替換成loop
時,由於with_items
執行單層隱式扁平化遍歷,在使用loop
作為輸出時,你需要結合flatten(1)
一起使用。舉例說明,下面兩種方式的輸出結果相同:
with_items:
- 1
- [2,3]
- 4
你需要像這樣使用:
loop: "{{ [1, [2,3] ,4] | flatten(1) }}"
- 任何依賴
lookup
插件的with_*
語句不應該被轉換成loop
關鍵字。舉例說明,不建議使用下面的場景:
loop: "{{ lookup('fileglob', '*.txt', wantlist=True) }}"
保持這樣的格式會更簡潔。
with_fileglob: '*.txt'
標準loop
遍歷一個簡單的列表
重複的任務可以通過簡單的字元串列表寫成標準的loop
形式。你可以在任務里使用列表:
- name: add several users
user:
name: "{{ item }}"
state: present
groups: "wheel"
loop:
- testuser1
- testuser2
你可以使用一個變數文件或者在你的劇本中使用var
定義列表,然後在任務里通過列表名稱引用即可:
loop: "{{ somelist }}"
上面例子中的任何一個都相當於:
- name: add user testuser1
user:
name: "testuser1"
state: present
groups: "wheel"
- name: add user testuser2
user:
name: "testuser2"
state: present
groups: "wheel"
您可以直接將列表傳遞給某些插件作為參數。 大多數打包模組如 yum 包管理器和apt包管理器都具有這種功能。 如果可用,將列表傳遞給參數要比在任務上循環要好。 例如:
- name: optimal yum
yum:
name: "{{ list_of_packages }}"
state: present
- name: non-optimal yum, slower and may cause issues with interdependencies
yum:
name: "{{ item }}"
state: present
loop: "{{ list_of_packages }}"
查看 模組文檔 ,看看是否可以將列表傳遞給任何特定模組的參數。
遍歷哈希列表
如果你有一個哈希列表,你可以在循環中引用子鍵。例如:
- name: add several users
user:
name: "{{ item.name }}"
state: present
groups: "{{ item.groups }}"
loop:
- { name: 'testuser1', groups: 'wheel' }
- { name: 'testuser2', groups: 'root' }
當將條件句與循環結合時,When
語句將為每個項分別處理。 有關示例,請參見 When 語句。
遍歷字典
若要遍歷字典, 請使用 dict2items
字典過濾器:
- name: create a tag dictionary of non-empty tags
set_fact:
tags_dict: "{{ (tags_dict|default({}))|combine({item.key: item.value}) }}"
loop: "{{ tags|dict2items }}"
vars:
tags:
Environment: dev
Application: payment
Another: "{{ doesnotexist|default() }}"
when: item.value != ""
在這裡,我們不想設置空標記,因此我們創建了一個只包含非空標記的字典。
用循環註冊變數
你可以將loop
的輸出註冊為變數,例如:
- shell: "echo {{ item }}"
loop:
- "one"
- "two"
register: echo
當使用循環註冊時,放置在變數中的數據結構將包含一個 results 屬性,該屬性是來自模組的所有響應的列表。 這與直接註冊而不循環時返回的數據結構不同:
{
"changed": true,
"msg": "All items completed",
"results": [
{
"changed": true,
"cmd": "echo \"one\" ",
"delta": "0:00:00.003110",
"end": "2013-12-19 12:00:05.187153",
"invocation": {
"module_args": "echo \"one\"",
"module_name": "shell"
},
"item": "one",
"rc": 0,
"start": "2013-12-19 12:00:05.184043",
"stderr": "",
"stdout": "one"
},
{
"changed": true,
"cmd": "echo \"two\" ",
"delta": "0:00:00.002920",
"end": "2013-12-19 12:00:05.245502",
"invocation": {
"module_args": "echo \"two\"",
"module_name": "shell"
},
"item": "two",
"rc": 0,
"start": "2013-12-19 12:00:05.242582",
"stderr": "",
"stdout": "two"
}
]
}
對註冊變數進行後續循環以檢查結果如下所示:
- name: Fail if return code is not 0
fail:
msg: "The command ({{ item.cmd }}) did not have a 0 return code"
when: item.rc != 0
loop: "{{ echo.results }}"
在迭代過程中,當前項的結果將被放置在變數中:
- shell: echo "{{ item }}"
loop:
- one
- two
register: echo
changed_when: echo.stdout != "one"
複雜的loops
遍歷嵌套列表
你可以使用 Jinja2表達式來遍歷複雜的列表,例如,循環可以組合嵌套的列表:
- name: give users access to multiple databases
mysql_user:
name: "{{ item[0] }}"
priv: "{{ item[1] }}.*:ALL"
append_privs: yes
password: "foo"
loop: "{{ ['alice', 'bob'] | product(['clientdb', 'employeedb', 'providerdb'])|list }}"
嘗試一個任務知道滿足條件為止
在 1.4版本中引入
你可以使用 until 關鍵字重試一個任務,直到滿足某個條件為止:
- shell: /usr/bin/foo
register: result
until: result.stdout.find("all systems go") != -1
retries: 5
delay: 10
此任務最多運行5次,每次嘗試之間延遲10秒。 如果任何嘗試的結果在其標準輸出中具有「 all systems go」 ,則任務成功。 「 retries」的默認值為3,「 delay」為5。
若要查看單個重試的結果,請使用-vv
運行此劇本。
當您使用 until 運行任務並將結果註冊為變數時,註冊的變數將包含一個名為「 attempts」的鍵,該鍵記錄任務的重試次數。
注意
如果要重試任務,則必須設置 until 參數。 如果沒有定義 until,則強制重試參數的值為1。
主機清單的循環
為了遍歷你的主機清單,或者僅僅是主機清單的一個子集,你可以使用一個常規的循環來遍歷可以播放的批處理或者分組變數:
# show all the hosts in the inventory
- debug:
msg: "{{ item }}"
loop: "{{ groups['all'] }}"
# show all the hosts in the current play
- debug:
msg: "{{ item }}"
loop: "{{ ansible_play_batch }}"
還有一個特定的查找插件主機清單中主機名,可以這樣使用:
# show all the hosts in the inventory
- debug:
msg: "{{ item }}"
loop: "{{ query('inventory_hostnames', 'all') }}"
# show all the hosts matching the pattern, ie all but the group www
- debug:
msg: "{{ item }}"
loop: "{{ query('inventory_hostnames', 'all:!www') }}"
關於更多資訊可以在模式中找到目標主機和組。
使用query
或lookup
確保列表輸入
Loop 關鍵字需要一個列表作為輸入,但是 lookup 關鍵字默認返回一個逗號分隔值字元串。 2.5引入了一個新的 Jinja2函數,命名為調用查找插件,它總是返回一個列表,當使用 loop 關鍵字時,它提供了一個更簡單的介面和更可預測的查找插件輸出。
您可以使用 wantlist=true
強制查找返回一個要循環的列表,或者您可以改用 query。
這些例子做了同樣的事情:
loop: "{{ query('inventory_hostnames', 'all') }}"
loop: "{{ lookup('inventory_hostnames', 'all', wantlist=True) }}"
為loops添加控制
在 2.1版本中引入
loop_control 關鍵字可以讓您以有用的方式管理循環。
用label限制loop輸出
在 2.2版本中引入
當循環遍歷複雜的數據結構時,任務的控制台輸出可能是巨大的。 要限制顯示的輸出,使用循環控制的 label 指令:
- name: create servers
digital_ocean:
name: "{{ item.name }}"
state: present
loop:
- name: server1
disks: 3gb
ram: 15Gb
network:
nic01: 100Gb
nic02: 10Gb
...
loop_control:
label: "{{ item.name }}"
此任務的輸出將僅顯示每個項的 name 欄位,而不是多行{{ item }}
變數的全部內容。
注意
這是為了使控制台輸出更具可讀性,而不是保護敏感數據。 如果循環中有敏感數據,請在任務上設置
no_log: yes
以防止泄露。
loop中的暫停
在 2.2版本中引入
若要控制任務循環中每個項目執行之間的時間(以秒為單位) ,請使用帶循環控制的 pause 指令loop_control
:
# main.yml
- name: create servers, pause 3s before creating next
digital_ocean:
name: "{{ item }}"
state: present
loop:
- server1
- server2
loop_control:
pause: 3
通過index_var
跟蹤進度
在 2.5版本中引入
若要跟蹤您在循環中的位置,請使用 index_var
指令和 loop_control
。 這個指令指定一個包含當前循環索引的變數名:
- name: count our fruit
debug:
msg: "{{ item }} with index {{ my_idx }}"
loop:
- apple
- banana
- pear
loop_control:
index_var: my_idx
通過loop_var
定義內部和外部變數名
在 2.1版本中引入
可以使用 include 任務嵌套兩個循環任務。 但是,默認情況下,Ansible 為每個循環設置循環變數項。 這意味著內部的嵌套循環將覆蓋外部循環中 item 的值。 您可以使用 loop_var
loop_control
為每個循環指定變數的名稱:
# main.yml
- include_tasks: inner.yml
loop:
- 1
- 2
- 3
loop_control:
loop_var: outer_item
# inner.yml
- debug:
msg: "outer item={{ outer_item }} inner item={{ item }}"
loop:
- a
- b
- c
注意
如果 Ansible 檢測到當前循環正在使用一個已經定義的變數,它將引發一個錯誤以使任務失敗。
擴展loop變數
在 2.8 版本中引入
從 ansible 2.8開始,你可以使用擴展選項來獲得擴展循環資訊來進行循環控制。 此選項將公開以下資訊。
Variable | Description |
---|---|
ansible_loop.allitems |
循環中所有項的列表 |
ansible_loop.index |
循環的當前迭代的索引。(索引從1開始) |
ansible_loop.index0 |
循環的當前迭代的索引。(索引從0開始) |
ansible_loop.revindex |
倒序循環的當前迭代的索引。(索引到1結束) |
ansible_loop.revindex0 |
倒序循環的當前迭代的索引。(索引到0結束) |
ansible_loop.first |
如果第一次迭代則為True,否則是False |
ansible_loop.last |
如果最後一次迭代則為True,否則是False |
ansible_loop.length |
循環中的項數 |
ansible_loop.previtem |
循環上一次迭代中的項。在第一次迭代中這個變數未定義 |
ansible_loop.nextitem |
循環下一次迭代中的項。在最後一次迭代中這個變數未定義 |
loop_control:
extended: yes
訪問loop_var
在 2.8 版本中引入
從 ansible2.8你可以得到提供的值的名稱循環控制。循環變數使用安塞循環變數
對於角色作者,編寫允許循環的角色,而不是口述所需的循環變數值,您可以通過以下方式收集該值:
"{{ lookup('vars', ansible_loop_var) }}"
從 with_X 遷移到 loop
隨著 Ansible 2.5的發布,推薦的循環執行方式是使用新的 loop 關鍵字,而不是使用 with_x
樣式的循環。
在許多情況下,循環語法更好地使用過濾器,而不是更複雜地使用query
或者 lookup
。
下面的示例將展示如何將許多常見的樣式循環轉換為循環和過濾器。
with_list
with_list
用 loop
替換。
- name: with_list
debug:
msg: "{{ item }}"
with_list:
- one
- two
- name: with_list -> loop
debug:
msg: "{{ item }}"
loop:
- one
- two
with_items
with_items
用 loop
和 flatten
過濾器替換。
- name: with_items
debug:
msg: "{{ item }}"
with_items: "{{ items }}"
- name: with_items -> loop
debug:
msg: "{{ item }}"
loop: "{{ items|flatten(levels=1) }}"
with_indexed_items
with_indexed_items
用 loop
, flatten
過濾器 和 loop_control.index_var
替換。
- name: with_indexed_items
debug:
msg: "{{ item.0 }} - {{ item.1 }}"
with_indexed_items: "{{ items }}"
- name: with_indexed_items -> loop
debug:
msg: "{{ index }} - {{ item }}"
loop: "{{ items|flatten(levels=1) }}"
loop_control:
index_var: index
with_flattened
with_flattened
用 loop
和 flatten
過濾器替換。
- name: with_flattened
debug:
msg: "{{ item }}"
with_flattened: "{{ items }}"
- name: with_flattened -> loop
debug:
msg: "{{ item }}"
loop: "{{ items|flatten }}"
with_together
with_together
用 loop
和 zip
過濾器替換。
- name: with_together
debug:
msg: "{{ item.0 }} - {{ item.1 }}"
with_together:
- "{{ list_one }}"
- "{{ list_two }}"
- name: with_together -> loop
debug:
msg: "{{ item.0 }} - {{ item.1 }}"
loop: "{{ list_one|zip(list_two)|list }}"
with_dict
with_dict
用 loop
和 dictsort
或者 dict2items
過濾器替換。
- name: with_dict
debug:
msg: "{{ item.key }} - {{ item.value }}"
with_dict: "{{ dictionary }}"
- name: with_dict -> loop (option 1)
debug:
msg: "{{ item.key }} - {{ item.value }}"
loop: "{{ dictionary|dict2items }}"
- name: with_dict -> loop (option 2)
debug:
msg: "{{ item.0 }} - {{ item.1 }}"
loop: "{{ dictionary|dictsort }}"
with_sequence
with_sequence
用 loop
和 range
函數, format
過濾器替換。
- name: with_sequence
debug:
msg: "{{ item }}"
with_sequence: start=0 end=4 stride=2 format=testuser%02x
- name: with_sequence -> loop
debug:
msg: "{{ 'testuser%02x' | format(item) }}"
# range is exclusive of the end point
loop: "{{ range(0, 4 + 1, 2)|list }}"
with_subelements
用循環和子元素過濾器代替子元素過濾器。
- name: with_subelements
debug:
msg: "{{ item.0.name }} - {{ item.1 }}"
with_subelements:
- "{{ users }}"
- mysql.hosts
- name: with_subelements -> loop
debug:
msg: "{{ item.0.name }} - {{ item.1 }}"
loop: "{{ users|subelements('mysql.hosts') }}"
with_nested/with_cartesian
with_nested
和 with_cartesian
用 loop
和product
過濾器替換。
- name: with_nested
debug:
msg: "{{ item.0 }} - {{ item.1 }}"
with_nested:
- "{{ list_one }}"
- "{{ list_two }}"
- name: with_nested -> loop
debug:
msg: "{{ item.0 }} - {{ item.1 }}"
loop: "{{ list_one|product(list_two)|list }}"
with_random_choice
with_random_choice
用 random
過濾器替換,不在需要loop
。
- name: with_random_choice
debug:
msg: "{{ item }}"
with_random_choice: "{{ my_list }}"
- name: with_random_choice -> loop (No loop is needed here)
debug:
msg: "{{ my_list|random }}"
tags: random