ansible playbook loop 翻譯

ansible源文檔地址
有時候你想多次重複一個任務。 在電腦編程中,這叫做循環。 常見的 Ansible 循環包括使用文件模組更改幾個文件和 / 或目錄的所有權,使用用戶模組創建多個用戶,並重複一個輪詢步驟,直到達到某個結果。 為創建循環提供了兩個關鍵字: loopwith_<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') }}"

關於更多資訊可以在模式中找到目標主機和組

使用querylookup確保列表輸入

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_listloop替換。

- name: with_list
  debug:
    msg: "{{ item }}"
  with_list:
    - one
    - two

- name: with_list -> loop
  debug:
    msg: "{{ item }}"
  loop:
    - one
    - two

with_items

with_itemsloopflatten 過濾器替換。

- 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_itemsloop, 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_flattenedloopflatten 過濾器替換。

- name: with_flattened
  debug:
    msg: "{{ item }}"
  with_flattened: "{{ items }}"

- name: with_flattened -> loop
  debug:
    msg: "{{ item }}"
  loop: "{{ items|flatten }}"

with_together

with_togetherloopzip過濾器替換。

- 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_dictloopdictsort 或者 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_sequencelooprange 函數, 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_nestedwith_cartesianloopproduct 過濾器替換。

- 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_choicerandom 過濾器替換,不在需要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