Как написать playbook ansible

Playbooks are automation blueprints, in YAML format, that Ansible uses to deploy and configure managed nodes.

Playbook

A list of plays that define the order in which Ansible performs operations, from top to bottom, to achieve an overall goal.

Play

An ordered list of tasks that maps to managed nodes in an inventory.

Task

A list of one or more modules that defines the operations that Ansible performs.

Module

A unit of code or binary that Ansible runs on managed nodes.
Ansible modules are grouped in collections with a Fully Qualified Collection Name (FQCN) for each module.

In the previous section, you used the ansible command to ping hosts in your inventory.
Now let’s create a playbook that pings your hosts and also prints a “Hello world” message.

Complete the following steps:

  1. Open a terminal window on your control node.

  2. Create a new playbook file named playbook.yaml in any directory and open it for editing.

  3. Add the following content to playbook.yaml:

    - name: My first play
      hosts: virtualmachines
      tasks:
       - name: Ping my hosts
         ansible.builtin.ping:
       - name: Print message
         ansible.builtin.debug:
           msg: Hello world
    
  4. Run your playbook.

    ansible-playbook -i inventory.yaml playbook.yaml
    

Ansible returns the following output:

PLAY [My first play] **********************************************************************

TASK [Gathering Facts] ********************************************************************
ok: [vm01]
ok: [vm02]
ok: [vm03]

TASK [Ping my hosts] **********************************************************************
ok: [vm01]
ok: [vm02]
ok: [vm03]

TASK [Print message] **********************************************************************
ok: [vm01] => {
    "msg": "Hello world"
}
ok: [vm02] => {
    "msg": "Hello world"
}
ok: [vm03] => {
    "msg": "Hello world"
}

PLAY RECAP ********************************************************************************
vm01: ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
vm02: ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
vm03: ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

In this output you can see:

  • The names that you give the play and each task.
    You should always use descriptive names that make it easy to verify and troubleshoot playbooks.

  • The Gather Facts task runs implicitly.
    By default Ansible gathers information about your inventory that it can use in the playbook.

  • The status of each task.
    Each task has a status of ok which means it ran successfully.

  • The play recap that summarizes results of all tasks in the playbook per host.
    In this example, there are three tasks so ok=3 indicates that each task ran successfully.

Congratulations! You have just created your first Ansible playbook.

Статья подготовлена на основе уроков из открытой темы «Установка LEMP стека с помощью Ansible» курса по Ansible от Слёрм. Автор – Всеволод Севостьянов, Lead Engineer в Vene.io (Affiliate marketing solution). Первые две темы курса доступны на Youtube.

Материал этого урока будет интересен тем, кто разобрался с установкой Ansible и готов написать свой первый плейбук. Результатом будет плейбук, устанавливающий nginx на удалённой машине.

Hosts

Начнём наше знакомство с Ansible с мануала по командам. 

Плох тот админ, который не читает маны – верно?

На что смотрим. Первое – это обязательный аргумент pattern, который указывает, какие хосты мы берем, на каких машинах мы будем выполнять наши команды. Мы будем выполнять на всех – all. Второе, персональная команда – вывести хосты, то есть, команда, которая выведет нашу машину. 

ansible all --list-hosts

All означает – все хосты, которые у нас возможны. Увидим ответ, что список пустой. Почему так происходит? Потому что Ansible по умолчанию берет данные из /etc/ansible/hosts. Посмотрим, что это за файл.

cat /etc/ansible/hosts

Здесь всё задокументировано

Здесь всё задокументировано

Видим, что мы можем указывать хосты просто через enter и указывать их IP-адреса, либо их доменные имена. Можем их группировать. 

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

Заходим в папочку ansible, в нашем проекте. И создаём файлик, я назову его hosts.ini. Ini – это как раз тот самый формат, когда вы всё перечисляете просто через enter и пишите строку, стандартный формат конфигурации. 

Укажем в первой строке IP-адрес нашего второго хоста, то есть, нашего сервера непосредственно. У меня 192.168.50.5 и теперь запускаем команду Ansible ещё раз.

ansible all --list-hosts

Естественно, он никак не отреагирует, он по-прежнему берет данные из файлика /etc/ansible/hosts

Для того чтобы он брал данные из нашего файлика – вызовем команду h и увидим в списке команду –i. Ansible называет файлики hosts – эти файлики, где у нас все машины перечислены, inventory. То есть, как по аналогии с каким-то кладовщиком – у него есть инвентарь и в нём указаны все машины, которые нам принадлежат. Мне кажется, это вполне логичным, легко запоминается и указывается через –i. 

Мы находимся в корневой папке пользователя и для того, чтобы нам перейти в папку выше – мы указываем ./ansible/hosts.ini. 

ansible all --list-hosts -i ./ansible/hosts.ini

И давайте разберемся немножко с тем, как запускать команды из Ansible. Попробуем подключиться к удаленному хосту и запустить что-нибудь. В Ansible все команды являются модулями и указываются с помощью ключевого слова m. Запустим стандартный модуль, называемый ping, который ничего не сделает на машине, кроме того, как даст ей команду ping. Это команда, которая физически выполняется Ansible на удаленной машине. Он в неё заходит, загружает модуль Python, вызывает команду ping. И выдаёт ответ. 

cd /ansible
ansible all -i hosts.ini -m ping

Нас просят подтвердить, поскольку подключается по SSH, нужно подтверждение сертификата. И мы увидим первую ошибку, что доступ закрыт. Стандартная ошибка, это значит, что нам нужно ввести пароль. На этот случай у Ansible существует команда —ask-pass.

ansible all -i hosts.ini -m ping --ask-pass

Которая спрашивает с каким паролем мы заходим. Следующая ошибка, с которой мы сталкиваемся: нельзя использовать пароли без установки команды sshpass. Проблема решается просто.

sudo apt install sshpass

Итак, команда sshpass установлена, и мы, соответственно, вызываем команду ping и задаём ему пароль.

ansible all -i hosts.ini -m ping --ask-pass

При этом, как мы увидим, команда выполнилась, вывела нам ответ в формате json. Написала, что на удаленной машине у нас Python3, который мы используем для выполнения этого модуля, ничего не изменилось на ней. Команда, не меняющая ничего, и ответ на ping – pong. 

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

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

Одним из атрибутов нашего файлика hosts является то, что мы можем присвоить каждой из машин, находящихся в нём какие-то специфичные для неё параметры. И одним из набора специфичных параметров для этой машины являются переменные ansible_user и ansible_password, которые, соответственно, задают пользователя и пароль.

