Управление серверами – Ansible (часть 2)

В предыдущей статье были описаны базовые концепции ansible. 

Часть при работе с серверами и создании скриптов необходимо учитывать особенности конкретного сервера, это например может быть разыне системы установки пакетов, поэтому внутри playbook можно использовать управляющие конструкции реализующие ветвление скрипта.

Условное выполнение

Расмотрим следующий скрипт, модификацию скрипта из предыдущей части:

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

Блоки

Блоки позволяют объединить группы задач.

Стоит отметить, что теперь мы имеем два блока каждый из которых выполняется по своему условию, расположенному к конце блока. Важно что бы начало блока и when были с одинаковым количеством отступов (на одном уровне). Этот подход позволил решить проблему с вызовом разных обработчиков.

Циклы

Программный пакет Ansible имеет несколько встроенных директив организации циклов, это директивы loop, unit и группа директив with_*. Эти директивы позволяют выполнить повторяющиеся действия такие как изменение прав доступа для множества файлов, создание многих пользователей и т.п.

Часто возникают задачи связанные с повтороение каких то действий, например копирование множества файлов:

Задача “Say hello” – будет повторять вывод сообщения для значений заданных в атрибуте with_items (в нашем случае Bob и World), при этом переменная item это зарезервировання переменная для использования в циклах. Стоит отметить, что начиная с версии 2.5 есть еще один атрибут, который называется loop имеющий синтаксис такой же как показан в примере. В более новой версии будет только loop.

Полезное применение, это установка множества пакетов:

Вторая задача “Loop Until” это демо пример, который выполняет на сервере shell команду, которая добавляет в файлл символ Z без вставки переноса строки и выводит текщие содержимое этого файла. Результат работы команды сохраняется в переменную output. Далее указаны два не обязательных атрибута delay и retries, которые задают паузу в секундах между выполнениями и количество попыток. Затем указывается условие проверки на завершение цикла, которое проверяет что stdout команды содержит 4 символа “Z”. (Цикл выполняется до тех пор пока условие истинное, то есть в нашем случае до тех пор пока в строке не будет 4-х символов). retries – задает максимальное количество повторений, после которого цикл все равно завершится.

Перепишем пример с установкой WEB серверов, так, что бы копировалось множество файлов при этом модифицируем сам скрипт.

Первое, на что стоит обратить внимание, это то, что имееется возможность вызвать более одного обработчика. Кроме того к обработчикам также может быть применен атрибут when – проверяющий условие выполнения. Оптимизация выносит копирование из блоков, таким образом общая операция появляется в файле только один раз.

Копирование множества файлов можно сделать через применение дериктивы цикла with_fileglob, как показано в примере выше. Эта директива возмет по шаблону все файлы и выполнит для каждого из них команду.

Второй вариант показан в виде коментария это копирование циклом в тому случае если файлы нужно именно указать.

Краткие различие этих директив:

  • Директивы  with_* – базируются на использовании плагина Lookup. Любое применение этих директив может быть преобразовано в loop.
  • Директива loop – является встроенной и эквивалентна with_loop, для простых циклов это самый лучший выбор. Эта директива не работает со строками содержащими разделенными запятыми значения (по типу список в строке).

Важно заметить, директива with_items производит разворачивание списков первого уровня, поэтому для замены with_items на loop, требуется применение фильтра flatten(1). Пример:

– debug: var=item
  loop:
   – 1
   – [2, 3]

   – 4

Будет обрабатывать следующие значения: 1, [2, 3], 4.

Однако директива with_items ведет себя по-другому:

– debug: var=item
  loop:
   – 1
   – [2, 3]

   – 4

Будет обрабатывать вот такие элементы: 1, 2, 3, 4. Стоит отметить, что внутренний список был раскрыт и также обработан. Что бы вместе loop получить такое же поведение требуется использовать вот такую команду:

  • debug: var=item

loop: “{{ [1, [2, 3], 4] | flatten(1) }}”

Еще одна особенность применения может касаться применения команд вроде with_fileglob, эта команда перебирает файлы:

with_fileglob: ‘*.txt’

Однако вариант с применением loop будет намного сложнее:

loop: “{{ lookup(‘fileglob’, ‘*.txt’, wantlist=True) }}”

Циклы

К любой команде могут быть добавлено циклическое повторение при этом команда выполнится для списка значений подставляя каждое из значений в команду:

