Что вы получите: рабочий шаблон Ansible за 20 минут
Ansible приводит серверы к заданному состоянию без агентов на управляемых хостах: подключение идёт по SSH, а описание состояния лежит в YAML-файлах и версионируется в Git. Один плейбук за 15-20 минут закрывает типовой набор: установка пакета, создание пользователя, доставка конфигурационного файла, открытие порта в firewall и запуск сервиса.
Карта материала: инвентарь и переменные, первый плейбук и идемпотентность, роли и порядок в репозитории, секреты через Ansible Vault, безопасная проверка на staging и разбор ошибок новичков. В финале собран сквозной пример роли Nginx с пользователем deploy и правилом ufw.
Примеры ориентированы на ansible-core 2.15 и новее, Ubuntu 22.04 и Debian 12. Синтаксис YAML и основы SSH здесь не разбираются: предполагается, что вы работаете с Linux-серверами и умеете подключаться к ним по ключу. Если нужно понять, где заканчивается зона Ansible и начинается зона Terraform, посмотрите разбор Ansible против Terraform: в этой статье речь только о конфигурационном менеджменте.
Итоговая структура репозитория, к которой мы придём:
ansible-repo/ ├── ansible.cfg ├── inventory/ │ ├── staging.ini │ └── production.ini ├── group_vars/ │ ├── all/ │ │ └── vault.yml │ └── web.yml ├── host_vars/ │ └── web01.yml ├── playbooks/ │ └── site.yml ├── roles/ │ └── nginx/ ├── requirements.yml └── .gitignore
Кому подойдёт это руководство
DevOps-инженерам и системным администраторам, у которых Ansible ещё не выстроен в процесс: команды запускаются вручную, репозиторий ролей не структурирован, секреты лежат открытым текстом, а staging отсутствует как этап. Материал рассчитан и на тех, кто только переносит ручные операции в плейбуки.
Примеры закрывают реальные задачи: настройка Nginx, пользователи и SSH-доступ, правила ufw, доставка конфигов через template, перезапуск сервиса только при изменении файла. Теоретические разделы про принципы IaC не разбираются, зато поясняются места, где новички чаще всего ломают логику: приоритет переменных, режим прогноза, поведение shell и command.
Что понадобится для повторения примеров
- Control node (рабочая станция или отдельная VM) с Python 3.10 и ansible-core 2.15 и выше.
- SSH-доступ по ключу к одному-двум тестовым серверам. Подойдут Vagrant, Multipass или контейнеры с systemd.
- Git для версионирования репозитория.
- Python 3.8 и выше на управляемых хостах. В Ubuntu 22.04 и Debian 12 он уже установлен, а путь задаётся переменной ansible_python_interpreter.
Для staging достаточно одной виртуальной машины. Не запускайте первые прогоны на рабочих серверах: команды с become меняют системные файлы, а ошибка в шаблоне конфига роняет сервис.
Инвентарь Ansible: пример структуры для staging и production
Инвентарь - это список управляемых хостов, разбитых на группы, плюс переменные подключения. Окружения разделяются файлами: inventory/staging.ini и inventory/production.ini. Одна и та же роль применяется к разным стендам без правок кода.
[web] staging-web01 ansible_host=10.10.1.11 [db] staging-db01 ansible_host=10.10.1.21 [all:children] web db [all:vars] ansible_user=deploy ansible_ssh_private_key_file=~/.ssh/id_ed25519 ansible_python_interpreter=/usr/bin/python3
Файл ansible.cfg в корне репозитория убирает длинные ключи из команд:
[defaults] inventory = ./inventory/staging.ini host_key_checking = False retry_files_enabled = False vault_password_file = ~/.ansible/vault_pass
Строка host_key_checking = False отключает проверку отпечатка SSH-ключа. Оставляйте её только в тестовом окружении: она открывает путь для подмены хоста в сети. Если файл с паролем Vault отсутствует, Ansible сообщит об ошибке при первом обращении к зашифрованным данным, поэтому такой файл создаётся до прогона.
Проверка доступности всех хостов и работоспособности Python:
ansible all -m ping
Ответ "ping": "pong" на каждом хосте означает, что инвентарь и ключи настроены. Любой unreachable разбирается до того, как написан первый плейбук.
Статический и динамический инвентарь: когда что использовать
Статический INI или YAML подходит для парка из десятков серверов с редкими изменениями. Динамический инвентарь берёт список хостов из API облака: например, плагин amazon.aws.aws_ec2 собирает инстансы по тегам и регулярно обновляет список. Переход на него не требует переписывать плейбуки, если группы названы одинаково, потому что плейбук ссылается на группу, а не на файл. Для старта хватает статического файла, динамический добавляется, когда хосты начинают создаваться и удаляться автоматически.
Групповые и хостовые переменные: group_vars и host_vars
Правило приоритета: host_vars перекрывает group_vars, а group_vars перекрывает defaults роли. На практике group_vars/web.yml задаёт nginx_port: 80 для всей группы, а host_vars/web01.yml переопределяет его на 8080 только для одного хоста.
group_vars/web.yml nginx_port: 80 ansible_user: deploy host_vars/web01.yml nginx_port: 8080
Facts (ansible_os_family, ansible_distribution, ansible_default_ipv4) собирает модуль setup, и они доступны только для чтения. Переопределить их в vars нельзя: запись ansible_os_family: RedHat будет проигнорирована без предупреждения. Для своих признаков берите отдельное имя, например my_os_family. Схема dev/prod с раздельными переменными и переопределениями для сети разобрана в материале про плейбуки Ansible для сетевой маршрутизации.
Первый плейбук: декларативное описание состояния сервера
Плейбук описывает, каким должен быть сервер, а не последовательность команд. Файл playbooks/site.yml для группы web:
- name: Base configuration for web servers
hosts: web
become: true
tasks:
- name: Ensure nginx is installed
ansible.builtin.apt:
name: nginx
state: present
update_cache: true
- name: Ensure deploy user exists
ansible.builtin.user:
name: deploy
groups: sudo
shell: /bin/bash
state: present
- name: Deploy nginx configuration
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: "0644"
notify: restart nginx
- name: Ensure nginx is running and enabled
ansible.builtin.service:
name: nginx
state: started
enabled: true
handlers:
- name: restart nginx
ansible.builtin.service:
name: nginx
state: restarted
Запуск: ansible-playbook -i inventory/staging.ini playbooks/site.yml
В конце выводится PLAY RECAP: ok - успешные задачи, changed - задачи, изменившие систему, failed - ошибки, skipped - задачи, пропущенные по условию when, unreachable - недоступные хосты. Строка changed=4 при первом прогоне нормальна. Те же changed=4 при втором прогоне означают, что часть задач неидемпотентна.
Идемпотентность в Ansible: как проверить и почему это важно
Идемпотентность означает, что повторный запуск не меняет систему, если она уже в нужном состоянии. Модули apt, user, template, service идут с этим свойством по умолчанию. Модули command, shell и raw выполняются всегда и всегда возвращают changed, поэтому их либо заменяют профильным модулем, либо закрывают условием when.
Два режима проверки без изменения хостов:
ansible-playbook -i inventory/staging.ini playbooks/site.yml --check --diff
check_mode прогоняет плейбук в режиме прогноза, diff показывает построчную разницу для файлов, которые изменились бы. Ограничение: для command и shell прогноз тоже требует подсказок, иначе задача помечается как skipped.
Для команд, которые только читают состояние, ставится changed_when: false:
- name: Validate nginx configuration syntax
ansible.builtin.command: nginx -t
changed_when: false
- name: Check active state of nginx
ansible.builtin.command: systemctl is-active nginx
register: nginx_state
changed_when: false
failed_when: nginx_state.stdout != "active"
Такой блок оставляет PLAY RECAP чистым: прогон без изменений показывает changed=0, и любой неожиданный changed сразу указывает на проблемную задачу.
Handlers: перезапуск сервиса только при изменении конфига
Handler запускается один раз в конце play, даже если notify вызван десятью задачами. Если шаблон nginx.conf.j2 не менялся, notify не срабатывает и сервис не перезапускается, и на этом строится безопасная доставка конфигов. Принудительный запуск до конца play делается метазадачей ansible.builtin.meta: flush_handlers. Падение handler помечает хост как failed, и следующие play для него не выполняются.
Ansible роли: структура и организация репозитория
Роль упаковывает задачи, шаблоны, переменные и handlers в один переиспользуемый каталог. Скелет создаётся командой ansible-galaxy init nginx, дальше каталоги наполняются:
roles/nginx/ ├── tasks/main.yml ├── handlers/main.yml ├── defaults/main.yml ├── vars/main.yml ├── templates/nginx.conf.j2 ├── files/ └── meta/main.yml
Подключение в плейбуке сводится к списку:
- name: Apply web roles
hosts: web
become: true
roles:
- nginx
Внешние роли и коллекции фиксируются в requirements.yml и ставятся командой ansible-galaxy install -r requirements.yml. Файл в репозитории нужен, чтобы версии зависимостей совпадали у всех участников команды. Готовые шаблоны задач для повседневных операций собраны в подборке автоматизация инфраструктуры для DevOps и сисадминов.
Как избежать дублирования: defaults, vars и group_vars
defaults/main.yml хранит значения по умолчанию и легко перекрывается из inventory. vars/main.yml хранит константы роли и имеет приоритет выше group_vars и host_vars, поэтому не ставьте туда значения, которые планируете менять по окружению. Порядок перекрытия от высшего к низшему: host_vars, group_vars/web, group_vars/all, defaults роли.
roles/nginx/defaults/main.yml nginx_port: 80 nginx_worker_processes: auto group_vars/web.yml nginx_port: 8080
Типичная ловушка: одна и та же переменная объявлена и в vars роли, и в group_vars. Приоритет у vars, переопределение из inventory молча не применяется, а конфиг на сервере остаётся со значением по умолчанию.
Теги и выборочный запуск задач
Теги позволяют прогнать только часть автоматизации. Пример задачи в роли nginx:
- name: Deploy nginx configuration
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
tags:
- nginx
- config
Запуск только конфигурации: ansible-playbook playbooks/site.yml --tags config. Список доступных тегов: --list-tags. Исключение блока: --skip-tags packages. Без тегов на большом плейбуке нельзя безопасно применить одну правку: придётся гнать всё, включая задачи с перезапуском сервисов.
Ansible Vault: работа с секретами без утечек в Git
Пароли баз данных, токены API и приватные ключи хранятся в зашифрованных файлах. Создание файла секретов группы:
ansible-vault create group_vars/all/vault.yml
Шифрование одного значения без отдельного файла:
ansible-vault encrypt_string 'SuperSecret' --name 'db_password'
Пароль Vault указывается в ansible.cfg строкой vault_password_file = ~/.ansible/vault_pass, после чего ключ --vault-password-file в командах не нужен. Файл с паролем добавляется в .gitignore и не коммитится. Задача, работающая с секретом, помечается no_log: true, иначе значение попадёт в вывод терминала и в лог CI.
- name: Create application database user
community.postgresql.postgresql_user:
name: app
password: "{{ db_password }}"
no_log: true
Vault в CI/CD: как передать пароль безопасно
Пароль передаётся через переменную окружения или файл из секретов пайплайна. Пример шага GitLab CI:
deploy:
stage: deploy
script:
- echo "$VAULT_PASS" > vault_pass
- ansible-playbook -i inventory/staging.ini playbooks/site.yml --vault-password-file vault_pass
- rm -f vault_pass
Не передавайте пароль в неинтерактивных сценариях и не указывайте его в командной строке: значение оседает в истории shell, списке процессов и логах раннера. Полный пайплайн с Terraform, Ansible и GitLab CI разобран в статье инфраструктура как код и CI/CD.
Безопасная проверка результата: staging, --check и линтеры
Порядок применения: линт, прогноз на staging, полный прогон на staging, затем production с ограничением по хостам и тегам. Каждый шаг ловит свой класс ошибок.
ansible-lint playbooks/ ansible-playbook -i inventory/staging.ini playbooks/site.yml --check --diff ansible-playbook -i inventory/staging.ini playbooks/site.yml ansible-playbook -i inventory/production.ini playbooks/site.yml --limit web01 --tags nginx
ansible-lint отмечает устаревшие конструкции, команды вместо модулей и отсутствие режима у файлов. molecule прогоняет роль в контейнерах и проверяет идемпотентность: сценарий converge запускает роль дважды и падает, если второй прогон что-то меняет. Ограничение режима --check: задачи с command и shell без changed_when в прогнозе пропускаются, поэтому их поведение проверяется только реальным прогоном на staging.
Чек-лист перед запуском на production
- Резервная копия конфигов, которые меняет плейбук.
- Полный прогон на staging с нуля на чистом хосте.
- --check на production без failed и без неожиданных changed.
- Теги проставлены, прогон идёт с --limit и --tags, а не целиком.
- Vault подключён, секреты не выводятся в лог.
- ansible-lint без ошибок уровня critical.
Для критичных сервисов сначала выведите один хост из балансировщика, примените плейбук, проверьте работу и верните хост в пул. После успеха расширяйте --limit.
Типовые ошибки новичков в Ansible и как их избежать
Facts вместо vars. Запись vars: ansible_os_family: RedHat не сработает: факт собирает модуль setup, и переопределению он не подлежит. Свои признаки храните под своими именами (my_os_family) и сравнивайте с ними в when.
Отсутствие тегов. Без тегов нельзя запустить одну правку, поэтому растёт соблазн прогнать весь плейбук целиком на production. Минимум один тег на логический блок задач: packages, config, firewall.
Запуск сразу на production. Плейбук с become правит /etc и перезапускает сервисы. Порядок staging, прогноз, ограниченный прогон на одном хосте обязателен.
shell и command вместо модулей. Такие задачи всегда показывают changed и не умеют проверять состояние. Сначала проверьте, есть ли модуль под задачу (apt, copy, template, lineinfile, ufw), и только потом берите command с changed_when.
Секреты открытым текстом. Пароль в group_vars/all.yml уезжает в Git вместе с историей коммитов и остаётся там даже после удаления строки. Файлы с секретами шифруются Vault, а пароль Vault живёт вне репозитория.
become: true на всём плейбуке. Права root нужны только части задач. Ставьте become на уровне конкретной задачи и добавляйте become_user, когда действие выполняется от имени сервисного пользователя.
gather_facts: true без необходимости. Сбор фактов занимает секунды на каждом хосте. Если плейбук факты не использует, отключите их: gather_facts: false.
Сквозной пример: роль Nginx с пользователем и firewall
Файл roles/nginx/tasks/main.yml собирает все элементы вместе:
- name: Install nginx
ansible.builtin.apt:
name: nginx
state: present
update_cache: true
tags: packages
- name: Ensure deploy user exists
ansible.builtin.user:
name: deploy
groups: sudo
shell: /bin/bash
state: present
- name: Deploy nginx configuration
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: "0644"
notify: restart nginx
tags: config
- name: Allow HTTP port
community.general.ufw:
rule: allow
port: "{{ nginx_port }}"
proto: tcp
tags: firewall
- name: Ensure nginx is running and enabled
ansible.builtin.service:
name: nginx
state: started
enabled: true
Плейбук подключения:
- name: Configure web servers
hosts: web
become: true
roles:
- role: nginx
tags: nginx
Запуск: ansible-playbook -i inventory/staging.ini playbooks/site.yml. Роль сразу готова к production: порт, имя пользователя и содержимое шаблона переопределяются через group_vars соответствующего окружения. Готовый стек с Nginx, балансировкой и автомасштабированием описан в материале про IaC-стек для отказоустойчивого веб-приложения.
Проверка результата: curl, systemctl и повторный прогон
Проверка на staging:
curl -I http://staging-web01 systemctl status nginx ansible-playbook -i inventory/staging.ini playbooks/site.yml
Ожидаемый результат: код 200 или 301 в первой строке ответа, active (running) в статусе сервиса и changed=0 в PLAY RECAP второго прогона. Если changed больше нуля, найдите задачи с changed и разберитесь с их идемпотентностью: чаще всего виноваты shell или command без changed_when. Handler при повторном прогоне не срабатывает: конфиг не менялся, значит и перезапуск не нужен.
Актуальность версий и совместимость примеров
Версия проверяется командой ansible --version. Примеры из статьи рассчитаны на ansible-core 2.15 и новее; модули ansible.builtin дают единый неймспейс и не конфликтуют с внешними коллекциями. На управляемых хостах нужен Python 3.8 и выше: Ubuntu 22.04 и Debian 12 подходят без дополнительных настроек, для минимальных образов путь к интерпретатору задаётся через ansible_python_interpreter.
Коллекции ставятся отдельно от ядра: ansible-galaxy collection install community.general community.postgresql. Модуль community.general.ufw живёт в коллекции community.general, поэтому в requirements.yml нужна секция collections с указанием версий. В ansible 2.9 и ниже структура другая: модули вызываются по коротким именам, часть параметров отсутствует, а сам механизм коллекций не используется.
Сверяйте совместимость модуля с вашей версией ansible-core и версией целевой ОС по официальной документации модуля перед прогоном на рабочих серверах. Поведение параметров и значения по умолчанию между мажорными версиями меняются, а проверка версии на хосте стоит одного лишнего запуска на staging.