192.168.50.5 ansible_user=vagrant ansible_password=vagrant

Теперь, если мы запустим Ansible, если мы уберем —ask-pass, то есть, не будем запрашивать пароль, но оставим всё остальное, то, соответственно, все равно нам вернётся success, на этот раз никто у нас никакой пароль не спрашивал.

ansible all -i hosts.ini -m ping

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

NGINX

Итак, что такое плейбук? Это термин, взятый из футбола. Обозначает книгу тренера, который там что-то записывает и задаёт сценарий игре. Плейбук– это оно и есть, список сценариев для запуска Ansible. То, что мы делали командой Ansible –m, просто запущенная с разными командами, разными аргументами несколько раз. Плейбуки позволяют нам также определить, на каких хостах мы им запускаем и какие задачи мы им задаём. И мы посмотрим, как это работает.

В папке ansible создадим файл playbook.yml. Плейбуки пишутся в формате yaml. Мы будем знакомиться с этим форматом в процессе написания плейбуков. На данный момент, что нас интересует? Плейбуки начинаются с трёх дэшей или с трёх чёрточек и закачиваются тремя точками.

---
...

Этот формат необязательный. Я, например, не использую три точки, но начинаю с чёрточек, потому что это достаточно удобно. Плейбуки позволяют нам делать комментарии, поэтому я здесь сразу и напишу.

# LEMP PLAYBOOK
---

Соответственно, в плейбуке нужно обязательно ставить какие-то play. Play задаются с помощью массива, то есть в yaml следующая запись обозначает массив.

# LEMP PLAYBOOK
---
- test
- test1
- test2

И здесь мы можем написать, например, разные переменные. Наш плейбук в себе будет содержать play, и каждый play в себе обязан содержать hosts – это также как мы писали в консоли Ansible – all. Здесь то же самое. Это базовая вещь, базовый play, и мы можем запустить плейбук.

# LEMP PLAYBOOK
---
- hosts: "all"

Если мы хотим создать второй play – мы создаём второй элемент массива, ему определяем какой-то второй параметр и для yaml это значит, что это массив с двумя переменными. Сейчас нам это не нужно.

Переходим в консоль Ansible. Командой ls увидим, что у нас есть файлы hosts.ini и playbook.yml. И для того, чтоб запустись плейбук, у Ansible существует консольная команда: ansible-playbook. Она очень похожа на команду Ansible, за исключением того, что вместо передачи аргументов хостов, мы передаём аргумент плейбук, то есть путь к файлику playbook.yml. Укажем hosts.ini, как файлик инвентаря, то есть, это две базовые вещи, которые нужно ansible-playbook для того, чтобы работать. Какие хосты и что мы делаем.

ansible-playbook playbook.yml -i hosts.ini

Теперь он запускает PLAY [all] – это play, который нам говорит для всех хостов. И первый TASK [Gathering Facts], который мы даже не указывали – это собирание фактов, первый модуль, который загрузится – это модуль Ansible, который загружается на машину и собирает все факты с этой машины. На данном моменте нам достаточно знать, что каждый play в плейбуке собирает факты о машине – это может быть операционная система, версия Python, которая там стоит, IP-адреса, порты и так далее.

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

Каждый хост, каждый play в плейбуке характеризуется целью, на какие хосты мы его применяем и набором задач, так называемыми task-ами. Task-и – это тоже массив, потому что task на play может быть много, соответственно, как play в плейбуке, здесь всё логично. И каждый task, как минимум, содержит в себе имя. Имя, также как и hosts, — это строка. Строки в yaml задаются любо через кавычки, либо без них. Я рекомендую кавычки, потому что в таком случае yaml не будет теряться при попытке скастовать что-то, то есть, если у вас там строка — это строка, если у вас число – это число и к тому же, это очень хорошо видно в IDE. И первая task-а для нас – это будет установка самого nginx на удалённой машине. Давайте, так её и назовём. Install nginx via apt. И поехали делать task. 

# LEMP PLAYBOOK
---
- hosts: "all"
  tasks:
  - name: "Install nginx via apt"

После указания имени task, нам нужно указать модуль, который будет его выполнять. Как вот мы указывали -m ping, здесь надо сделать тоже самое. Поскольку nginx у нас установится через систему APT, мы можем посмотреть, как же она называется в Ansible.

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

Существует модуль ansible.builtin.apt, который как раз позволяет устанавливать нам софт через apt-packages. И здесь все его параметры и достаточно интересная документация с примерами. Я смотрю на примеры, как человек со стажем, как правило, они более актуальны, чем документация. В данном случае они совпадают. И смотрим, что для apt нужно указать имя и state.

Смотрим. Для начала: разница как раз между Ansible 2.9, 2.10 и 2.11 в том, что Ansible будет форсить, будет заставлять людей использовать полные имена модулей. Потому что, если кто-то еще напишет модуль apt, и он у вас будет установлен в системе, Ansible может потеряться. Он может не понять, какой apt ему запускать. Поэтому надо всегда указывать полный путь. И так мы и будем поступать. Соответственно, здесь указывается полный путь к модулю. И дальше указываются параметры модуля.

# LEMP PLAYBOOK
---
- hosts: "all"
  tasks:
  - name: "Install nginx via apt"
    ansible.builtin.apt:

Важно понимать, что это все словари. Имя — это ключ словаря и строка «Install nginx via apt» — это значение. Ansible.builtin.apt – это тоже ключ, а значение у него это список аргументов и они в yaml идут через 2 пробела. Это важно, потому что если у вас не будет нужного количества пробелов, yaml будет ругаться на то, что он не может ничего сконфигурировать. И поэтому я и рекомендую использовать какую-нибудь IDE, а не писать просто это все в блокноте и следить там руками за пробелами. Это неудобно.

Поэтому, указываем первый параметр — это имя пакета. Имя пакета у нас просто nginx.

И второй параметр — это state. Соответственно, что такое state? State, как написано в документации — это в каком состоянии мы хотим найти пакет. Ansible, он сам по себе декларативный, то есть, вы не указываете в плейбуке, что ему нужно сделать, вы указываете, что вы хотите видеть. И state – это как раз очень хороший пример. То есть вы не говорите, что я хочу установить такой-то nginx, вы говорите: я хочу видеть пакет nginx в apt в таком-то state-е. По умолчанию state – present. Это значит, что если у вас уже есть какой-то пакет в Ansible, то state у него, соответственно, будет неизменным. И есть state – latest. В таком случае, если apt найдет какую-то более свежую версию пакета, то он попробует ее обновить.