name: Add several users  debug:    var: “{{ item }}”  loop:      testuser1      testuser2

Также в качестве значений может быть использован список заданный в переменной, тогда директива loop примет вид:

loop: “{{ somelist }}”

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

Итерации по словарям:

name: Add several users  ansible.builtin.user:    name: “{{ item.name }}”    state: present    groups: “{{ item.groups }}”  loop:     { name: ‘testuser1’, groups: ‘wheel’ }     { name: ‘testuser2’, groups: ‘root’ }

Возможный второй вариант итерации по объектам:

name: Using dict2items  ansible.builtin.debug:    msg: “{{ item.key }} – {{ item.value }}”  loop: “{{ tag_data | dict2items }}”  vars:    tag_data:      Environment: dev      Application: payment

Замечание по директиве register, стоит отметить, что при многократном выполнении команд, результат каждого выполнения помещается в список results, который будет содержатся в переменной, которую зарегистрировали.

Итерации по хостам в инвентаризации

Есть в вариант итерации по переменным group и ansible_play_batch:

name: Отображает все хосты в инвентаризационном файле  

     debug:    msg: “{{ item }}”  

     loop: “{{ groups[‘all’] }}” 

name: Отображает все хосты для которых работает плейбук  

      debug:    msg: “{{ item }}”  

      loop: “{{ ansible_play_batch }}”

Кроме того есть еще плагин inventory_hostnames:

name: Отображает все хосты в инвентаризационном файле  

   debug:    msg: “{{ item }}”  

   loop: “{{ query(‘inventory_hostnames’, ‘all’) }}” 

name: Отображает все хосты в инвентаризационном файле, кроме группы www   

   debug:    msg: “{{ item }}”  

   loop: “{{ query(‘inventory_hostnames’, ‘all:!www’) }}”

Есть две особенные функции lookup (возвращает разделенную запятыми строку) и query (всегда возвращает список), можно заставить lookup, также возвращать список, применив аргумент wantlist. Следующие команды аналогичны, но первый вариант более рекомендуем

loop: “{{ query(‘inventory_hostnames’, ‘all’) }}” loop: “{{ lookup(‘inventory_hostnames’, ‘all’, wantlist=True) }}”

Управление циклом

При итерации по сложным структурам, иногда необходимо проходить не по всем значениям, а только по определённым ключам:

name: Create servers  

    digital_ocean:    name: “{{ item.name }}”    

    state: present  

  loop:    

      – name: server1      

        disks: 3gb      

         ram: 15Gb      

         network:        …  

  loop_control:    label: “{{ item.name }}”

В этом примере происходит использование каждого ключа name, все остальные не используются.

Есть еще дополнительные возможности по управлению циклом, например можно установить паузу между итерациями, которая задается в секундах:

  loop_control:    pause: 3

Кроме того, можно определить переменную, которая будет сдержать индекс итерации:

   loop_control:    index_var: my_idx

Эта индексная переменная начинается с 0.

Также можно изменить имя переменной с элементом итерации с item, на другое:

  loop_control:    loop_var: outer_item

Кроме того значение имени которое было задано можно получить через переменную ansible_loop_var (только для версии позднее 2.8 можно просмотреть как то так: “{{ lookup(‘vars’, ansible_loop_var) }}” ).

Что может быть полезно, например, в случае, когда нужен внутренний цикл и при этом во внутреннем цикле требуется доступ к обоим переменным. Стоит также отметить, что циклы не могут напрямую вкладываться друг в друга, в этом случае придется применить трюк с include_tasks (вынесением отдельных задач в другой файл):

# main.yml

include_tasks: inner.yml

 loop:

     1

     2

 loop_control:

    loop_var: outer_item 

# inner.yml

name: Print outer and inner items

 debug:    msg: “outer item={{ outer_item }} inner item={{ item }}”  

loop:

     a

     b

В новых версиях есть еще одно значение:

loop_control:  extended: yes

Которое дает циклу доступ к набору расширенных переменных цикла:

ansible_loop.allitems

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

ansible_loop.index

Текущая итерация цикла. (первое значение 1)

ansible_loop.index0

Текущая итерация цикла. (первое значение 1)

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

Значение, которое будет на следующем шаге. Для последнего шага не определено.

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

В следующей части расмотрим шаблоны и роли.

Обсуждение закрыто.