Spec-Zone.ru › Ansible

Циклы

Ansible предлагает ключевые слова loop, with_<lookup>, и until для многократного выполнения задачи. Примеры часто используемых циклов включают изменение владельца нескольких файлов и/или каталогов с помощью модуля file, создание нескольких пользователей с помощью модуля user и повторение шага опроса до достижения определенного результата.

Примечание

  • Мы добавили loop в Ansible 2.5 в качестве более простого способа выполнения циклов, но рекомендуем его для большинства случаев использования.
  • Мы не устарели with_<lookup> — этот синтаксис останется допустимым в обозримом будущем.
  • loop и with_<lookup> взаимоисключающие. Хотя их можно вложенно использовать в until, это повлияет на каждую итерацию цикла.
  • Сравнение циклов
  • Использование циклов

    • Итерация по простому списку
    • Итерация по списку словарей
    • Итерация по словарю
    • Регистрация переменных с помощью цикла
    • Повторное выполнение задачи до достижения условия
    • Итерация по инвентарю
  • Обеспечение входных данных в виде списка для цикла: использование query вместо lookup
  • Добавление управления циклами

    • Ограничение вывода цикла с помощью метки label
    • Приостановка внутри цикла
    • Отслеживание прогресса в цикле с помощью переменной index_var
    • Расширенные переменные цикла
    • Доступ к имени вашей переменной цикла
  • Вложенные циклы

    • Итерация по вложенным спискам
    • Укладка циклов с помощью include_tasks
    • Until и loop
  • Миграция с with_X на loop

    • with_list
    • with_items
    • with_indexed_items
    • with_flattened
    • with_together
    • with_dict
    • with_sequence
    • with_subelements
    • with_nested/with_cartesian
    • with_random_choice

Сравнение циклов

  • Обычный случай использования 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 для управления циклом. Эта опция предоставит следующую информацию.

Переменная

Описание

ansible_loop.allitems

Список всех элементов в цикле

ansible_loop.index

Текущая итерация цикла (индексирование с 1).

ansible_loop.index0

Текущая итерация цикла (индексирование с 0).

ansible_loop.revindex

Количество итераций с конца цикла (индексирование с 1)

ansible_loop.revindex0

Количество итераций с конца цикла (индексирование с 0)

ansible_loop.first

True если первая итерация

ansible_loop.last

True если последняя итерация

ansible_loop.length

Количество элементов в цикле

ansible_loop.previtem

Элемент из предыдущей итерации цикла. Не определено во время первой итерации.

ansible_loop.nextitem

Элемент из следующей итерации цикла. Не определено во время последней итерации.

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

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API