Последняя команда, которую я хотел бы задать здесь, это update_cache. Это тоже самое, что вы запускаете каждый раз перед запуском любой команды apt, соответственно sudo apt. Update, которая просто обновит cache всех библиотек, которые у вас лежат в архиве.

# LEMP PLAYBOOK
---
- hosts: "all"
  tasks:
  - name: "Install nginx via apt"
    ansible.builtin.apt:
      name: "nginx"
      state: "latest"
      update_cache: true

Запускаем Плейбук в консоли Ansible второй раз.

ansible-playbook playbook.yml -i hosts.ini

Первая команда Gathering Facts. Вторая команда Install nginx via apt. И она провалилась. То есть написано «Failed to lock apt for exclusive operation». Это не очень подробное описание, но оно нам позволяет понять, что не получилось у apt получить какой-то lock.

Почему? Потому что существует в Ansible политика выполнения команд от того пользователя, которым вы залогинились. То есть в данном случае пользователь vagrant пытается установить что-то через apt. И не может, потому что apt пишет в системные файлы, которым нужен root.

Для того, чтобы Ansible стал root, мы используем команду в play become true, которая будет выполнятся для всех task в этом play. Можем ее использовать внутри task, можем ее использовать внутри play. В данном случае я хочу, чтобы все, что мы делали, делалось через sudo под root. И поэтому ставлю ее в непосредственно play.

# LEMP PLAYBOOK
---
- hosts: "all"
  become: true
  tasks:
  - name: "Install nginx via apt"
    ansible.builtin.apt:
      name: "nginx"
      state: "latest"
      update_cache: true

Теперь команда будет запущенна от root. Можно видеть, что true в данном случае не строка, а булево. То есть истина или ложь. Точно также, как и update_cache. Это еще один тип переменных в формате yaml.

Запускаем Плейбук в консоли Ansible третий раз.

ansible-playbook playbook.yml -i hosts.ini

И мы видим, что nginx успешно установился. Также видим новый статус – changed. Это значит, что nginx что-то изменил в системе – установил пакет.

Здесь самое время сказать, что Ansible является идемпотентным, то есть, он задуман, как идемпотентная система. Что это значит? При запуске одного и того же плейбука 2 раза на одной и той же системе, у вас в этой системе ничего не изменится. Второй раз apt даже пытаться не будет, он проверит список пакетов, он увидит nginx и не будет ничего дальше делать. То есть статус ok — это значит, что система соответствует тому статусу, который вы от неё ожидаете.

Теперь чтобы проверить, что nginx действительно настроен, возьмём любой шаблон лендига и запустим.

Создаём task. Удаляем папку по умолчанию var/www/html. Для того, чтобы скопировать нашу папку с нашим лендингом для заглушки на веб-сайт. Воспользуемся другой командой Ansible – ansible.builtin.file. Указываем путь – path. И дальше помним про идемпотентность – указываю state – absent. Просто указываю state. То есть, как там всё будет происходить, как эти файлы будут удалятся. Ansible написан в таком духе, что мы описываем состояние системы.

# LEMP PLAYBOOK
---
- hosts: "all"
  become: true
  tasks:
  - name: "Install nginx via apt"
    ansible.builtin.apt:
      name: "nginx"
      state: "latest"
      update_cache: true

  - name: "Delete /var/www/html folder"
    ansible.builtin.file:
      path: "/var/www/html"
      state: "absent"

Ну, и последнее, что мы делаем, копируем наш лендинг в папку var/www/html. У меня сейчас лендинг в папке files в директории Ansible. Пишем последнее имя task «Copy our lending to /var/www/html folder». Будем пользоваться командой ansible.builtin.copy, которая будет копировать файлы с нашей локальной машины на удаленную машину.

Указываем путь. Пути поддерживаются абсолютные и относительные. На нашей машине, скорее всего, мы не знаем, откуда мы будем запускать Ansible, поэтому пишем относительные пути – scr: «files/html» И в dest мы указываем «/var/www/». В чем смысл? Когда мы копируем через vagrant upload. Vagrant upload нам копирует содержимое папки внутрь. В данном случае Ansible будет копировать всю папку. И, поэтому, если мы хотим скопировать что-то в var/www/html, нам нужно назвать папку соответствующим образом и скопировать ее корневую директорию.

Далее мы указываем owner и group. Это просто пользователь и группа. И указываем mode – режим. Если не указать пользователя, группу и режим, то Ansible будет пытаться скопировать это все под пользователем, под которым он запущен. И с become true это, скорее всего, будет root.

# LEMP PLAYBOOK
---
- hosts: "all"
  become: true
  tasks:
  - name: "Install nginx via apt"
    ansible.builtin.apt:
      name: "nginx"
      state: "latest"
      update_cache: true

  - name: "Delete /var/www/html folder"
    ansible.builtin.file:
      path: "/var/www/html"
      state: "absent"

  - name: "Copy our lending to /var/www/html folder"
    ansible.builtin.copy:
      scr: "files/html"
      dest: "/var/www/"
      owner: "vagrant"
      group: "vagrant"
      mode: "0644"

Запускаем наш Playbook ещё раз. Он идемпотентный, то есть второй раз nginx у нас не установится. Наш объект будет удален и копируется наш новый лендинг в var/www/html.

Осталось проверить, как это работает. Открываем IP-адрес нашего сервиса. Всё, поздравляю, nginx настроен. Буквально 3 команды в плейбуке.

/

26 мая, 2021 12:27 пп
973 views
| Комментариев нет

Development, VPS

Ansible – это современный инструмент управления конфигурацией. Для связи и выполнения команд на управляемых серверах (нодах) он использует только SSH и Python – то есть вам не нужно устанавливать агентское программное обеспечение на удаленные ноды. В этой серии мануалов вы познакомитесь с основными функциями Ansible, которые можно использовать для написания сценариев автоматизации серверов. В конце серии вы найдете практический пример плейбука для автоматизации настройки удаленного веб-сервера Nginx и развертывания на нем статического HTML-сайта.

Примечание: Это первая часть серии по работе с Ansible. Другие мануалы из этой серии вы найдете по тегу ansible-practice. Весь код можно найти в этом репозитории.

Ansible позволяет управлять серверами двумя способами: с помощью специальных команд и с помощью плейбуков. Плейбуки (playbooks) – это сценарии Ansible, которые используют формат YAML для определения одного или нескольких плеев. Плей – это упорядоченный набор задач, которые организованы таким образом, чтобы автоматизировать пошаговый процесс (к примеру, это может быть настройка веб-сервера или развертывание приложения в производственной среде). Плейбуки Ansible позволяют полностью автоматизировать настройку сервера и развертывание приложений, имеют доступный синтаксис и предоставляют обширную библиотеку встроенных ресурсов.

