Циклы
Ansible предлагает ключевые слова loop, with_<lookup>, и until для многократного выполнения задачи. Примеры часто используемых циклов включают изменение владельца нескольких файлов и/или каталогов с помощью модуля file, создание нескольких пользователей с помощью модуля user и повторение шага опроса до достижения определенного результата.
Примечание
- Мы добавили
loopв Ansible 2.5 в качестве более простого способа выполнения циклов, но рекомендуем его для большинства случаев использования. - Мы не устарели
with_<lookup>— этот синтаксис останется допустимым в обозримом будущем. -
loopиwith_<lookup>взаимоисключающие. Хотя их можно вложенно использовать вuntil, это повлияет на каждую итерацию цикла.
- Сравнение циклов
- Обеспечение входных данных в виде списка для цикла: использование
queryвместоlookup
Сравнение циклов
- Обычный случай использования
untilсвязан с задачами, которые могут завершиться ошибкой, в то время какloopиwith_<lookup>предназначены для многократного выполнения задач с небольшими изменениями. loopиwith_<lookup>будут запускать задачу один раз на каждый элемент в списке, используемом в качестве входных данных, в то время какuntilбудет повторно запускать задачу до тех пор, пока не будет выполнено условие. Для программистов первые — это «циклы for», а последний — «цикл while/until».- Ключевые слова
with_<lookup>полагаются на Плагины Lookup — дажеitemsявляется плагином Lookup. - Ключевое слово
loopэквивалентноwith_list, и это лучший выбор для простых циклов. - Ключевое слово
loopне принимает строку в качестве входных данных, см. Обеспечение входных данных в виде списка для цикла: использование query вместо lookup. - Ключевое слово
untilпринимает «условие окончания» (выражение, возвращающееTrueилиFalse) которое «неявным образом шаблонизируется» (нет необходимости в{{ }}), обычно основанное на переменной, которую выregisterдля задачи. -
loop_controlвлияет наloopиwith_<lookup>, но не наuntil, у которого есть свои сопутствующие ключевые слова:retriesиdelay. - В общем случае любое использование
with_*, описанное в Миграция с with_X на loop, можно обновить для использованияloop - Будьте осторожны при изменении
with_itemsнаloop, так какwith_itemsвыполняет неявную одноуровневую уплощение. Вам может потребоваться использовать| flatten(1)сloopдля соответствия точному результату. Например, чтобы получить тот же результат, что и:
with_items: - 1 - [2,3] - 4
вам потребуется
loop: "{{ [1, [2, 3], 4] | flatten(1) }}"
- Любое утверждение
with_*, требующее использованиеlookupвнутри цикла, не должно быть преобразовано для использования ключевого словаloop. Например, вместо того чтобы сделать:
loop: "{{ lookup('fileglob', '*.txt', wantlist=True) }}"
более понятно оставить
with_fileglob: '*.txt'
Использование циклов
Итерация по простому списку
Повторяющиеся задачи могут быть записаны как стандартные циклы по простому списку строк. Вы можете определить список непосредственно в задаче.
- name: Add several users
ansible.builtin.user:
name: "{{ item }}"
state: present
groups: "wheel"
loop:
- testuser1
- testuser2
Вы можете определить список в файле переменных или в разделе «vars» вашего исполнения, а затем сослаться на имя списка в задаче.
loop: "{{ somelist }}"
Любой из этих примеров эквивалентен
- name: Add user testuser1
ansible.builtin.user:
name: "testuser1"
state: present
groups: "wheel"
- name: Add user testuser2
ansible.builtin.user:
name: "testuser2"
state: present
groups: "wheel"
Вы можете передать список напрямую в параметр для некоторых плагинов. Большинство модулей пакетирования, таких как yum и apt, обладают этой возможностью. Если это доступно, передача списка в параметр лучше, чем итерация по задаче. Например:
- name: Optimal yum
ansible.builtin.yum:
name: "{{ list_of_packages }}"
state: present
- name: Non-optimal yum, slower and may cause issues with interdependencies
ansible.builtin.yum:
name: "{{ item }}"
state: present
loop: "{{ list_of_packages }}"
Проверьте документацию модуля, чтобы узнать, можно ли передать список в какой-либо параметр модуля.
Итерация по списку словарей
Если у вас есть список словарей, вы можете ссылаться на подключаемые ключи в цикле. Например:
- name: Add several users
ansible.builtin.user:
name: "{{ item.name }}"
state: present
groups: "{{ item.groups }}"
loop:
- { name: 'testuser1', groups: 'wheel' }
- { name: 'testuser2', groups: 'root' }
При объединении условий с циклом инструкция when: обрабатывается отдельно для каждого элемента. См. Базовые условные выражения с when для примеров.
Итерация по словарю
Для итерации по словарю используйте dict2items:
- name: Using dict2items
ansible.builtin.debug:
msg: "{{ item.key }} - {{ item.value }}"
loop: "{{ tag_data | dict2items }}"
vars:
tag_data:
Environment: dev
Application: payment
Здесь мы итерируемся по tag_data и выводим ключ и значение из него.
Регистрация переменных с помощью цикла
Вы можете зарегистрировать результат цикла как переменную. Например
- name: Register loop output as a variable
ansible.builtin.shell: "echo {{ item }}"
loop:
- "one"
- "two"
register: echo
Когда вы используете register с циклом, структура данных, помещенная в переменную, будет содержать атрибут results, который представляет собой список всех ответов от модуля. Это отличается от структуры данных, возвращаемой при использовании register без цикла. Атрибут changed/failed/skipped, который находится рядом с results, будет представлять общее состояние. changed/failed будет true, если хотя бы одна из итераций вызвала изменение/сбой, в то время как skipped будет true, только если все итерации были пропущены.
{
"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
ansible.builtin.fail:
msg: "The command ({{ item.cmd }}) did not have a 0 return code"
when: item.rc != 0
loop: "{{ echo.results }}"
Во время итерации результат текущего элемента будет помещен в переменную.
- name: Place the result of the current item in the variable
ansible.builtin.shell: echo "{{ item }}"
loop:
- one
- two
register: echo
changed_when: echo.stdout != "one"
Повторная попытка задачи до достижения условия
Новое в версии 1.4.
Вы можете использовать ключевое слово until для повторной попытки задачи до тех пор, пока не будет выполнено определенное условие. Вот пример:
- name: Retry a task until a certain condition is met
ansible.builtin.shell: /usr/bin/foo
register: result
until: result.stdout.find("all systems go") != -1
retries: 5
delay: 10
Эта задача выполняется до 5 раз с задержкой в 10 секунд между каждой попыткой. Если результат любой попытки содержит «все системы работают» в своем stdout, задача завершается успешно. Значение по умолчанию для «retries» — 3, а для «delay» — 5.
Чтобы увидеть результаты отдельных повторов, запустите выполнение с -vv.
Когда вы запускаете задачу с until и регистрируете результат как переменную, зарегистрированная переменная будет содержать ключ «attempts», который записывает количество повторов для задачи.
Если until не указан, задача будет повторяться до тех пор, пока задача не завершится успешно, но не более retries раз (Новое в версии 2.16).
Вы можете объединить ключевое слово until с loop или with_<lookup>. Результат задачи для каждого элемента цикла регистрируется в переменной и может использоваться в условии until . Вот пример:
- name: Retry combined with a loop
uri:
url: "https://{{ item }}.ansible.com"
method: GET
register: uri_output
with_items:
- "galaxy"
- "docs"
- "forum"
- "www"
retries: 2
delay: 1
until: "uri_output.status == 200"
Примечание
Когда вы используете ключевое слово timeout в цикле, оно применяется к каждой попытке выполнения действия задачи. Подробнее см. TASK_TIMEOUT.
Итерация по инвентаризации
Обычно само исполнение представляет собой цикл по вашей инвентаризации, но иногда вам нужно, чтобы задача делала то же самое с другим набором хостов. Чтобы выполнить цикл по своей инвентаризации или только по подмножеству, вы можете использовать обычный loop с переменными ansible_play_batch или groups.
- name: Show all the hosts in the inventory
ansible.builtin.debug:
msg: "{{ item }}"
loop: "{{ groups['all'] }}"
- name: Show all the hosts in the current play
ansible.builtin.debug:
msg: "{{ item }}"
loop: "{{ ansible_play_batch }}"
Также существует специальный плагин поиска inventory_hostnames, который можно использовать так:
- name: Show all the hosts in the inventory
ansible.builtin.debug:
msg: "{{ item }}"
loop: "{{ query('inventory_hostnames', 'all') }}"
- name: Show all the hosts matching the pattern, ie all but the group www
ansible.builtin.debug:
msg: "{{ item }}"
loop: "{{ query('inventory_hostnames', 'all:!www') }}"
Дополнительную информацию о шаблонах можно найти в Шаблоны: нацеливание на хосты и группы.
Обеспечение входных данных в виде списка для loop: использование query вместо lookup
Ключевое слово loop требует списка в качестве входных данных, но ключевое слово lookup по умолчанию возвращает строку с запятыми, разделенными значениями. В Ansible 2.5 был представлен новый Jinja2-функция под названием query, которая всегда возвращает список, предлагая более простой интерфейс и более предсказуемый вывод от плагинов поиска при использовании ключевого слова loop.
Вы можете принудительно заставить lookup вернуть список для loop с помощью wantlist=True, или вы можете использовать query вместо него.
Следующие два примера делают одно и то же.
loop: "{{ query('inventory_hostnames', 'all') }}"
loop: "{{ lookup('inventory_hostnames', 'all', wantlist=True) }}"
Добавление управления циклами
Новое в версии 2.1.
Ключевое слово loop_control позволяет управлять циклами полезными способами.
Ограничение вывода цикла с помощью label
Новое в версии 2.2.
При итерации по сложным структурам данных вывод задачи в консоли может быть огромным. Для ограничения отображаемого вывода используйте директиву label с loop_control.
- 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 вместо всего содержимого многострочной переменной {{ item }}.
Примечание
Это для улучшения читабельности вывода в консоли, а не для защиты конфиденциальных данных. Если в loop есть конфиденциальные данные, установите no_log: true для задачи, чтобы предотвратить раскрытие.
Пауза внутри цикла
Новое в версии 2.2.
Для управления временем (в секундах) между выполнением каждого элемента в цикле задачи используйте директиву pause с loop_control.
# main.yml
- name: Create servers, pause 3s before creating next
community.digitalocean.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
ansible.builtin.debug:
msg: "{{ item }} with index {{ my_idx }}"
loop:
- apple
- banana
- pear
loop_control:
index_var: my_idx
Примечание
index_var имеет нумерацию с 0.
Расширенные переменные цикла
Новое в версии 2.8.
Начиная с Ansible 2.8, вы можете получить расширенную информацию о цикле, используя опцию extended для управления циклом. Эта опция предоставит следующую информацию.
Переменная | Описание |
| Список всех элементов в цикле |
| Текущая итерация цикла (индексирование с 1). |
| Текущая итерация цикла (индексирование с 0). |
| Количество итераций с конца цикла (индексирование с 1) |
| Количество итераций с конца цикла (индексирование с 0) |
|
|
|
|
| Количество элементов в цикле |
| Элемент из предыдущей итерации цикла. Не определено во время первой итерации. |
| Элемент из следующей итерации цикла. Не определено во время последней итерации. |
loop_control: extended: true
Примечание
При использовании loop_control.extended на узле управления будет использоваться больше памяти. Это результат того, что ansible_loop.allitems содержит ссылку на все данные цикла для каждой итерации. При сериализации результатов для отображения в плагинах обратной связи в основном процессе Ansible эти ссылки могут быть удалены, что приводит к увеличению использования памяти.
Новое в версии 2.14.
Для отключения элемента ansible_loop.allitems с целью уменьшения потребления памяти установите loop_control.extended_allitems: false.
loop_control: extended: true extended_allitems: false
Получение имени переменной цикла
Новое в версии 2.8.
Начиная с Ansible 2.8, вы можете получить имя значения, предоставленного loop_control.loop_var с помощью переменной ansible_loop_var
Для авторов ролей, создающих роли, допускающие циклы, вместо указания требуемого значения loop_var, вы можете получить значение следующим образом
"{{ lookup('vars', ansible_loop_var) }}"
Вложенные циклы
Хотя в этих примерах используется loop, то же самое относится и к with_<lookup>.
Итерация по вложенным спискам
Самый простой способ «вложить» циклы — избегать вложенности циклов, а структурировать данные для достижения того же результата. Вы можете использовать выражения Jinja2 для итерации по сложным спискам. Например, цикл может объединять вложенные списки, что имитирует вложенный цикл.
- name: Give users access to multiple databases
community.mysql.mysql_user:
name: "{{ item[0] }}"
priv: "{{ item[1] }}.*:ALL"
append_privs: true
password: "foo"
loop: "{{ ['alice', 'bob'] | product(['clientdb', 'employeedb', 'providerdb']) | list }}"
Укладка циклов с помощью include_tasks
Новое в версии 2.1.
Вы можете вложить два циклических задания с помощью include_tasks . Однако по умолчанию Ansible задаёт переменную цикла item для каждого цикла. Это означает, что внутренний, вложенный цикл перепишет значение item из внешнего цикла. Чтобы избежать этого, вы можете указать имя переменной для каждого цикла, используя loop_var с loop_control.
# main.yml
- include_tasks: inner.yml
loop:
- 1
- 2
- 3
loop_control:
loop_var: outer_item
# inner.yml
- name: Print outer and inner items
ansible.builtin.debug:
msg: "outer item={{ outer_item }} inner item={{ item }}"
loop:
- a
- b
- c
Примечание
Если Ansible обнаружит, что текущий цикл использует переменную, которая уже определена, он выведет ошибку, чтобы выполнить задачу.
Until и цикл
Условие until будет применяться к каждой item в loop:
- debug: msg={{item}}
loop:
- 1
- 2
- 3
retries: 2
until: item > 2
Это заставит Ansible повторно выполнить первые 2 элемента дважды, затем отклонить элемент на 3-й попытке, затем успешно выполнить элемент на 1-й попытке 3-й итерации, в итоге отклоняя задачу в целом.
[started TASK: debug on localhost]
FAILED - RETRYING: [localhost]: debug (2 retries left).Result was: {
"attempts": 1,
"changed": false,
"msg": 1,
"retries": 3
}
FAILED - RETRYING: [localhost]: debug (1 retries left).Result was: {
"attempts": 2,
"changed": false,
"msg": 1,
"retries": 3
}
failed: [localhost] (item=1) => {
"msg": 1
}
FAILED - RETRYING: [localhost]: debug (2 retries left).Result was: {
"attempts": 1,
"changed": false,
"msg": 2,
"retries": 3
}
FAILED - RETRYING: [localhost]: debug (1 retries left).Result was: {
"attempts": 2,
"changed": false,
"msg": 2,
"retries": 3
}
failed: [localhost] (item=2) => {
"msg": 2
}
ok: [localhost] => (item=3) => {
"msg": 3
}
fatal: [localhost]: FAILED! => {"msg": "One or more items failed"}
Миграция от with_X к loop
В большинстве случаев циклы работают лучше с ключевым словом loop вместо циклов стиля with_X . Синтаксис loop обычно лучше выражается с помощью фильтров, а не более сложного использования query или lookup.
Эти примеры показывают, как преобразовать многие распространённые циклы стиля with_ в циклы loop и фильтры.
with_list
with_list непосредственно заменяется на loop.
- name: with_list
ansible.builtin.debug:
msg: "{{ item }}"
with_list:
- one
- two
- name: with_list -> loop
ansible.builtin.debug:
msg: "{{ item }}"
loop:
- one
- two
with_items
with_items заменяется на loop и фильтр flatten.
- name: with_items
ansible.builtin.debug:
msg: "{{ item }}"
with_items: "{{ items }}"
- name: with_items -> loop
ansible.builtin.debug:
msg: "{{ item }}"
loop: "{{ items|flatten(levels=1) }}"
with_indexed_items
with_indexed_items заменяется на loop, фильтр flatten и loop_control.index_var.
- name: with_indexed_items
ansible.builtin.debug:
msg: "{{ item.0 }} - {{ item.1 }}"
with_indexed_items: "{{ items }}"
- name: with_indexed_items -> loop
ansible.builtin.debug:
msg: "{{ index }} - {{ item }}"
loop: "{{ items|flatten(levels=1) }}"
loop_control:
index_var: index
with_flattened
with_flattened заменяется на loop и фильтр flatten.
- name: with_flattened
ansible.builtin.debug:
msg: "{{ item }}"
with_flattened: "{{ items }}"
- name: with_flattened -> loop
ansible.builtin.debug:
msg: "{{ item }}"
loop: "{{ items|flatten }}"
with_together
with_together заменяется на loop и фильтр zip.
- name: with_together
ansible.builtin.debug:
msg: "{{ item.0 }} - {{ item.1 }}"
with_together:
- "{{ list_one }}"
- "{{ list_two }}"
- name: with_together -> loop
ansible.builtin.debug:
msg: "{{ item.0 }} - {{ item.1 }}"
loop: "{{ list_one|zip(list_two)|list }}"
Еще один пример со сложными данными
- name: with_together -> loop
ansible.builtin.debug:
msg: "{{ item.0 }} - {{ item.1 }} - {{ item.2 }}"
loop: "{{ data[0]|zip(*data[1:])|list }}"
vars:
data:
- ['a', 'b', 'c']
- ['d', 'e', 'f']
- ['g', 'h', 'i']
with_dict
with_dict может быть заменено на loop и либо фильтр dictsort, либо dict2items.
- name: with_dict
ansible.builtin.debug:
msg: "{{ item.key }} - {{ item.value }}"
with_dict: "{{ dictionary }}"
- name: with_dict -> loop (option 1)
ansible.builtin.debug:
msg: "{{ item.key }} - {{ item.value }}"
loop: "{{ dictionary|dict2items }}"
- name: with_dict -> loop (option 2)
ansible.builtin.debug:
msg: "{{ item.0 }} - {{ item.1 }}"
loop: "{{ dictionary|dictsort }}"
with_sequence
with_sequence заменяется на loop и функцию range, и, возможно, фильтр format.
- name: with_sequence
ansible.builtin.debug:
msg: "{{ item }}"
with_sequence: start=0 end=4 stride=2 format=testuser%02x
- name: with_sequence -> loop
ansible.builtin.debug:
msg: "{{ 'testuser%02x' | format(item) }}"
loop: "{{ range(0, 4 + 1, 2)|list }}"
Диапазон цикла не включает конечную точку.
with_subelements
with_subelements заменяется на loop и фильтр subelements.
- name: with_subelements
ansible.builtin.debug:
msg: "{{ item.0.name }} - {{ item.1 }}"
with_subelements:
- "{{ users }}"
- mysql.hosts
- name: with_subelements -> loop
ansible.builtin.debug:
msg: "{{ item.0.name }} - {{ item.1 }}"
loop: "{{ users|subelements('mysql.hosts') }}"
with_nested/with_cartesian
with_nested и with_cartesian заменяются на цикл и фильтр product.
- name: with_nested
ansible.builtin.debug:
msg: "{{ item.0 }} - {{ item.1 }}"
with_nested:
- "{{ list_one }}"
- "{{ list_two }}"
- name: with_nested -> loop
ansible.builtin.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
ansible.builtin.debug:
msg: "{{ item }}"
with_random_choice: "{{ my_list }}"
- name: with_random_choice -> loop (No loop is needed here)
ansible.builtin.debug:
msg: "{{ my_list|random }}"
tags: random
См. также
- Сценарии Ansible
-
Введение в сценарии
- Роли
-
Организация сценариев с помощью ролей
- Общие советы
-
Полезные советы и рекомендации для сценариев
- Условные операторы
-
Условные операторы в сценариях
- Использование переменных
-
Всё о переменных
- Связь
-
Есть вопросы? Нужна помощь? Хотите поделиться идеями? Посетите руководство Ansible по взаимодействию
© 2012–2018 Michael DeHaan
© 2018–2024 Red Hat, Inc.
Licensed under the GNU General Public License version 3.
https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_loops.html