Циклы
Часто вам потребуется выполнить много действий в одной задаче, например, создать много пользователей, установить много пакетов или повторить шаг опроса до достижения определенного результата.
В этой главе рассказывается о том, как использовать циклы в плейбуках.
- Стандартные циклы
- Сложные циклы
- Использование lookup и query в цикле
- Циклы «пока не»
- Использование register с циклом
- Итерация по инвентаризации
- Управление циклом
- Циклы и включения в версии 2.0
Стандартные циклы
Чтобы сократить написание кода, повторяющиеся задачи можно записать в сокращенном виде:
- name: add several users
user:
name: "{{ item }}"
state: present
groups: "wheel"
loop:
- testuser1
- testuser2
Если вы определили список YAML в файле переменных или в разделе «vars», вы также можете сделать следующее:
loop: "{{ somelist }}"
Вышеприведенное эквивалентно следующему:
- name: add user testuser1
user:
name: "testuser1"
state: present
groups: "wheel"
- name: add user testuser2
user:
name: "testuser2"
state: present
groups: "wheel"
Примечание
До версии 2.5 Ansible в основном использовал ключевые слова with_<lookup> для создания циклов, ключевое слово loop по существу аналогично with_list.
Некоторые плагины, такие как модули yum и apt, могут принимать списки напрямую в свои параметры. Это более эффективно, чем цикл по задаче. Смотрите документацию каждого действия для получения подробностей, а сейчас вот пример:
- name: optimal yum
yum:
name: "{{list_of_packages}}"
state: present
- name: non optimal yum, not only slower but might 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 для примера.
Сложные циклы
Иногда вам нужно больше, чем просто список. Вы можете использовать выражения Jinja2 для создания сложных списков: например, используя функцию поиска «nested», вы можете объединить списки:
- name: give users access to multiple databases
mysql_user:
name: "{{ item[0] }}"
priv: "{{ item[1] }}.*:ALL"
append_privs: yes
password: "foo"
loop: "{{ query('nested', [ 'alice', 'bob' ], [ 'clientdb', 'employeedb', 'providerdb' ]) }}"
Примечание
Циклы with_ на самом деле представляют собой комбинацию with_ + lookup(), даже items является функцией поиска. loop может использоваться таким же образом, как показано выше.
Использование lookup и query в цикле
В Ansible 2.5 была добавлена новая функция Jinja2 под названием query, которая предлагает несколько преимуществ по сравнению с lookup при использовании нового ключевого слова loop.
Это более подробно описано в документации по функциям поиска, однако query предоставляет более простой интерфейс и более предсказуемый результат от плагинов поиска, обеспечивая лучшую совместимость с loop.
В определенных ситуациях функция lookup может не возвращать список, который loop требует.
Следующие вызовы эквивалентны, используя wantlist=True с lookup для обеспечения возвращаемого типа списка:
loop: "{{ query('nested', ['alice', 'bob'], ['clientdb', 'employeedb', 'providerdb']) }}"
loop: "{{ lookup('nested', ['alice', 'bob'], ['clientdb', 'employeedb', 'providerdb'], wantlist=True) }}"
Циклы «пока не»
Новое в версии 1.4.
Иногда вам нужно повторить задачу до тех пор, пока не будет выполнено определенное условие. Вот пример:
- shell: /usr/bin/foo
register: result
until: result.stdout.find("all systems go") != -1
retries: 5
delay: 10
В приведенном выше примере модуль shell запускается рекурсивно до тех пор, пока результат модуля не содержит «all systems go» в своем стандартном выводе или задача не будет повторно выполнена 5 раз с задержкой в 10 секунд. Значение по умолчанию для «retries» равно 3, а для «delay» — 5.
Задача возвращает результаты, возвращенные последней выполненной задачей. Результаты отдельных повторных попыток можно просмотреть с помощью параметра -vv. Регистрируемая переменная также будет иметь новый ключ «attempts», который будет содержать количество повторных попыток для задачи.
Примечание
Если параметр until не определен, значение параметра retries принудительно устанавливается в 1.
Использование register с циклом
После использования register с циклом структура данных, помещенная в переменную, будет содержать атрибут results, который представляет собой список всех ответов модуля.
Вот пример использования register с loop:
- shell: "echo {{ item }}"
loop:
- "one"
- "two"
register: echo
Это отличается от структуры данных, возвращаемой при использовании register без цикла:
{
"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"
Итерация по инвентаризации
Если вы хотите пройти по инвентаризации или только по ее подмножеству, существует несколько способов. Можно использовать обычный loop с переменными ansible_play_batch или groups:
# 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 }}"
Также существует специальный плагин поиска inventory_hostnames, который можно использовать следующим образом:
# 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') }}"
Дополнительную информацию о шаблонах можно найти в работе с шаблонами
Управление циклом
Новое в версии 2.1.
В версии 2.0 вы снова можете использовать циклы и включения задач (но не плейбуков). Это добавляет возможность проходить по набору задач одним разом. Ansible по умолчанию устанавливает переменную цикла item для каждого цикла, что приводит к перезаписи значения item из «внешних» циклов в этих вложенных циклах. Начиная с Ansible 2.1, можно использовать параметр loop_control для указания имени переменной, используемой для цикла:
# main.yml
- include: inner.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 обнаружит, что текущий цикл использует переменную, которая уже определена, он вызовет ошибку, чтобы прервать выполнение задачи.
Новое в версии 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 }}"
Теперь будет отображаться только поле label вместо всей структуры для каждой item, по умолчанию используется {{ item }} для отображения по умолчанию.
Новое в версии 2.2.
Еще один вариант управления циклом — pause, который позволяет управлять временем (в секундах) между выполнением элементов в цикле задач:
# main.yml
- name: create servers, pause 3s before creating next
digital_ocean:
name: "{{ item }}"
state: present
loop:
- server1
- server2
loop_control:
pause: 3
Новое в версии 2.5.
Если вам нужно отслеживать текущее положение в цикле, вы можете использовать параметр index_var для управления циклом, чтобы указать имя переменной, которая будет содержать текущий индекс цикла:
- name: count our fruit
debug:
msg: "{{ item }} with index {{ my_idx }}"
loop:
- apple
- banana
- pear
loop_control:
index_var: my_idx
Циклы и включения в версии 2.0
Поскольку loop_control недоступен в Ansible 2.0, при использовании include с циклом следует использовать set_fact для сохранения значения «внешнего» цикла для item:
# main.yml
- include_tasks: inner.yml
loop:
- 1
- 2
- 3
# inner.yml
- set_fact:
outer_item: "{{ item }}"
- debug:
msg: "outer item={{ outer_item }} inner item={{ item }}"
loop:
- a
- b
- c
Примечание
include устарело, следует использовать include_tasks, import_tasks, import_play вместо него.
См. также
- Работа с плейбуками
- Введение в плейбуки
- Роли
- Организация плейбуков по ролям
- Рекомендации по практике
- Рекомендации по практике в плейбуках
- Условные выражения
- Условные операторы в плейбуках
- Переменные
- Все о переменных
- Список рассылки пользователей
- У вас есть вопрос? Заходите на группу Google!
- irc.freenode.net
- IRC-чат-канал #ansible
© 2012–2018 Michael DeHaan
© 2018–2019 Red Hat, Inc.
Licensed under the GNU General Public License version 3.
https://docs.ansible.com/ansible/2.5/user_guide/playbooks_loops.html