Требования

  • Главная нода Ansible: это машина, с помощью которой мы будем подключаться к удаленным нодам кластера по SSH и управлять их конфигурацией. Главной нодой может быть ваша локальная машина или отдельный сервер, выделенный под работу Ansible. В данной серии мы используем в качестве главной ноды машину Ubuntu 20.04. На этой ноде должен быть пользователь с привилегиями sudo (обратитесь к мануалу по начальной настройке сервера). Имейте в виду: если вы используете удаленный сервер в качестве главной ноды, вам нужен брандмауэр (инструкции по его настройке также есть в вышеупомянутом мануале).
  • Ключи SSH, связанные с пользователем sudo. Обратитесь к мануалу Установка SSH-ключей в Ubuntu 20.04.
  • Один или несколько хостов Ansible: хост в Ansible – это любая машина, автоматизация которой происходит через главную ноду Ansible. В этом руководстве хосты Ansible являются удаленными серверами Ubuntu 20.04. Обязательно добавьте открытый SSH ключ главной ноды в authorized_keys системного пользователя на каждом хосте Ansible. Этот может быть либо пользователь root, либо обычный пользователь с привилегиями sudo. За инструкциями можно обратиться к разделу 2 мануала Установка SSH-ключей в Ubuntu 20.04.
  • Настроенный экземпляр Ansible на главной ноде. Чтобы настроить Ansible, следуйте руководству Установка и настройка Ansible в Ubuntu 20.04.
  • Рабочий файл инвентаря Ansible. Этот файл должен находиться на главной ноде и содержать все ваши хосты Ansible. В руководстве Создание файла инвентаря Ansible подробно объясняется, как создать такой файл.

Выполнив эти предварительные требования, проверьте подключение, как описано в руководстве Управление несколькими серверами при помощи команд Ansible, чтобы убедиться, что вы можете подключаться к удаленным хостам.

Создание плейбука

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

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

Часто директиве hosts присваивается значение all, потому что в Ansible вы можете ограничить цели плея во время выполнения, запустив команду ansible-playbook с параметром -l. Такой подход позволяет запускать один и тот же плейбук на разных серверах или в разных группах – при этом без необходимости каждый раз переписывать сам плейбук.

Начнем с создания нового каталога в домашнем каталоге, где мы сможем хранить плейбуки. Во-первых, убедитесь, что вы находитесь в домашнем каталоге вашего системного пользователя. Затем создайте каталог ansible-practice и перейдите в этот каталог с помощью команды cd:

cd ~

mkdir ansible-practice

cd ansible-practice

Если вы выполнили все предварительные требования, у вас уже должен быть готовый рабочий файл инвентаря. Теперь вы можете скопировать этот файл в свой новый каталог ansible-practice. Допустим, если вы создали свой тестовый файл инвентаря в каталоге ansible в своей домашней папке, вы можете скопировать файл в новый каталог с помощью такой команды:

cp ~/ansible/inventory ~/ansible-practice/inventory

Затем создайте новый плейбук:

nano playbook-01.yml

Следующий плейбук определяет плей, нацеленный на все хосты из данного инвентаря. Он содержит единственную задачу – вывести на экран отладочное сообщение.

Примечание: Больше о задачах мы расскажем в следующей части этой серии.

Добавьте в файл playbook-01.yml следующие строки:

---

- hosts: all

  tasks:

    - name: Print message

      debug:

        msg: Hello Ansible World

Сохраните и закройте файл. Если вы используете nano, вы можете нажать Ctrl+X, затем Y и Enter для подтверждения.

Чтобы проверить этот playbook на сервере (или серверах), который вы настроили в своем файле инвентаря, запустите команду ansible-playbook с теми же аргументами подключения, которые вы использовали при запуске теста в самом начале мануала. Здесь мы используем файл инвентаря по имени inventory и пользователя 8host для подключения к удаленному серверу (но не забудьте изменить эти данные, чтобы они соответствовали вашим данным):

ansible-playbook -i inventory playbook-01.yml -u 8host

Вы увидите такой результат:

PLAY [all] ***********************************************************************************

TASK [Gathering Facts] ***********************************************************************

ok: [203.0.113.10]

TASK [Update apt cache] **********************************************************************

ok: [203.0.113.10] => {

    "msg": "Hello Ansible World"

}

PLAY RECAP ***********************************************************************************

203.0.113.10             : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

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

Мы еще поговорим о фактах Ansible в других мануалах данной серии.

Tags: Ansible, ansible-practice

Learning to Write Playbooks

In this tutorial we are going to create a simple playbook to add system users, install and start ntp service and some basic utilities.

Anatomy of a Playbook

YAML — The Language to write Playbooks

Writing and executing our first Playbook

Problem Statement

You have to create a playbook which will

  • create a admin user with uid 5001
  • remove user dojo
  • install tree utility
  • install ntp

on all systems which belong to prod group in the inventory

To create playbook,

  • Change working directory to /vagrant/code/chap5
  • Edit myhosts.ini if required and comment the hosts which are absent.

  • Add the following configuration to ansible.cfg.

retry_files_save_path = /tmp

This defines the path where retry files are created in case of failed ansible run. We will learn about this in later in this chapter.

  • Create a new file with name playbook.yml and add the following content to it
---
  - name: Base Configurations for ALL hosts
    hosts: all
    become: true
    tasks:
      - name: create admin user
        user: name=admin state=present uid=5001

      - name: remove dojo
        user: name=dojo  state=absent

      - name: install tree
        yum:  name=tree  state=present

      - name: install ntp
        yum:  name=ntp   state=present

Validating Syntax

Option 1 : Using —syntax-check option with ansible-playbook

ansible-playbook playbook.yml --syntax-check

Exercise: Break the syntax, run playbook with —syntax check again, and learn how it works.

Option 2 : Using YAML Linter Online

Another way to validate syntax

  • Visit http://www.yamllinter.com
    YAML Linter

Using ansible-playbook utility

We will start using ansible-playbook utility to execute playbooks.

To learn how to use ansible-playbook execute the following command,

[output]

Usage: ansible-playbook playbook.yml

Options:
  --ask-become-pass     ask for privilege escalation password
  -k, --ask-pass        ask for connection password
  --ask-su-pass         ask for su password (deprecated, use become)
  -K, --ask-sudo-pass   ask for sudo password (deprecated, use become)
  --ask-vault-pass      ask for vault password
  -b, --become          run operations with become (nopasswd implied)
  --become-method=BECOME_METHOD
                        privilege escalation method to use (default=sudo),
                        valid choices: [ sudo | su | pbrun | pfexec | runas |
                        doas ]

