ADG Оставить заявку
Блог DevOps 5 мин чтения

Serverspec в Jenkins-пайплайне: тестируем инфраструктуру после деплоя Ansible

Добавляем Serverspec-тесты в Jenkins-пайплайн: после ansible-playbook проверяем порты, сервисы и конфиги на целевых хостах автоматически.

Контекст момента

Концепция Infrastructure as Code и автоматизированного тестирования инфраструктуры набирает обороты в DevOps-сообществе в 2014 году

В мартовском посте про Jenkins в конце честно признались: «следующий шаг - добавить проверки после применения, убедиться что сервисы поднялись, порты открыты». С тех пор прошло полтора месяца, и мы этот шаг наконец сделали. Правда, не без приключений.

Откуда вообще Serverspec

Идея тестировать инфраструктуру как код появилась не вчера, но инструментов для этого долгое время почти не было. Serverspec - относительно новый проект: написан на Ruby, построен поверх RSpec, позволяет описывать ожидаемое состояние сервера в виде тестов. Проверить что порт 443 слушает, что nginx запущен и enabled, что файл /etc/nginx/nginx.conf содержит нужную директиву - всё это пишется на читаемом DSL и прогоняется по SSH без установки агентов на целевые хосты.

Нашли его через доклад на конференции (онлайн-запись), где японский разработчик Mizzy Hamano объяснял зачем это нужно. Идея понравилась - особенно отсутствие агентов. Ставить что-то дополнительно на все управляемые хосты ради тестов не хотелось.

Что именно проверяем

Для начала сформулировали три категории проверок, которые имеют смысл сразу после применения Ansible-плейбука:

  • Порты и сокеты. Nginx слушает 80 и 443, SSH не уехал на нестандартный порт, PostgreSQL доступен на 5432 только на loopback. Примерно то, что проверяешь руками первым делом.
  • Состояние сервисов. Сервис запущен (running) и добавлен в автозагрузку (enabled). Ansible мог применить конфиг, но если в нём синтаксическая ошибка - сервис не поднимется. Без теста это обнаруживается только когда приходит алерт от мониторинга или звонит клиент.
  • Ключевые параметры конфигурации. Конкретные директивы в конфигах - не весь файл, а то, что Ansible должен был поставить. Например, что worker_processes действительно выставлен в нужное значение, что server_tokens off присутствует.

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

Как это устроено в пайплайне

В Jenkins задание стало двухэтапным:

  1. ansible-playbook -i inventory/production site.yml - применяем изменения как раньше.
  2. rspec spec/ - прогоняем Serverspec-тесты против тех же хостов.

Второй шаг стартует только при успехе первого. При падении тестов задание в Jenkins отмечается как failed, письмо уходит команде - так же как при падении самого плейбука.

Технически Serverspec работает через SSH с Jenkins-сервера прямо на продакшн-хосты. Тот же ключ, тот же пользователь jenkins, что и для запуска Ansible. Дополнительных прав не нужно - тесты только читают состояние, ничего не меняют.

Для разных ролей сделали отдельные наборы тестов в директории spec/: web_server_spec.rb, db_server_spec.rb, base_spec.rb. base_spec.rb прогоняется на всех хостах - там проверки общих вещей вроде SSH-конфига и часового пояса.

Где пришлось повозиться

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

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

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

Что это меняет по-настоящему

До добавления тестов пайплайн давал гарантию одного вида: «Ansible отработал без ошибок». Это не то же самое, что «сервис работает корректно». Ansible может применить конфиг и закончить с нулевым кодом возврата, а сервис при этом не стартует - если конфиг синтаксически корректный, но логически неправильный.

Теперь пайплайн даёт более сильную гарантию: после применения мы проверяем реальное поведение системы. Это ближе к тому, что понимают под Continuous Delivery - не просто доставить конфиг, а убедиться что он работает.

Несколько раз тесты уже поймали то, что иначе дошло бы до клиентов или мониторинга. В одном случае обновление роли сдвинуло порт, на котором слушает внутренний сервис - тест упал, мы разобрались до того, как что-то реально сломалось.

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

Что не закрыто

Staging-окружение по-прежнему не автоматизировано - деплой идёт прямо в продакшн. Идеальный пайплайн должен сначала накатить на staging, прогнать тесты там, и только при успехе идти в продакшн. Технически Jenkins это позволяет через цепочку заданий или плагин Build Pipeline, но настроить пока не дошли руки.

Тестовое покрытие тоже неполное. Сейчас spec-файлы есть только для основных ролей, примерно половины от общего числа. Остальное - постепенно добавляем по мере того, как трогаем соответствующие части инфраструктуры.

Контакт

Нужна такая же инженерная работа?

Опишите задачу и контекст. Ответим в течение рабочего дня, при необходимости подпишем NDA.