Spec-Zone.ru › Ansible 2.5

Циклы

Часто вам потребуется выполнить много действий в одной задаче, например, создать много пользователей, установить много пакетов или повторить шаг опроса до достижения определенного результата.

В этой главе рассказывается о том, как использовать циклы в плейбуках.

  • Стандартные циклы
  • Сложные циклы
  • Использование 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

Spec-Zone.ru

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