.......

Dry Run

To execute ansible in a check mode, which will simulate tasks on the remote nodes, without actually committing, ansible provides —check or -C option. This can be invoked as ,

ansible-playbook playbook.yml --check

or

ansible-playbook playbook.yml -C

Listing Hosts, Tasks and Tags in a Playbook

ansible-playbook playbook.yml --list-hosts

ansible-playbook playbook.yml --list-tasks

ansible-playbook playbook.yml --list-tags

Executing Actions with Playbooks

To execute the playbook, we are going to execute ansible-playbook comman with playbook YAML file as an argument. Since we have already defined the inventory and configurations, additional options are not necessary at this time.

ansible-playbook playbook.yml
[output]

PLAY [Base Configurations for ALL hosts] ***************************************

TASK [setup] *******************************************************************
ok: [192.168.61.14]
ok: [192.168.61.11]
ok: [localhost]
ok: [192.168.61.12]
ok: [192.168.61.13]

TASK [create admin user] *******************************************************
changed: [192.168.61.13]
changed: [192.168.61.12]
changed: [localhost]
changed: [192.168.61.11]
changed: [192.168.61.14]

TASK [remove dojo] *************************************************************
changed: [192.168.61.14]
changed: [localhost]
changed: [192.168.61.12]
changed: [192.168.61.11]
changed: [192.168.61.13]

TASK [install tree] ************************************************************
ok: [localhost]
ok: [192.168.61.13]
ok: [192.168.61.12]
ok: [192.168.61.14]
ok: [192.168.61.11]

TASK [install ntp] *************************************************************
changed: [192.168.61.12]
changed: [192.168.61.13]
changed: [192.168.61.11]
changed: [localhost]
changed: [192.168.61.14]

Error Handling and Debugging

We are now going to add a new task to the playbook that we created. This task would start ntp service on all prod hosts.

When you add this task, make sure the indentation is correct.


      - name: start ntp service
        service: name=ntp state=started enabled=yes

  • Apply playbook again, check the output
ansible-playbook playbook.yml

[output]

TASK [start ntp service] *******************************************************
fatal: [localhost]: FAILED! => {"changed": false, "failed": true, "msg": "no service or tool found for: ntp"}
fatal: [192.168.61.11]: FAILED! => {"changed": false, "failed": true, "msg": "no service or tool found for: ntp"}
fatal: [192.168.61.12]: FAILED! => {"changed": false, "failed": true, "msg": "no service or tool found for: ntp"}

NO MORE HOSTS LEFT *************************************************************
	to retry, use: --limit @/tmp/playbook.retry

PLAY RECAP *********************************************************************
192.168.61.11              : ok=5    changed=0    unreachable=0    failed=1
192.168.61.12              : ok=5    changed=0    unreachable=0    failed=1
localhost                  : ok=5    changed=0    unreachable=0    failed=1

Exercise : There was a intentional error introduced in the code. Identify the error from the log message above, correct it and run the playbook again. This time you should run it only on the failed hosts by limiting with the retry file mentioned above (e.g. —limit @/tmp/playbook.retry )

Debugging Technique : Step By Step Execution

Ansible provides a way to execute tasks step by step, asking you whether to run or skip each task. This can be useful while debugging issues.

ansible-playbook playbook.yml --step

[Output]

root@control:/workspace/chap5# ansible-playbook playbook.yml --step                             

PLAY [Base Configurations for ALL hosts] ***************************************                
Perform task: TASK: setup (N)o/(y)es/(c)ontinue: y                                              


TASK [setup] *******************************************************************                
y                                                                                               
ok: [app1]                                                                                      
ok: [db]                                                                                        
ok: [app2]                                                                                      
ok: [lb]                                                                                        
Perform task: TASK: create admin user (N)o/(y)es/(c)ontinue:                                    

TASK [create admin user] *******************************************************                
yok: [app2]                                                                                     
ok: [app1]                                                                                      
ok: [db]                                                                                        
ok: [lb]                                                                                        

Perform task: TASK: install tree (N)o/(y)es/(c)ontinue: y                                       

TASK [install tree] ************************************************************                
ok: [app2]                                                                                      
ok: [lb]                                                                                        
ok: [app1]                                                                                      
ok: [db]                                                  

Adding Additional Play

Problem Statement:

You have to a new play to existing playbook which will

  • create a app user with uid 5003
  • install git
  • on all app servers in the inventory

Lets add a second play specific to app servers. Add the following block of code in playbook.yml file and save

- name: App Server Configurations
  hosts: app
  become: true
  tasks:
    - name: create app user
      user: name=app state=present uid=5003

    - name: install git
      yum:  name=git  state=present

Run the playbook again…

ansible-playbook playbook.yml
.......

PLAY [App Server Configurations] ***********************************************

TASK [setup] *******************************************************************
ok: [192.168.61.13]
ok: [192.168.61.12]

TASK [create app user] *********************************************************
changed: [192.168.61.12]
changed: [192.168.61.13]

TASK [install git] *************************************************************
ok: [192.168.61.13]
ok: [192.168.61.12]

PLAY RECAP *********************************************************************
192.168.61.11              : ok=6    changed=0    unreachable=0    failed=0
192.168.61.12              : ok=9    changed=1    unreachable=0    failed=0
192.168.61.13              : ok=9    changed=1    unreachable=0    failed=0
192.168.61.14              : ok=6    changed=0    unreachable=0    failed=0
localhost                  : ok=6    changed=0    unreachable=0    failed=0

Limiting the execution to a particular group

Now run the following command to restrict the playbook execution to app servers

ansible-playbook playbook.yml --limit app

This will give us the following output, plays will be executed only on app servers…


PLAY [Base Configurations for ALL hosts] ***************************************

TASK [setup] *******************************************************************
ok: [192.168.61.13]
ok: [192.168.61.12]

.........

TASK [start ntp service] *******************************************************
ok: [192.168.61.12]
ok: [192.168.61.13]

PLAY [App Server Configurations] ***********************************************

........

TASK [install git] *************************************************************
ok: [192.168.61.12]
ok: [192.168.61.13]

PLAY RECAP *********************************************************************
192.168.61.12              : ok=9    changed=0    unreachable=0    failed=0
192.168.61.13              : ok=9    changed=0    unreachable=0    failed=0

Exercise:

Exercise1: Create a Playbook with the following specifications,

  • It should apply only on local host (ansible host)
  • Should use become method
  • Should create a user called webadmin with shell as «/bin/sh»
  • Should install and start nginx service
  • Should deploy a sample html app into the default web root directory of nginx using ansible’s git module.
    • Source repo: https://github.com/schoolofdevops/html-sample-app
    • Deploy Path : /usr/share/nginx/html/app
  • Once deployed, validate the site by visting http://CONTROL_HOST_IP/app

Exercise 2: Disable Facts Gathering

  • Run ansible playbook and observe the output
  • Add the following configuration parameter to ansible.cfg
  • Launch ansible playbook run again, observe the output and compare it with the previous run.

About Playbooks¶

Playbooks are a completely different way to use ansible than in adhoc task execution mode, and are
particularly powerful.

Simply put, playbooks are the basis for a really simple configuration management and multi-machine deployment system,
unlike any that already exist, and one that is very well suited to deploying complex applications.

Playbooks can declare configurations, but they can also orchestrate steps of
any manual ordered process, even as different steps must bounce back and forth
between sets of machines in particular orders. They can launch tasks
synchronously or asynchronously.

While you might run the main /usr/bin/ansible program for ad-hoc
tasks, playbooks are more likely to be kept in source control and used
to push out your configuration or assure the configurations of your
remote systems are in spec.

There are also some full sets of playbooks illustrating a lot of these techniques in the
ansible-examples repository. We’d recommend
looking at these in another tab as you go along.

There are also many jumping off points after you learn playbooks, so hop back to the documentation
index after you’re done with this section.

Playbook Language Example¶

Playbooks are expressed in YAML format (see YAML Syntax) and have a minimum of syntax, which intentionally
tries to not be a programming language or script, but rather a model of a configuration or a process.

Each playbook is composed of one or more ‘plays’ in a list.

The goal of a play is to map a group of hosts to some well defined roles, represented by
things ansible calls tasks. At a basic level, a task is nothing more than a call
to an ansible module (see About Modules).

By composing a playbook of multiple ‘plays’, it is possible to
orchestrate multi-machine deployments, running certain steps on all
machines in the webservers group, then certain steps on the database
server group, then more commands back on the webservers group, etc.

“plays” are more or less a sports analogy. You can have quite a lot of plays that affect your systems
to do different things. It’s not as if you were just defining one particular state or model, and you
can run different plays at different times.

For starters, here’s a playbook that contains just one play:

---
- hosts: webservers
  vars:
    http_port: 80
    max_clients: 200
  remote_user: root
  tasks:
  - name: ensure apache is at the latest version
    yum: name=httpd state=latest
  - name: write the apache config file
    template: src=/srv/httpd.j2 dest=/etc/httpd.conf
    notify:
    - restart apache
  - name: ensure apache is running (and enable it at boot)
    service: name=httpd state=started enabled=yes
  handlers:
    - name: restart apache
      service: name=httpd state=restarted

When working with tasks that have really long parameters or modules that take
many parameters, you can break tasks items over multiple lines to improve the
structure. Below is another version of the above example but using
YAML dictionaries to supply the modules with their key=value arguments.:

---
- hosts: webservers
  vars:
    http_port: 80
    max_clients: 200
  remote_user: root
  tasks:
  - name: ensure apache is at the latest version
    yum:
      name: httpd
      state: latest
  - name: write the apache config file
    template:
      src: /srv/httpd.j2
      dest: /etc/httpd.conf
    notify:
    - restart apache
  - name: ensure apache is running
    service:
      name: httpd
      state: started
  handlers:
    - name: restart apache
      service:
        name: httpd
        state: restarted

Playbooks can contain multiple plays. You may have a playbook that targets first
the web servers, and then the database servers. For example:

---
- hosts: webservers
  remote_user: root

  tasks:
  - name: ensure apache is at the latest version
    yum: name=httpd state=latest
  - name: write the apache config file
    template: src=/srv/httpd.j2 dest=/etc/httpd.conf

- hosts: databases
  remote_user: root

  tasks:
  - name: ensure postgresql is at the latest version
    yum: name=postgresql state=latest
  - name: ensure that postgresql is started
    service: name=postgresql state=started

You can use this method to switch between the host group you’re targeting,
the username logging into the remote servers, whether to sudo or not, and so
forth. Plays, like tasks, run in the order specified in the playbook: top to
bottom.

Below, we’ll break down what the various features of the playbook language are.

Basics¶

Hosts and Users¶

For each play in a playbook, you get to choose which machines in your infrastructure
to target and what remote user to complete the steps (called tasks) as.

The hosts line is a list of one or more groups or host patterns,
separated by colons, as described in the Patterns
documentation. The remote_user is just the name of the user account:

---
- hosts: webservers
  remote_user: root

Note

The remote_user parameter was formerly called just user. It was renamed in Ansible 1.4 to make it more distinguishable from the user module (used to create users on remote systems).

Remote users can also be defined per task:

---
- hosts: webservers
  remote_user: root
  tasks:
    - name: test connection
      ping:
      remote_user: yourname

Note

The remote_user parameter for tasks was added in 1.4.

Support for running things as another user is also available (see Become (Privilege Escalation)):

---
- hosts: webservers
  remote_user: yourname
  become: yes

You can also use become on a particular task instead of the whole play:

---
- hosts: webservers
  remote_user: yourname
  tasks:
    - service: name=nginx state=started
      become: yes
      become_method: sudo

Note

The become syntax deprecates the old sudo/su specific syntax beginning in 1.9.

You can also login as you, and then become a user different than root:

---
- hosts: webservers
  remote_user: yourname
  become: yes
  become_user: postgres

You can also use other privilege escalation methods, like su:

---
- hosts: webservers
  remote_user: yourname
  become: yes
  become_method: su

If you need to specify a password to sudo, run ansible-playbook with --ask-become-pass or
when using the old sudo syntax --ask-sudo-pass (-K). If you run a become playbook and the
playbook seems to hang, it’s probably stuck at the privilege escalation prompt.
Just Control-C to kill it and run it again adding the appropriate password.

Important

When using become_user to a user other than root, the module
arguments are briefly written into a random tempfile in /tmp.
These are deleted immediately after the command is executed. This
only occurs when changing privileges from a user like ‘bob’ to ‘timmy’,
not when going from ‘bob’ to ‘root’, or logging in directly as ‘bob’ or
‘root’. If it concerns you that this data is briefly readable
(not writable), avoid transferring unencrypted passwords with
become_user set. In other cases, /tmp is not used and this does
not come into play. Ansible also takes care to not log password
parameters.

