Работа с Ansible Playbooks — советы и рекомендации с примерами

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

Если вы совсем новичок в Ansible, ознакомьтесь сначала с этим вводным учебником по Ansible.

Что такое Ansible Playbook?

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

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

Структура плейбука

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

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

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

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

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

---
- name: Example Simple Playbook
  hosts: all
  become: yes

  tasks:
  - name: Copy file example_file to /tmp with permissions
    ansible.builtin.copy:
      src: ./example_file
      dest: /tmp/example_file
      mode: '0644'

  - name: Add the user 'bob' with a specific uid 
    ansible.builtin.user:
      name: bob
      state: present
      uid: 1040

- name: Update postgres servers
  hosts: databases
  become: yes

  tasks:
  - name: Ensure postgres DB is at the latest version
    ansible.builtin.yum:
      name: postgresql
      state: latest

  - name: Ensure that postgresql is started
    ansible.builtin.service:
      name: postgresql
      state: started
Войти в полноэкранный режим Выход из полноэкранного режима

Мы определяем описательное имя для каждой пьесы в соответствии с ее целью на верхнем уровне. Затем мы представляем группу хостов, на которых будет выполняться пьеса, взятую из инвентаря. Наконец, мы определяем, что эти пьесы должны выполняться от имени пользователя root с опцией become, установленной на yes.

Вы также можете определить множество других ключевых слов Playbook на разных уровнях, таких как play, tasks, playbook, чтобы настроить поведение Ansible. Более того, большинство из них можно задать во время выполнения как флаги командной строки в конфигурационном файле ansible, ansible.cfg, или в инвентаре. Ознакомьтесь с правилами старшинства, чтобы понять, как Ansible ведет себя в этих случаях.

Далее мы используем параметр tasks, чтобы определить список задач для каждой пьесы. Для каждой задачи мы определяем четкое и описательное имя. Каждая задача использует модуль для выполнения определенной операции.

Например, первая задача первой пьесы использует модуль ansible.builtin.copy. Вместе с модулем нам обычно приходится определять некоторые аргументы модуля. Для второй задачи первой пьесы мы используем модуль ansible.builtin.user, который помогает нам управлять учетными записями пользователей. В данном конкретном случае мы настраиваем имя пользователя, состояние учетной записи пользователя и его uid соответствующим образом.

Запуск книги воспроизведения

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

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

В конце Ansible предоставляет сводку о выполнении плейбука вместе с информацией о неудачах и успехах. Давайте посмотрим на это в действии, запустив пример плейбука, который мы рассматривали ранее, с помощью команды ansible-playbook.

Из вывода мы видим имена игр, задачу Gathering Facts, задачи игр и в конце — Play Recap. Так как мы не определили группу хостов баз данных, вторая игра в учебнике была пропущена.

Мы можем использовать флаг —limit, чтобы ограничить выполнение Playbook определенными хостами. Например:

ansible-playbook example-simple-playbook.yml --limit host1
Войти в полноэкранный режим Выйти из полноэкранного режима

Использование переменных в плейбуках

Переменные — это держатели для значений, которые можно повторно использовать в Playbook или других объектах Ansible. Они могут содержать только буквы, цифры, знаки подчеркивания и начинаться с букв.

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

Чтобы задать переменные хоста и группы, создайте каталоги group_vars и host_vars. Например, чтобы определить групповые переменные для группы баз данных, создайте файл group_vars/databases. Задайте общие переменные по умолчанию в файле group_vars/all.

Более того, чтобы определить переменные хоста для конкретного хоста, создайте файл с тем же именем, что и хост, в каталоге hosts_vars.

Чтобы заменить какие-либо переменные во время выполнения, используйте флаг -e.

Наиболее простым методом определения переменных является использование блока vars в начале пьесы. Они определяются с помощью стандартного синтаксиса YAML.

- name: Example Variables Playbook
  hosts: all
  vars:
    username: bob
    version: 1.2.3
Вход в полноэкранный режим Выйти из полноэкранного режима

Другой способ — определить переменные во внешних YAML-файлах.

- name: Example Variables Playbook
  hosts: all
  vars_files:
    - vars/example_variables.yml
Войти в полноэкранный режим Выход из полноэкранного режима

Чтобы использовать их в задачах, мы должны сослаться на них, поместив их имя в двойные скобки, используя синтаксис Jinja2:

- name: Example Variables Playbook
  hosts: all
  vars:
    username: bob

  tasks:
  - name: Add the user {{ username }}
    ansible.builtin.user:
      name: "{{ username }}"
      state: present
Войти в полноэкранный режим Выйти из полноэкранного режима

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

Мы также можем задавать переменные с несколькими значениями в виде списков.

package:
  - foo1
  - foo2
  - foo3
Войти в полноэкранный режим Выйти из полноэкранного режима

Можно также ссылаться на отдельные значения из списка. Например, чтобы выбрать первое значение foo1:

package: "{{ package[0] }}"
Войти в полноэкранный режим Выйти из полноэкранного режима

Другим возможным вариантом является определение переменных с помощью словарей YAML. Например:

dictionary_example: 
  - foo1: one
  - foo2: two
Войти в полноэкранный режим Выйти из полноэкранного режима

Аналогично, чтобы получить первое поле из словаря:

dictionary_example['foo1']
Enter fullscreen mode Выйти из полноэкранного режима

Для ссылки на вложенные переменные необходимо использовать скобки или точечную нотацию. Например, чтобы получить значение example_name_2 из этой структуры:

vars:
  var1:
    foo1:
      field1: example_name_1
      field2: example_name_2

tasks:
- name: Create user for field2 value
  user: 
    name: "{{ var1['foo1']['field2'] }}"
Войти в полноэкранный режим Выйти из полноэкранного режима

Мы можем создавать переменные с помощью оператора register, который фиксирует вывод команды или задачи, а затем использовать их в других задачах.

- name: Example-2 Variables Playbook
  hosts: all

  tasks:
  - name: Run a script and register the output as a variable
    shell: "find example_file"
    args:
      chdir: "/tmp"
    register: example_script_output

  - name: Use the output variable of the previous task
    debug:
      var: example_script_output
Вход в полноэкранный режим Выход из полноэкранного режима

Работа с чувствительными данными

Иногда нам необходимо получить доступ к конфиденциальным данным (ключи API, пароли и т.д.) в наших плейбуках. Ansible предоставляет Ansible Vault, чтобы помочь нам в таких случаях. Хранение этих данных в виде переменных в открытом виде считается риском для безопасности, поэтому мы можем использовать команду ansible-vault для шифрования и расшифровки этих секретов.

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

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

В любом случае, нам нужно подумать о том, как мы будем управлять паролями хранилища. Чтобы определить зашифрованное содержимое, мы добавляем тег !vault, который сообщает Ansible, что содержимое должно быть расшифровано, и символ | перед нашей многострочной зашифрованной строкой.

Чтобы создать новый зашифрованный файл:

ansible-vault create new_file.yml
Войдите в полноэкранный режим Выйдите из полноэкранного режима

Затем будет запущен редактор для добавления нашего содержимого, которое нужно зашифровать. Также можно зашифровать существующие файлы с помощью команды encrypt:

ansible-vault encrypt existing_file.yml
Войти в полноэкранный режим Выйти из полноэкранного режима

Чтобы просмотреть зашифрованный файл:

ansible-vault view existing_file.yml
Войти в полноэкранный режим Выйти из полноэкранного режима

Чтобы отредактировать зашифрованный файл на месте, используйте команду edit для временной расшифровки файла:

ansible-vault edit existing_file.yml
Войти в полноэкранный режим Выйти из полноэкранного режима

Чтобы использовать другой пароль на зашифрованном файле, используйте команду rekey, используя исходный пароль:

ansible-vault rekey existing_file.yml
Войти в полноэкранный режим Выйти из полноэкранного режима

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

ansible-vault decrypt existing_file.yml
Войти в полноэкранный режим Выйти из полноэкранного режима

Аналогично, мы используем команду encrypt_string для шифрования отдельных строк, которые мы можем использовать позже в переменных и включать их в плейбуки или файлы переменных:

ansible-vault encrypt_string <password_source> '<string_to_encrypt>' –'<variable_name>'
Войти в полноэкранный режим Выйти из полноэкранного режима

Например, для шифрования строки db_password ‘12345679’ с помощью ansible vault:

Поскольку мы опустили , мы вручную ввели пароль хранилища. Этого также можно добиться, передав файл паролей с параметром —vault-password-file.

Чтобы просмотреть содержимое приведенной выше зашифрованной переменной, которую мы сохранили в файле vars.yml, используйте тот же пароль, что и раньше, с флагом —ask-vault-pass:

ansible localhost -m ansible.builtin.debug -a var="db_password" -e "@vars.yml" --ask-vault-pass

Vault password:

localhost | SUCCESS => {
    "changed": false,
    "db_password": "12345678"
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Для управления несколькими паролями используйте опцию —vault-id для установки метки. Например, чтобы установить метку dev на файле и запросить пароль для использования:

ansible-vault encrypt existing_file.yml --vault-id dev@prompt
Войти в полноэкранный режим Выйти из полноэкранного режима

Чтобы подавить вывод данных из задачи, которая может записать в консоль чувствительное значение, мы используем атрибут no_log: true:

tasks:
- name: Hide sensitive value example
  debug:
    msg: "This is sensitive information"
  no_log: true
Вход в полноэкранный режим Выход из полноэкранного режима

Если мы запустим эту задачу, то заметим, что сообщение не выводится на консоль:

TASK [Hide sensitive value example] ***********************************
ok: [host1]
Вход в полноэкранный режим Выход из полноэкранного режима

Наконец, давайте используем пример зашифрованной переменной, которую мы создали выше, в плейбуке и выполним его.

Отлично, мы убедились, что можем успешно расшифровать значение и использовать его в задачах.

Запуск задач при изменении с помощью обработчиков

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

Для решения этой задачи Ansible использует обработчики, запускаемые при уведомлении от других задач. Задачи уведомляют свои обработчики с помощью параметра notify: только тогда, когда задачи действительно что-то меняют.

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

- name: Example with handler - Update apache config
  hosts: webservers

  tasks:
  - name: Update the apache config file
    ansible.builtin.template:
      src: ./httpd.conf
      dest: /etc/httpd.conf
    notify:
    - Restart apache

  handlers:
    - name: Restart apache
      ansible.builtin.service:
        name: httpd
        state: restarted
Вход в полноэкранный режим Выйти из полноэкранного режима

В приведенном выше примере задача Restart apache будет запущена только тогда, когда мы изменим что-то в конфигурации. В действительности обработчики можно рассматривать как неактивные задачи, ожидающие запуска с помощью оператора notify.

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

Чтобы контролировать такое поведение, можно использовать задачу **meta: flush_handlers*, которая запускает все обработчики, которые уже были уведомлены на тот момент.

Задача также может уведомлять более одного обработчика в своем операторе notify.

Условные задачи

Для дальнейшего контроля потока выполнения в Ansible мы можем использовать условия. Условные элементы позволяют запускать или пропускать задачи в зависимости от выполнения определенных условий. Для создания таких условий можно использовать переменные, факты или результаты предыдущих задач, а также операторы.

Некоторые примеры использования могут быть следующими: обновить переменную на основе значения другой переменной, пропустить задачу, если переменная имеет определенное значение, выполнить задачу только в том случае, если факт из узла возвращает значение, превышающее пороговое.

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

- name: Example Simple Conditional
  hosts: all
  vars:
    trigger_task: true

  tasks:
  - name: Install nginx
    apt:
      name: "nginx"
      state: present
    when: trigger_task
Вход в полноэкранный режим Выход из полноэкранного режима

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

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

- name: Example Facts Conditionals 
  hosts: all
  vars:
    supported_os:
      - RedHat
      - Fedora

  tasks:
  - name: Install nginx
    yum:
      name: "nginx"
      state: present
    when: ansible_facts['distribution'] in supported_os
Вход в полноэкранный режим Выйти из полноэкранного режима

Можно объединить несколько условий с помощью логических операторов и сгруппировать их с помощью круглых скобок:

when: (colour=="green" or colour=="red") and (size="small" or size="medium")
Войти в полноэкранный режим Выйти из полноэкранного режима

Оператор when поддерживает использование списка в случаях, когда у нас есть несколько условий, которые все должны быть истинными:

when:
  - ansible_facts['distribution'] == "Ubuntu"
  - ansible_facts['distribution_version'] == "20.04"
  - ansible_facts['distribution_release'] == "bionic"
Войти в полноэкранный режим Выйти из полноэкранного режима

Другой вариант — использовать условия, основанные на зарегистрированных переменных, которые мы определили в предыдущих задачах:

- name: Example Registered Variables Conditionals
  hosts: all

  tasks:
  - name: Register an example variable
    ansible.builtin.shell: cat /etc/hosts
    register: hosts_contents

  - name: Check if hosts file contains "localhost"
    ansible.builtin.shell: echo "/etc/hosts contains localhost"
    when: hosts_contents.stdout.find(localhost) != -1
Войти в полноэкранный режим Выйти из полноэкранного режима

Циклы

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

Для итерации по простому списку элементов используйте ключевое слово loop. Мы можем ссылаться на текущее значение с помощью переменной item цикла.

- name: "Create some files"
  ansible.builtin.file:
    state: touch
    path: /tmp/{{ item }}
  loop:
    - example_file1
    - example_file2
    - example_file3
Вход в полноэкранный режим Выйти из полноэкранного режима

Вывод приведенной выше задачи, использующей цикл и item:

TASK [Create some files] *********************************
changed: [host1] => (item=example_file1)
changed: [host1] => (item=example_file2)
changed: [host1] => (item=example_file3)
Вход в полноэкранный режим Выход из полноэкранного режима

Также можно выполнять итерацию по словарям:

- name: "Create some files with dictionaries"
  ansible.builtin.file:
    state: touch
    path: "/tmp/{{ item.filename }}"
    mode: "{{ item.mode }}"
  loop:
    - { filename: 'example_file1', mode: '755'}
    - { filename: 'example_file2', mode: '775'}
    - { filename: 'example_file3', mode: '777'}
Войти в полноэкранный режим Выход из полноэкранного режима

Другим полезным шаблоном является итерация по группе хостов инвентаря:

- name: Show all the hosts in the inventory
  ansible.builtin.debug:
    msg: "{{ item }}"
  loop: "{{ groups['databases'] }}"
Войти в полноэкранный режим Выход из полноэкранного режима

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

- name: Execute when values in list are lower than 10
  ansible.builtin.command: echo {{ item }}
  loop: [ 100, 200, 3, 600, 7, 11 ]
  when: item < 10
Войти в полноэкранный режим Выйти из полноэкранного режима

Наконец, еще один вариант — использовать ключевое слово until для повторного выполнения задачи до тех пор, пока условие не станет истинным.

- name: Retry a task **until** we find the word "success" in the logs
  shell: cat /var/log/example_log
  register: logoutput
  until: logoutput.stdout.find("success") != -1
  retries: 10
  delay: 15
Войти в полноэкранный режим Выйти из полноэкранного режима

В приведенном выше примере мы проверяем файл example_log 10 раз, с задержкой в 15 секунд между каждой проверкой, пока не найдем слово success. Если мы дадим задаче выполниться и через некоторое время добавим слово success в файл example_log, то заметим, что задача успешно завершилась.

TASK [Retry a task until we find the word “success” in the logs] *********
FAILED - RETRYING: Retry a task until we find the word "success" in the logs (10 retries left).
FAILED - RETRYING: Retry a task until we find the word "success" in the logs (9 retries left).
changed: [host1]
Вход в полноэкранный режим Выход из полноэкранного режима

Ознакомьтесь с официальным руководством Ansible по Loops для более сложных случаев использования.

Советы и рекомендации по использованию Ansible Playbooks

Соблюдение этих советов и рекомендаций при создании своих плейбуков поможет вам быть более продуктивными и повысить эффективность работы.

1) Будьте как можно проще

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

2) Поместите артефакты Ansible под контроль версий

Считается лучшей практикой хранить плейбуки в git или любой другой системе контроля версий и пользоваться ее преимуществами.

3) Всегда давайте описательные имена своим задачам, пьесам и плейбукам.

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

4) Стремитесь к удобочитаемости

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

5) Всегда указывайте состояние задач в явном виде

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

6) Используйте комментарии, когда это необходимо

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

Ключевые точки

В этой статье мы рассмотрели основной компонент автоматизации Ansible — плейбуки. Мы увидели, как создавать, структурировать и инициировать выполнение плейбуков.

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

Спасибо за чтение, и я надеюсь, что вам понравилась эта статья «Ansible: Работа с Playbooks» так же, как и мне.

Кстати, если вы хотите управлять инфраструктурой как кодом, Spacelift — это то, что вам нужно. Он поддерживает рабочие процессы Git, политику как код, программную конфигурацию, совместное использование контекста и множество других замечательных функций. В настоящее время он работает с Terraform, Pulumi и CloudFormation, а поддержка Ansible уже на подходе! Вы можете опробовать его бесплатно, перейдя сюда и создав пробную учетную запись.

Оцените статью
Procodings.ru
Добавить комментарий