Jenkins + Ansible: автоматический прогон playbooks после каждого коммита
Настраиваем Jenkins для запуска Ansible playbooks при каждом коммите в Git. Первый реальный шаг от «руками применили» к непрерывной доставке конфигураций.
Jenkins утверждается как де-факто стандарт CI-сервера для автоматизации сборок и тестов в 2013-2014 году
Две недели назад описывали нашу Git+GitFlow-схему для Ansible playbooks. В конце того поста честно написали: применение изменений по-прежнему ручное - инженер берёт актуальную ветку, запускает ansible-playbook сам. Теперь с этим разобрались.
Почему именно Jenkins
Короткий ответ: потому что он уже был. На одном из проектов стоял Jenkins для сборки Java-приложений, и мы знали как с ним работать. Альтернативы рассматривали - TeamCity, Bamboo, что-то на базе Buildbot, - но аргументов переходить на что-то новое не нашлось. Jenkins как Hudson-форк существует с 2011 года, плагинов накоплено огромное количество, комьюнити активное, документации достаточно.
Для наших целей - запускать ansible-playbook по триггеру от Git - оверинжиниринга не нужно. Jenkins справляется.
Что настраивали
Схема в итоге получилась такая:
- SCM Polling - Jenkins опрашивает Git-репозиторий каждые 5 минут. Да, не хук, а поллинг - у нас не было прямого доступа к GitLab-серверу клиента для настройки webhook. Для начала работает.
- Условие запуска - задание триггерится только при изменениях в ветке
master. Коммиты вdevelopи feature-ветки запуск не инициируют. - Шаг сборки - Execute Shell, внутри просто
ansible-playbook -i inventory/production site.yml. Jenkins запускает это от выделенного пользователяjenkins, у которого заранее прописан SSH-ключ для доступа к управляемым хостам. - Уведомления - письмо на почту команды при провале. При успехе не шлём - иначе ящик засорится.
Дополнительно поставили плагин AnsiColor, чтобы вывод Ansible в консоли Jenkins был цветным и читаемым. Без него разбирать длинный лог задач тяжело.
Грабли, которые поймали сразу
Первое - окружение. Когда запускаешь ansible-playbook руками под своим пользователем, в PATH всё нужное есть, переменные окружения на месте. Под jenkins - нет. Пришлось явно прописывать полные пути к ansible-playbook и python, а также добавить нужные переменные в конфигурацию задания.
Второе - ansible-vault. Зашифрованные переменные с секретами требуют пароль vault при запуске. Руками вводишь в терминале - и не думаешь об этом. Jenkins же интерактивного ввода не предполагает. Решили через --vault-password-file: файл с паролем лежит на Jenkins-сервере в директории, недоступной другим пользователям, и передаётся в плейбук как параметр. Не самое изящное, но рабочее.
Третье - параллельные запуски. Если за 5 минут прилетело два коммита, Jenkins может попробовать запустить два экземпляра задания одновременно. Ansible при этом не падает, но два параллельных ansible-playbook на одних и тех же хостах - это лишний шум в логах и потенциально непредсказуемый порядок применения. Ограничили количество одновременных исполнителей для задания до одного - проблема ушла.
Что изменилось в процессе
До Jenkins цикл выглядел так: смержили в master -> кто-то из инженеров заметил -> дошли руки -> запустили плейбук. Между мержем и реальным применением могло пройти несколько часов, а то и до следующего утра.
Теперь в течение пяти минут после мержа Jenkins сам запускает применение и либо пишет об успехе в лог, либо шлёт письмо об ошибке. Репозиторий и реальное состояние инфраструктуры перестали расходиться.
Неожиданный побочный эффект: инженеры стали аккуратнее с тем, что мержат в master. Когда знаешь, что через несколько минут playbook полетит на продакшн-серверы - лишний раз перепроверишь что в коммите.
Что пока не закрыто
Staging-окружение. Сейчас плейбук идёт сразу на продакшн-хосты. Правильнее сначала применить к staging, убедиться что всё в порядке, и только потом - на продакшн. Для этого нужен отдельный этап в пайплайне с промежуточным одобрением или хотя бы задержкой. В Jenkins это делается через параметризованные задания или плагин Build Pipeline - разбираемся.
Тестирование самих playbooks до применения тоже пока ручное. Хотелось бы прогонять ansible-playbook --check на отдельной задаче как первый шаг, и только при чистом прогоне идти дальше. Технически несложно, просто не дошли руки.
Насколько это CI
По-честному - это половина CI. Continuous Integration в классическом смысле подразумевает ещё и автоматические тесты, которые подтверждают что изменение корректно. У нас автоматической проверки что инфраструктура после применения в нужном состоянии - нет. Jenkins просто применяет изменения автоматически.
Но даже в таком виде это ощутимо лучше чем было. Клиенты на сопровождении получают более предсказуемый процесс: изменения применяются по факту мержа, а не когда у инженера дошли руки.
Следующий шаг - добавить проверки после применения: убедиться что сервисы поднялись, порты открыты, мониторинг видит хосты. Это уже ближе к настоящему CD. Но об этом в другой раз.