New in version 2.4.

You can also control the order in which hosts are run. The default is to follow the order supplied by the inventory:

- hosts: all
  order: sorted
  gather_facts: False
  tasks:
    - debug: var=inventory_hostname

Possible values for order are:

inventory:
The default. The order is ‘as provided’ by the inventory
reverse_inventory:
As the name implies, this reverses the order ‘as provided’ by the inventory
sorted:
Hosts are alphabetically sorted by name
reverse_sorted:
Hosts are sorted by name in reverse alphabetical order
shuffle:
Hosts are randomly ordered each run

Tasks list¶

Each play contains a list of tasks. Tasks are executed in order, one
at a time, against all machines matched by the host pattern,
before moving on to the next task. It is important to understand that, within a play,
all hosts are going to get the same task directives. It is the purpose of a play to map
a selection of hosts to tasks.

When running the playbook, which runs top to bottom, hosts with failed tasks are
taken out of the rotation for the entire playbook. If things fail, simply correct the playbook file and rerun.

The goal of each task is to execute a module, with very specific arguments.
Variables, as mentioned above, can be used in arguments to modules.

Modules should be idempotent, that is, running a module multiple times
in a sequence should have the same effect as running it just once. One
way to achieve idempotency is to have a module check whether its desired
final state has already been achieved, and if that state has been achieved,
to exit without performing any actions. If all the modules a playbook uses
are idempotent, then the playbook itself is likely to be idempotent, so
re-running the playbook should be safe.

The command and shell modules will typically rerun the same command again,
which is totally ok if the command is something like
chmod or setsebool, etc. Though there is a creates flag available which can
be used to make these modules also idempotent.

Every task should have a name, which is included in the output from
running the playbook. This is human readable output, and so it is
useful to provide good descriptions of each task step. If the name
is not provided though, the string fed to ‘action’ will be used for
output.

Tasks can be declared using the legacy action: module options format, but
it is recommended that you use the more conventional module: options format.
This recommended format is used throughout the documentation, but you may
encounter the older format in some playbooks.

Here is what a basic task looks like. As with most modules,
the service module takes key=value arguments:

tasks:
  - name: make sure apache is running
    service: name=httpd state=started

The command and shell modules are the only modules that just take a list
of arguments and don’t use the key=value form. This makes
them work as simply as you would expect:

tasks:
  - name: enable selinux
    command: /sbin/setenforce 1

The command and shell module care about return codes, so if you have a command
whose successful exit code is not zero, you may wish to do this:

tasks:
  - name: run this command and ignore the result
    shell: /usr/bin/somecommand || /bin/true

Or this:

tasks:
  - name: run this command and ignore the result
    shell: /usr/bin/somecommand
    ignore_errors: True

If the action line is getting too long for comfort you can break it on
a space and indent any continuation lines:

tasks:
  - name: Copy ansible inventory file to client
    copy: src=/etc/ansible/hosts dest=/etc/ansible/hosts
            owner=root group=root mode=0644

Variables can be used in action lines. Suppose you defined
a variable called vhost in the vars section, you could do this:

tasks:
  - name: create a virtual host file for {{ vhost }}
    template: src=somefile.j2 dest=/etc/httpd/conf.d/{{ vhost }}

Those same variables are usable in templates, which we’ll get to later.

Now in a very basic playbook all the tasks will be listed directly in that play, though it will usually
make more sense to break up tasks as described in Creating Reusable Playbooks.

Action Shorthand¶

New in version 0.8.

Ansible prefers listing modules like this in 0.8 and later:

template: src=templates/foo.j2 dest=/etc/foo.conf

You will notice in earlier versions, this was only available as:

action: template src=templates/foo.j2 dest=/etc/foo.conf

The old form continues to work in newer versions without any plan of deprecation.

Handlers: Running Operations On Change¶

As we’ve mentioned, modules should be idempotent and can relay when
they have made a change on the remote system. Playbooks recognize this and
have a basic event system that can be used to respond to change.

These ‘notify’ actions are triggered at the end of each block of tasks in a play, and will only be
triggered once even if notified by multiple different tasks.

For instance, multiple resources may indicate
that apache needs to be restarted because they have changed a config file,
but apache will only be bounced once to avoid unnecessary restarts.

Here’s an example of restarting two services when the contents of a file
change, but only if the file changes:

- name: template configuration file
  template: src=template.j2 dest=/etc/foo.conf
  notify:
     - restart memcached
     - restart apache

The things listed in the notify section of a task are called
handlers.

Handlers are lists of tasks, not really any different from regular
tasks, that are referenced by a globally unique name, and are notified
by notifiers. If nothing notifies a handler, it will not
run. Regardless of how many tasks notify a handler, it will run only
once, after all of the tasks complete in a particular play.

Here’s an example handlers section:

handlers:
    - name: restart memcached
      service: name=memcached state=restarted
    - name: restart apache
      service: name=apache state=restarted

As of Ansible 2.2, handlers can also “listen” to generic topics, and tasks can notify those topics as follows:

handlers:
    - name: restart memcached
      service: name=memcached state=restarted
      listen: "restart web services"
    - name: restart apache
      service: name=apache state=restarted
      listen: "restart web services"

tasks:
    - name: restart everything
      command: echo "this task will restart the web services"
      notify: "restart web services"

This use makes it much easier to trigger multiple handlers. It also decouples handlers from their names,
making it easier to share handlers among playbooks and roles (especially when using 3rd party roles from
a shared source like Galaxy).

Note

  • Notify handlers are always run in the same order they are defined, not in the order listed in the notify-statement. This is also the case for handlers using listen.
  • Handler names and listen topics live in a global namespace.
  • If two handler tasks have the same name, only one will run.
    *
  • You cannot notify a handler that is defined inside of an include. As of Ansible 2.1, this does work, however the include must be static.

Roles are described later on, but it’s worthwhile to point out that:

  • handlers notified within pre_tasks, tasks, and post_tasks sections are automatically flushed in the end of section where they were notified;
  • handlers notified within roles section are automatically flushed in the end of tasks section, but before any tasks handlers.

If you ever want to flush all the handler commands immediately though, in 1.2 and later, you can:

tasks:
   - shell: some tasks go here
   - meta: flush_handlers
   - shell: some other tasks

In the above example any queued up handlers would be processed early when the meta
statement was reached. This is a bit of a niche case but can come in handy from
time to time.

Executing A Playbook¶

Now that you’ve learned playbook syntax, how do you run a playbook? It’s simple.
Let’s run a playbook using a parallelism level of 10:

ansible-playbook playbook.yml -f 10

