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 задание стало двухэтапным:
ansible-playbook -i inventory/production site.yml- применяем изменения как раньше.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-файлы есть только для основных ролей, примерно половины от общего числа. Остальное - постепенно добавляем по мере того, как трогаем соответствующие части инфраструктуры.