DevOps. Ansible и Docker

Цель данной статьи немного рассказать о том, что из себя представляет Ansible и Docker, привести примеры, когда их можно использовать и как можно эффективно, объединив две технологии, упростить жизнь QA специалистов.
Термины и определения
Прежде чем приступать к основной части, считаю необходимым дать определение используемых терминов.
Ansible — система управления конфигурациями, написанная на Python, с использованием декларативного языка разметки для описания конфигураций. Используется для автоматизации настройки и развертывания программного обеспечения.
Docker — программное обеспечение для автоматизации развёртывания и управления приложениями в среде виртуализации на уровне операционной системы.
SSH (англ. Secure Shell) — сетевой протокол прикладного уровня, позволяющий производить удалённое управление операционной системой и туннелирование TCP-соединений (например, для передачи файлов).
Power Shell — это язык сценариев от компании Microsoft, можно сказать что это более продвинутый вариант командной строки. Может использоваться для управления операционной системой Windows и ее компонентами, а также создания автоматизированных сценариев для администрирования системы.
Образ — самостоятельная файловая система, из него формируется контейнер.
Контейнер — запущенный процесс операционной системы в изолированном окружении с подключенной файловой системой из образа.
Общие сведения о Ansible
Итак, Ansible — это очень гибкий и легкий инструмент для написания сценариев автоматизации любой сложности, обычно используется для управления Linux-узлами, но целевой системой может выступать и Windows, а также MacOS. Кроме того поддерживается работа и с сетевыми устройствами, на которых установлен Python версии 2.4 и выше по SSH или PowerShell соединению. Хотя при помощи Ansible можно управлять Windows системами, но в качестве Ansible хоста может выступать только Unix-подобная система (Linux, MacOS, FreeBSD и т.д.).
Важно понимать, что целевых систем может быть множество, и на них нет необходимости ставить Ansible, достаточно установить только на 1 машине для управления остальными системами. Вся работа происходит с использованием SSH/PowerShell соединения.
Какие задачи можно решить, используя Ansible:
- установка/удаление и конфигурирование ПО;
- создание/удаление пользователей;
- контроль пользовательских паролей/ключей;
- создание/удаление контейнеров/виртуальных машин;
- запуск различных скриптов/тестов.
Структура проекта Ansible
В общем виде проект Ansible может включать в себя:
- переменные;
- playbook (сценарии);
- роли;
- inventory (списки групп хостов).
Переменные
Переменные используются для хранения значений, которые могут применяться в playbook.
Переменные можно определять:
- в специальных файлах для группы/устройства;
- в inventory;
- в playbook;
- в ролях, которые затем используются;
- переменные, передаваемые при вызове playbook.
Специальные файлы с переменными могут хранится в 2 директориях:
- group_vars/ — групповые переменные(общие). При необходимости можно создать файл all, содержащий переменные, относящиеся ко всем группам.
- host_vars/ — переменные отдельных хостов (частные). В них имена файлов должны совпадать с именем или адресом хоста.
Названия файлов в директориях не принципиальны, главное, чтобы имя директории совпадало с именами в inventory.
Файлы переменных должны быть в формате YAML.
Для каждой группы или хоста можно не создавать отдельную директорию, можно просто создать файл и записать в нем все необходимые переменные.
Приоритеты переменных (в порядке возрастания):
- установленные в ролях;
- заданные в inventory;
- групповые переменные (общие);
- групповые переменные (частные);
- переданные из playbook;
- передаваемые из команды (-e / —extra-vars).
Playbooks
Для выполнения сценариев используются playbooks.
Playbook представляет собой *.yml файл, в котором написано, что необходимо выполнить при вызове данного сценария.
Playbook минимально должен иметь такую структуру:
- hosts — целевая группа хостов, можно задавать исключения по маске или указывать несколько групп через двоеточие;
- tasks или roles — выполняемое действие или роль.
Также можно указать:
- become_user — пользователь, которым будет выполняться (пользователя sudo);
- remote_user — пользователь для ssh-соединения;
- include — дополнительно вызываемый сценарий, например валидация параметров приложения.
Roles
Роль представляет собой структурированный playbook, содержащий набор (как и минимум) тасков (task), и дополнительно — обработчиков событий (handler), переменных (defaults), файлов (files), шаблонов (templates), а также описание и зависимости (meta).
Пример структуры роли для установки couchbase на проекте:
├───couchbase
│ ├───create_views
│ │ └───tasks
│ ├───loading
│ │ └───tasks
│ ├───loading_light
│ │ └───tasks
│ ├───load_to_cache
│ │ └───tasks
│ └───options
│ └───tasks
Из текущей структуры мы видим, что при вызове данной роли будет выполнено:
- Будут созданы view.
- Загружены данные для работы.
- Загружены настройки для основного приложение или light поставке.
- Загружены дополнительные параметры.
Inventory
Можно сказать что Inventory это файл, в котором описываются устройства, к которым будет подключаться Ansible. В файле Inventory устройства могут указываться, используя IP-адреса или имена. Устройства могут быть указаны по одному или разбиты на группы. Файл описывается в формате INI.
Пример файла:

Название, которое указано в квадратных скобках, — это название группы. В данном случае созданы три группы устройств: service, service_light, service_backend. При этом, так как для них указано ansible_connection=local, все операции будут выполнены на текущей локальной машине, а параметр ansible_host будет проигнорирован. Нужно быть очень внимательным!
[simple:children] — показывает, что было выполнено объединение нескольких групп в одну.
При работе с Inventory неплохим правилом будет создание каждый раз нового файла, для того чтобы можно было вернуться к предыдущему состоянию файла при необходимости.
Запуск готового playbook осуществляется командой:
$ ansible-playbook simple-service.yml -i inventory/localhost -u имя_пользователя -k -vv
- -vv — параметр, который позволяет отображать большее количество логов в сравнении с -v,
- -i путь к inventory файлу.
Общие сведения о Docker
Самая главная и полезная особенность Docker — приложение можно поместить со всеми зависимостями в отдельный модуль, который будет самодостаточным и не будет перегружен ненужными компонентами, как обычно происходит с виртуальными машинами, а значит в большинстве случаев будет обеспечивать большую производительность и занимать меньше места.
После появления Docker упростилась работа системных администраторов и DevOps специалистов, поскольку теперь вместо того, чтобы думать как запустить приложение, нужно думать, где его запустить.
Docker может использоваться на всех типах ОС, но в Windows-системах могут появиться проблемы при установке и использовании. Подробнее про установку Docker можно почитать Get started with Docker.
Использование Docker на проекте
На проекте мы пришли к использованию Docker в качестве инструмента для подготовки тестового окружения, которое требует минимальной настройки и практически всегда готово к использованию.
Конечно были и подводные камни. После перехода (повышения версии) к другой версии контейнера couchbase начались проблемы: работающий контейнер мог в неожиданный момент перестать отвечать на запросы, зависнуть и т. п. А из-за того, что выбор готовых контейнеров ограничен, не было времени на подготовку своего контейнера — приняли решение использовать локальную установку приложения.
От всех проблем сразу нам избавиться не удалось, но всё решалось быстрее при помощи нескольких скриптов и дополнительного контроля со стороны QA не чаще одного раза в несколько дней. Чуть позже был собран уже собственный контейнер, но определенные проблемы в нём остались, что связано с текущей минорной версией приложения, которую на тот момент мы не могли сменить.
В качестве интересного примера запуска приведу пример скрипта добавления контейнера Apache Tomcat:

Позволю пояснить некоторые моменты:
- В первой строке, как видно из комментариев, мы проверяем запущен ли контейнер Apache Tomcat с именем “ps-tomcat”, для чего получаем список запущенных контейнеров и фильтруем по слову “ps-tomcat” и сохраняем в переменную.
- Далее, если значение переменной не равно ps-tomcat (можно было проверять и на пустое значение конечно же), мы считаем, что контейнер с Apache Tomcat не запущен и необходимо его запустить; если же равно — просто выводим уведомление и завершаем работу скрипта.
- Если необходимо выполнить установку, то скачиваем контейнер, запускаем его и копируем директории с логами, приложениями и конфиг файлами. Причина, по которой выполняется такое действие — мы используем кастомизированный образ Apache Tomcat, поэтому часть настроек в Apache Tomcat, в том числе и безопасности, уникальны.
При копировании директорий с настройками отпадает необходимость хранения файлов настроек в отдельной директории, проверять отсутствие изменений в текущей версии и, банально, можно не думать, что случайно будет удалена актуальная версия. - Удаляем контейнер и стартуем снова, связывая с локальными папками. Это нужно для того, чтобы мы могли просто добавив .war и .properties файлы в соответствующие директории сразу же запустить установленные приложения. Отмечу также что аргументами вида -p 9099:9099 выполняется пробрасывание портов, чтобы обращаться к приложению можно было извне.
- Также можно заметить, что везде используется выполнение команд от суперпользователя. Это связано с тем, что по умолчанию Docker работает через unix сокет. В целях безопасности сокет закрыт для пользователей, не входящих в группу docker. Исправить это можно, выполнив небольшой скрипт:

Какие же компоненты окружения мы вынесли в docker? Внимательный читатель заметит, что это был Apache Tomcat, а также RabbitMQ и ZooKeeper. Образы Couchbase выше версии 4.4.x, к сожалению, имеют те или иные проблемы, поэтому мы решили использовать локальную установку, но никто не мешает в будущем перенести Couch также в контейнер.
После вышеописанного может показаться, что Docker может быть идеальным решением для замены всего окружения, но всегда нужно взвешивать все за и против, иначе можно попасть в ситуацию: “Когда в руках молоток, все вокруг кажется гвоздями”.
Для чего, вероятно, может не подойти Docker? Для небольшого проекта, где временные затраты на подготовку и конфигурирование отдельного образа могут быть сравнимы с установкой необходимого окружения.
Можно ли было вынести наше приложение полностью в Docker? Конечно можно, но так как мы проверяем качество разрабатываемого приложения, делать это как минимум нецелесообразно из-за необходимости каждый раз собирать контейнер после добавления новой функциональности. Но готовый образ можно использовать для проведения интеграционного тестирования в составе с другими системами и продуктами.
Вместо итога
На проекте можно использовать Ansible в качестве автоинсталлятора продукта, в качестве помощника по установке и настройке Jenkins и любых других CI сред.
Самым лучшим применением этих двух технологий мне видится в тандеме, где Ansible — для первичной установки и настройки того, что должно быть вне контейнеров, а Docker используется для виртуализации конкретных приложений. Собственно к такому применению мы и пришли на проекте.
Полезные ссылки
- Ansible для сетевых инженеров — https://legacy.gitbook.com/book/natenka/ansible-dlya-setevih-inzhenerov/details
- 15 вещей, которые вы должны знать об Ansible — https://habr.com/post/306998/
- Контейнерно-ориентированное интеграционное тестирование — https://habr.com/company/redhatrussia/blog/420385/
- Шпаргалка по Docker — https://github.com/wsargent/docker-cheat-sheet