Ansible-Pull¶

Should you want to invert the architecture of Ansible, so that nodes check in to a central location, instead
of pushing configuration out to them, you can.

The ansible-pull is a small script that will checkout a repo of configuration instructions from git, and then
run ansible-playbook against that content.

Assuming you load balance your checkout location, ansible-pull scales essentially infinitely.

Run ansible-pull --help for details.

There’s also a clever playbook available to configure ansible-pull via a crontab from push mode.

Tips and Tricks¶

To check the syntax of a playbook, use ansible-playbook with the --syntax-check flag. This will run the
playbook file through the parser to ensure its included files, roles, etc. have no syntax problems.

Look at the bottom of the playbook execution for a summary of the nodes that were targeted
and how they performed. General failures and fatal “unreachable” communication attempts are
kept separate in the counts.

If you ever want to see detailed output from successful modules as well as unsuccessful ones,
use the --verbose flag. This is available in Ansible 0.5 and later.

Ansible playbook output is vastly upgraded if the cowsay
package is installed. Try it!

To see what hosts would be affected by a playbook before you run it, you
can do this:

ansible-playbook playbook.yml --list-hosts

В инструкции описано применение и работа с Ansible Playbook, а также кратко рассмотрена их структура.

Вы можете прочитать о том, как установить Ansible на Ubuntu 18.04 в другой нашей инструкции.

Что такое Ansible Playbooks?

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

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

Playbook — это YAML-файл, который обычно имеет следующую структуру:

---
- hosts: [target hosts]
  remote_user: [yourname]
  tasks:
    - [task 1]
    - [task 2]

Например, следующий playbook будет входить на все серверы группы marketingservers и обеспечивать запуск веб-сервера Apache:

---
- hosts: [marketingservers]
  remote_user: webadmin
  tasks:
    - name: Ensure the Apache daemon has started
      service: name=httpd state=started
      become: yes
      become_method: sudo

В плейбуке выше приведен пример задания (task):

tasks:
  - name: Ensure the Apache daemon has started
    service: name=httpd state=started
    become: yes
    become_method: sudo

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

Запуск Ansible Playbook

Запустить готовый плейбук можно используя следующую команду:

ansible-playbook playbook.yml

Например:

ansible-playbook nginx.yml

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

ansible-playbook -l host_subset playbook.yml

Например:

ansible-playbook -l host3 nginx.yml

Регистрация результатов

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

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

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

Например, укажем playbook загрузить файл index.php, если он существует. Если эта задача не выполнится, то начнется загрузка файла index.html:

---
- hosts: droplets
  tasks:
    - name: Installs nginx web server
      apt: pkg=nginx state=installed update_cache=true
      notify:
        - start nginx

    - name: Upload default index.php for host
      copy: src=static_files/index.php dest=/usr/share/nginx/www/ mode=0644
      register: php
      ignore_errors: True

    - name: Remove index.html for host
      command: rm /usr/share/nginx/www/index.html
      when: php|success

    - name: Upload default index.html for host
      copy: src=static_files/index.html dest=/usr/share/nginx/www/ mode=0644
      when: php|failed

  handlers:
    - name: start nginx
      service: name=nginx state=started

Этот сценарий пытается загрузить файл PHP на хост. Ansible регистрирует успех операции в переменной с именем php. Если эта операция прошла успешно, следующая задача — удалить файл index.html. Если операция завершилась неудачно, будет загружен файл index.html.

220140
Минск
ул. Домбровская, д. 9

+375 (173) 88-72-49

700
300

ООО «ИТГЛОБАЛКОМ БЕЛ»

220140
Минск
ул. Домбровская, д. 9

+375 (173) 88-72-49

700
300

ООО «ИТГЛОБАЛКОМ БЕЛ»

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

Содержание

  • Что такое плейбук Ansible?

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

  • Запуск плейбука

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

  • Чувствительные данные

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

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

  • Циклы

  • Советы по плейбукам Ansible

Что такое плейбук Ansible?

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

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

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

Плейбук состоит из сценариев (play), которые выполняются в заданном порядке. Сценарий представляет собой список задач для определённой группы хостов.

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

Плейбуки пишутся в 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.

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

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

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

Запуск плейбука

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

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

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

В выходных данных мы видим имена сценариев, задачу Gathering Facts (сбор фактов), другие задачи сценария, а в конце Play Recap — сводку по выполнению сценария. Мы не определили группу хостов databases, поэтому второй сценарий плейбука был пропущен.

С помощью флага –limit мы можем ограничить выполнение плейбука несколькими хостами. Например:

ansible-playbook example-simple-playbook.yml --limit host1

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

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

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

Для переменных на уровне группы и хоста нужны каталоги group_vars и host_vars. Например, чтобы определить переменные для группы databases, создаём файл 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']

Чтобы ссылаться на вложенные переменные, используем квадратные скобки или точку. Например, нам требуется значение 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 Vault. Хранить такие данные обычным текстом небезопасно, поэтому мы можем зашифровывать и расшифровывать их с помощью команды ansible-vault.

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

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

В любом случае, нужно продумать стратегию управления паролями для Vault. Зашифрованное содержимое мы помечаем тегом !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:

Поскольку мы опустили <password_source>, мы вручную ввели пароль от 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 (перезапустить Apache) будет выполняться, только если в конфигурации что-то изменилось. Обработчики можно считать неактивными задачами, которые ждут уведомления.

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

Это поведение можно изменить с помощью задачи 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

Выходные данные этой задачи, которая использует loop и 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 по циклам.

Советы по плейбукам Ansible

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

1. Чем проще, тем лучше.

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

2. Размещайте артефакты Ansible в системе управления версиями.

Плейбук рекомендуется хранить в git или другой системе управления версиями.

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

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

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

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

5. Всегда явно упоминайте состояние задачи.

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

6. Написание комментариев при необходимости.

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

Заключение

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

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

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

А если хотите глубже изучить Ansible, приходите на курс Ansible: Infrastructure as Code

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

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

Коротко о программе:

— Узнаете как работать с переменными, как писать плейбуки и роли;

— Развернете LEMP стек, PostgreSQL и Mongo кластеры, задеплоите Flask приложение;

— Напишите свой модуль для Ansible;

— Настроите IaC в Gitlab;

— Разберетесь с работой с облаками и enterprise решениями.

Посмотреть подробную программу и записаться: https://slurm.io/ansible

Понравилась статья? Поделить с друзьями:
  • Как написать paragraph
  • Как написать osint бота
  • Как написать opinion essay на английском
  • Как написать nvidia письмо
  • Как написать num