Ansible и идемпотентность: провизионируем серверы без ручных чеклистов
Как Ansible playbooks заменили нам чеклисты при развёртывании серверов и почему принцип идемпотентности меняет логику работы с инфраструктурой.
Ansible набирает популярность как agentless-инструмент автоматизации конфигураций в 2013-2014 году
Полгода назад писали про Ansible как про интересную агентless-альтернативу Puppet. С тех пор инструмент прочно вошёл в рабочий процесс, и можно говорить не о первых впечатлениях, а о том, что реально изменилось в работе.
Главное изменение - мы перестали держать в голове или на бумаге список «что нужно сделать на новом сервере».
Откуда берутся чеклисты
Когда разворачиваешь новый сервер вручную, неизбежно появляется список действий. Сначала он в голове, потом - в вики, потом в текстовом файле с инструкцией. У нас был такой файл на два экрана: поставить пакеты, настроить syslog, выставить timezone, добавить пользователей, отключить root-логин по SSH, положить authorized_keys, настроить NTP, прописать в Zabbix, сделать ещё пятнадцать вещей.
Проблема не в том, что список длинный. Проблема в том, что его выполняют руками. Шаг пропускается, пункт интерпретируется по-разному, коллега делает чуть иначе - и через месяц два «одинаково настроенных» сервера на самом деле разные. Ловишь это, когда что-то ломается в продакшне и оказывается, что на одном сервере NTP настроен, а на другом - нет.
С Ansible чеклист стал playbook-ом, и теперь он не просто напоминание, а исполняемый код.
Что такое идемпотентность на практике
Слово громкое, но за ним стоит простая идея: запусти playbook хоть раз, хоть десять - результат будет один и тот же. Если NTP уже установлен - Ansible не будет его переустанавливать. Если строка в конфиге уже есть - не добавит дубль. Если пользователь существует - не попытается создать его заново.
Это не магия, это явно прописанная логика модулей. Модуль apt проверяет, установлен ли пакет, прежде чем что-то делать. Модуль template сравнивает текущий файл с тем, что должно быть, и трогает его только если есть разница. Модуль user проверяет наличие пользователя в системе.
На практике это означает: можно запустить playbook на сервере, который уже работает в продакшне, и он ничего не сломает - просто проверит что всё на месте и пройдёт дальше. Это принципиально меняет отношение к инструменту: он перестаёт быть «скриптом, который запускают один раз при установке» и становится описанием желаемого состояния.
Как выглядит провизионинг сейчас
Структура у нас сложилась примерно такая:
- base.yml - всё что должно быть на любом Linux-сервере: пользователи, SSH-конфиг, базовые пакеты, NTP, rsyslog, Zabbix-агент.
- web.yml - nginx, PHP-FPM, нужные расширения, конфиги с шаблонами под конкретный сервер.
- db.yml - PostgreSQL или MySQL, настройки производительности, резервное копирование.
Поднимается новый сервер для клиента на сопровождении - берём нужные playbooks, прописываем хост в инвентарь, запускаем. За двадцать-тридцать минут сервер в нужном состоянии. Не «примерно как должно быть», а точно как описано в YAML.
Важный момент с инвентарём: переменные под конкретного клиента или конкретный сервер живут отдельно от самих playbooks. Одни и те же роли применяются к разным серверам с разными параметрами - hostname, IP Zabbix-сервера, список пользователей, которым нужен доступ.
Где пока не всё гладко
Сложности есть с идемпотентностью в shell-модуле. Когда нужно выполнить команду, для которой нет готового Ansible-модуля, используешь shell: - и тут идемпотентность надо обеспечивать самому через creates: или when:. Если забыть - команда выполнится при каждом запуске, что иногда нежелательно.
Ещё непростая тема - порядок выполнения задач при зависимостях. Если сервис должен стартовать только после того как применился конфиг, это нужно явно прописывать через notify и handlers. Первые разы путаешься, потом привыкаешь.
И самое болезненное - тестирование. Playbook прогоняешь на тестовой машине, всё работает. Но тест - это разовое выполнение на чистой машине. Идемпотентность нужно проверять отдельно: запустить второй раз и убедиться что изменений нет. Не всегда делаем, потом ловим баги. Работаем над этим.
Что изменилось в голове
Главный сдвиг - инфраструктура перестала быть набором серверов, которые кто-то когда-то настроил. Она стала кодом, который лежит в git и описывает желаемое состояние. Можно посмотреть историю и понять что и когда изменили. Можно поднять новый сервер идентично существующему. Можно проверить что prod и staging настроены одинаково.
Puppet делает то же самое, но Ansible дешевле в старте - никакого master-сервера, никаких агентов. Для парков в десятки серверов это ощутимо.
Чеклист в вики всё ещё существует - но теперь это не инструкция «что делать руками», а документация к playbook-ам: почему именно эти пакеты, зачем этот параметр в конфиге. Разница небольшая, но принципиальная.
- Ansible без агентов: playbook на 50 строк разворачивает сервер с нуля · 6 июня 2013
- Puppet 3: первый манифест вместо баш-скриптов · 7 февраля 2013