Все уроки
DevOps и облакоСредний12 мин чтенияПолезно знать

Ansible

Настраивайте много серверов одинаково с помощью читаемых YAML-плейбуков по обычному SSH.

  • Playbooks
  • Inventory
  • Roles
  • Idempotency
Шаг 1/6
Infrastructure as code: вы описываете нужные серверы и базу в файле main.tf и храните его в Git.

За 30 секунд

Ansible — это инструмент автоматизации, который подключается к серверам по SSH и приводит их к описанному состоянию: пакеты установлены, файлы на месте, сервисы запущены, пользователи созданы. Желаемое состояние вы описываете в YAML-плейбуках, а серверы перечисляете в inventory. Агент на серверах не нужен, а хорошо написанный плейбук идемпотентен: при повторном запуске он ничего не меняет.

Не в моём ежедневном стеке — я понимаю принципы и изучу детали, когда это понадобится проекту.

Почему это важно

  • Он заменяет ручную настройку серверов кодом, который можно ревьюить, повторять и версионировать, так что десятый сервер настроен точно так же, как первый.
  • Он agentless: на целевой машине нужны только SSH и Python, поэтому его легко внедрить на существующих серверах, ничего не устанавливая.
  • YAML-плейбуки читаемы для людей, которые не программируют на полный день, так что ими могут делиться команды эксплуатации и разработки.
  • Он хорошо дополняет Terraform: Terraform создаёт машины, а Ansible настраивает то, что на них работает.

Как это работает

01Идея: желаемое состояние по SSH

Представьте, что вы пишете чек-лист настройки сервера: установить nginx, скопировать конфиг, открыть порт 443, запустить сервис. Ansible превращает этот чек-лист в код. Control node — ваш ноутбук или CI-раннер с установленным Ansible — подключается к managed nodes по SSH, копирует небольшие Python-модули, запускает их и удаляет. Каждая задача (task) вызывает модуль — apt, copy, template, user или service — и описывает конечное состояние (state: present, state: started), а не команды, которые к нему ведут. Модуль сначала проверяет текущее состояние и меняет только то, что отличается, сообщая ok или changed. Это свойство — идемпотентность — означает, что один и тот же плейбук можно запускать каждый день как проверку дрейфа. Ansible распространяется как ansible-core (движок и встроенные модули) плюс коллекции — пакеты модулей для облаков, баз данных и сетевых устройств, — собранные вместе в пакете ansible community.

  • Control node: где работает Ansible. Managed nodes: серверы.
  • Модуль: единица работы (apt, template, service).
  • Идемпотентность: второй запуск сообщает ok, а не changed.

02Установка, inventory и ad-hoc команды

Устанавливайте Ansible на control node через pipx или pip в отдельное окружение — так не будет конфликтов с системным Python. Inventory перечисляет управляемые хосты и группирует их, в формате INI или YAML. Группы позволяют отдельно нацеливаться на веб-серверы или базы данных, а папки group_vars/ и host_vars/ хранят переменные для групп и хостов. Прежде чем писать плейбуки, проверьте связь ad-hoc командами — разовыми вызовами модулей из командной строки. ansible all -m ping не использует ICMP: он заходит по SSH и проверяет Python. Используйте SSH-ключи и не-root пользователя с sudo; become: true в Ansible повышает привилегии для задач, которым это нужно. Держите ansible.cfg в проекте, чтобы задать путь к inventory и разумные значения по умолчанию.

yaml
# install on the control node
pipx install --include-deps ansible
 
# inventory.yml
all:
  children:
    web:
      hosts:
        web1: { ansible_host: 203.0.113.10 }
        web2: { ansible_host: 203.0.113.11 }
  vars:
    ansible_user: your_user
 
# test connectivity and gather facts
ansible -i inventory.yml all -m ping
ansible -i inventory.yml web -m ansible.builtin.setup -a "filter=ansible_distribution*"

03Ваш первый плейбук

Плейбук — это список play; каждый play нацелен на хосты и выполняет задачи по порядку. Пример устанавливает nginx на группу web, разворачивает конфигурацию сайта из шаблона Jinja2 и убеждается, что сервис запущен. Строка notify запускает handler, только если шаблон действительно изменился, так что nginx перезагружается один раз в конце play, а не при каждом запуске. Всегда используйте полные имена модулей вроде ansible.builtin.apt — так понятно, из какой коллекции модуль. Сначала запустите с --check --diff, чтобы увидеть, что изменится, ничего не меняя, а потом по-настоящему. Второй настоящий запуск должен показать changed=0; если нет — какая-то задача не идемпотентна и её нужно исправить.

yaml
- name: Configure web servers
  hosts: web
  become: true
  tasks:
    - name: Install nginx
      ansible.builtin.apt:
        name: nginx
        state: present
        update_cache: true
    - name: Deploy site config
      ansible.builtin.template:
        src: templates/site.conf.j2
        dest: /etc/nginx/sites-available/default
        mode: "0644"
      notify: Reload nginx
    - name: Ensure nginx is running
      ansible.builtin.service: { name: nginx, state: started, enabled: true }
  handlers:
    - name: Reload nginx
      ansible.builtin.service: { name: nginx, state: reloaded }

04Переменные, шаблоны и секреты

Переменные позволяют одному плейбуку работать для многих окружений: одна и та же роль настраивает staging и production с разными значениями из group_vars/staging.yml и group_vars/production.yml. Шаблоны используют синтаксис Jinja2 — {{ variable }}, простые циклы и условия, — чтобы рендерить конфигурационные файлы из этих переменных. Facts — это переменные, которые Ansible автоматически собирает с каждого хоста: версия ОС, память, IP-адреса; они полезны для условий вроде when: ansible_distribution == 'Ubuntu'. У приоритета переменных много уровней; не усложняйте: разумные значения — в defaults роли, переопределения для окружений — в group_vars. Для секретов Ansible Vault шифрует файлы или отдельные значения паролем, так что их можно безопасно хранить в Git. В CI передавайте пароль от vault из хранилища секретов CI или получайте секреты во время запуска из менеджера вроде HashiCorp Vault через lookup-плагин.

yaml
# group_vars/production/vars.yml
app_domain: example.com
app_workers: 4
db_password: "{{ vault_db_password }}"
 
# group_vars/production/vault.yml (encrypted)
# ansible-vault create group_vars/production/vault.yml
# vault_db_password: change-me
 
# run with the vault password
ansible-playbook -i inventory.yml site.yml --ask-vault-pass

05Роли и коллекции: структура, которая масштабируется

Когда плейбуки разрастаются, разбивайте их на роли — стандартную структуру папок с tasks, handlers, templates, files, defaults и meta. Роль хорошо делает одну вещь, например nginx, postgres или базовый hardening, а site-плейбук собирает роли для групп хостов. ansible-galaxy role init создаёт скелет. Коллекции упаковывают роли, модули и плагины вместе под одним namespace; их ставят из Ansible Galaxy или приватного hub через requirements.yml с зафиксированными версиями — так же, как вы фиксируете npm-пакеты. Тестируйте роли через Molecule: он поднимает контейнер или VM, применяет роль, запускает её повторно для проверки идемпотентности и проверяет результат. Линтуйте через ansible-lint в CI, чтобы ловить устаревший синтаксис, отсутствующие имена и рискованные паттерны.

yaml
# requirements.yml
collections:
  - name: community.general
    version: ">=10.0.0,<11.0.0"
  - name: ansible.posix
    version: ">=2.0.0"
 
# site.yml
- hosts: web
  become: true
  roles:
    - base_hardening
    - nginx
 
# ansible-galaxy collection install -r requirements.yml

Когда применять

  • Единообразная настройка парка Linux-серверов или VM: пакеты, пользователи, конфиги, сервисы, hardening.
  • Повторяемая подготовка серверов, созданных через Terraform или облачную консоль.
  • Оркестрованные операции: rolling-перезапуски, патчинг пачками, ротация сертификатов на хостах.
  • Сетевые устройства и appliance, для которых у вендоров есть сильные коллекции Ansible.

Когда не стоит

  • Создание и отслеживание облачной инфраструктуры с зависимостями и состоянием — лучше подходят Terraform или OpenTofu.
  • Нагрузки, полностью контейнеризированные в Kubernetes, где конфигурацией управляют манифесты, Helm и операторы.
  • Очень большие парки, которым нужно постоянное применение конфигурации каждые несколько минут, — там лучше подойдут агентные инструменты или immutable-образы.

Альтернативы

  • Terraform / OpenTofu

    Декларативное создание инфраструктуры с состоянием; часто используется вместе с Ansible.

  • Chef / Puppet

    Агентное управление конфигурацией с постоянным применением.

  • Salt

    Быстрая событийная конфигурация и удалённое выполнение с агентом или в режиме SSH.

  • Immutable-образы (Packer, cloud-init)

    Запекайте конфигурацию в образы и заменяйте серверы вместо их изменения.

Лучшие практики

  1. Пишите идемпотентные задачи на модулях; shell или command — только когда модуля нет, с creates: или changed_when.
  2. Используйте полные имена коллекций (ansible.builtin.copy) и фиксируйте версии коллекций в requirements.yml.
  3. Организуйте код в небольшие роли с defaults, а различия окружений держите в group_vars.
  4. Шифруйте секреты через Ansible Vault или получайте их из менеджера секретов; никогда не коммитьте пароли открытым текстом.
  5. Запускайте --check --diff перед рискованными изменениями и используйте serial: для rolling-обновлений.
  6. Линтуйте через ansible-lint и тестируйте роли через Molecule в CI.
  7. Используйте SSH-ключи, отдельного пользователя для автоматизации и become только там, где нужно.

Частые ошибки

  • Использовать shell: для всего, так что каждый запуск сообщает changed и ничего нельзя безопасно повторить.
  • Смешивать табы, неправильные отступы или значения без кавычек, которые YAML читает как boolean или числа, например yes или 0644.
  • Полагаться на сложный приоритет переменных, пока никто уже не знает, откуда берётся значение.
  • Применять изменение сразу ко всем хостам без serial, положив весь сервис разом.
  • Хранить пароли от vault рядом с зашифрованными файлами в репозитории.

Глубже

06Как на самом деле выполняется задача

Для каждой задачи Ansible рендерит аргументы модуля через Jinja2 на control node, упаковывает код модуля (AnsiballZ) и отправляет его по SSH, затем запускает удалённым Python и читает обратно JSON. По умолчанию он делает это одновременно для forks хостов (по умолчанию 5) со стратегией linear, при которой каждый хост завершает задачу, прежде чем любой хост начнёт следующую. На масштабе важна настройка производительности: увеличьте forks, включите pipelining в ansible.cfg (он убирает лишние SSH-раунды, но требует, чтобы sudo не требовал TTY), держите включённым мультиплексирование SSH ControlPersist и отключайте или кэшируйте сбор фактов, когда они не нужны. Стратегия free позволяет быстрым хостам уходить вперёд. Долгие задачи можно запускать асинхронно через async и poll, чтобы не держать соединения открытыми.

ini
# ansible.cfg
[defaults]
inventory = inventory.yml
forks = 25
gathering = smart
fact_caching = jsonfile
fact_caching_connection = .ansible_facts
fact_caching_timeout = 3600
 
[ssh_connection]
pipelining = True
ssh_args = -o ControlMaster=auto -o ControlPersist=60s

07Rolling-обновления с serial и health check

Изменения в production никогда не должны попадать на все хосты сразу. serial принимает число, процент или список вроде [1, 5, "25%"], что даёт канареечный хост, затем небольшую пачку, затем большие. delegate_to выполняет задачу на другой машине — обычно чтобы вывести текущий хост из балансировщика. В сочетании с until и retries health check ждёт, пока сервис станет здоровым, прежде чем двигаться дальше. max_fail_percentage прерывает play, когда в пачке падает слишком много хостов, и не даёт плохому релизу распространиться. any_errors_fatal останавливает всё при первом сбое — это полезно для кластеров баз данных, где частичные изменения опасны.

yaml
- hosts: web
  become: true
  serial: [1, 5, "25%"]
  max_fail_percentage: 0
  tasks:
    - name: Deploy new release
      ansible.builtin.include_role: { name: app_release }
    - name: Wait until healthy
      ansible.builtin.uri:
        url: "http://{{ ansible_host }}:8080/healthz"
        status_code: 200
      register: health
      until: health.status == 200
      retries: 20
      delay: 3
      delegate_to: localhost

08Динамический inventory, AWX и Execution Environments

Статические файлы inventory в облаке быстро устаревают. Inventory-плагины вроде amazon.aws.aws_ec2, azure.azcollection.azure_rm или google.cloud.gcp_compute опрашивают провайдера и строят группы по тегам, так что сервер с тегом role=web автоматически попадает в группу web. Чтобы запускать Ansible как общий сервис, а не с ноутбуков, AWX (open-source upstream Red Hat Ansible Automation Platform) даёт веб-интерфейс, ролевой доступ, хранилище credentials, расписания, API и аудит-логи. Execution Environments — это контейнерные образы, в которые упакованы ansible-core, коллекции и Python-зависимости; их собирают через ansible-builder и запускают через ansible-navigator или AWX. Они решают классическую проблему «у меня на control node работает», делая среду выполнения автоматизации такой же воспроизводимой, как образы приложений, которые она деплоит.

Реальные ситуации

01

Единообразный hardening двадцати существующих серверов

Проблема

Двадцать серверов Ubuntu настраивались вручную несколько лет. Настройки SSH, правила файрвола и пользователи на них различаются, а впереди аудит.

Как решить

Создайте роль base_hardening: отключите вход по паролю и вход root в SSH через шаблон sshd_config с проверкой validate: sshd -t, установите и настройте ufw с default deny incoming, включите автоматические обновления безопасности, управляйте администраторами и их ключами из списка в переменной и установите fail2ban. Запустите её с --check --diff по всем хостам, чтобы увидеть дрейф, проанализируйте, затем примените к одному хосту, а потом к остальным через serial. Запланируйте еженедельный запуск в check-режиме из CI с алертом на любой changed, чтобы дрейф обнаруживался до следующего аудита.

02

Плейбук всегда сообщает changed

Проблема

Deploy-плейбук команды показывает changed на каждой задаче при каждом запуске, поэтому никто не понимает, когда что-то действительно изменилось, а handler-ы зря перезапускают сервисы.

Как решить

Замените shell-команды модулями: apt вместо apt-get install, copy или template вместо echo в файлы, systemd вместо systemctl restart. Где команда неизбежна, добавьте creates: для разовой настройки или сделайте register вывода и задайте changed_when на его основе. Перенесите перезапуски в handler-ы, вызываемые через notify, чтобы они срабатывали только после реальных изменений. Запустите плейбук дважды в Molecule с тестом idempotence и добавьте в CI ansible-lint, который ловит паттерны no-changed-when. Второй запуск должен сообщить changed=0.

03

Безопасный патчинг кластера баз данных

Проблема

Трём репликам базы данных и primary нужны обновления ядра и перезагрузки. Перезагрузка больше чем одного узла за раз или primary первым вызовет простой.

Как решить

Напишите play с serial: 1 и any_errors_fatal: true, который сначала обрабатывает реплики в их собственной группе, а primary — в следующем play. Для каждого хоста: проверочной задачей убедитесь, что репликация в порядке, выполните apt upgrade, перезагрузите модулем ansible.builtin.reboot (он ждёт возвращения хоста), затем через until и retries дождитесь, пока отставание репликации опустится ниже порога. Перед primary выполните контролируемый failover на реплику средствами базы данных, а затем пропатчите бывший primary как реплику. Сначала прогоните на staging.

Шпаргалка

Проверить связь
ansible -i inventory.yml all -m ping
Пробный запуск с diff
ansible-playbook site.yml --check --diff
Ограничить одним хостом
ansible-playbook site.yml --limit web1
Запустить только задачи с тегом
ansible-playbook site.yml --tags nginx
Создать зашифрованный файл
ansible-vault create group_vars/production/vault.yml
Зашифровать одно значение
ansible-vault encrypt_string 'change-me' --name 'vault_db_password'
Скелет новой роли
ansible-galaxy role init nginx
Установить зафиксированные коллекции
ansible-galaxy collection install -r requirements.yml
Линтинг плейбуков
ansible-lint
Показать документацию модуля
ansible-doc ansible.builtin.template

Вопросы и ответы

Q1Что означает идемпотентность в Ansible?

Идемпотентная задача даёт одно и то же конечное состояние, сколько бы раз её ни запускали, и ничего не меняет, если система уже в нужном состоянии. Например, ansible.builtin.apt со state: present ставит nginx, только если его нет; при следующем запуске сообщает ok. Благодаря этому плейбуки можно запускать многократно как страховку и проверку дрейфа. Модули идемпотентны по задумке; голые shell-команды — нет, потому что Ansible не может знать, что они сделали. Если приходится использовать command или shell, добавьте creates:, removes: или условие changed_when, чтобы задача честно отчитывалась. Хороший тест — запустить плейбук дважды: второй запуск должен показать changed=0.

Q2Ansible или Terraform?

Они решают разные уровни задач и хорошо работают вместе. Terraform — это декларативное создание ресурсов с состоянием: он создаёт облачные ресурсы — сети, VM, базы данных, DNS-записи, — отслеживает их в state-файле, планирует изменения и умеет их удалять. Ansible — это процедурная конфигурация по SSH: он настраивает содержимое машин — пакеты, файлы, пользователей и сервисы — и оркестрирует операции вроде rolling-перезапусков. У Ansible есть облачные модули, но без state-файла он хуже отслеживает и удаляет ресурсы. Частый паттерн: Terraform создаёт серверы и выводит их адреса, а Ansible через динамический inventory их настраивает.

Q3Почему Ansible называют agentless и какие у этого компромиссы?

Ansible не нужен демон на управляемых серверах. Он подключается по SSH, копирует небольшие Python-модули, запускает их и убирает за собой. От целевой машины требуются только SSH и Python, которые есть почти на любом Linux-сервере. Это упрощает внедрение и уменьшает поверхность атаки. Компромиссы: это push-модель, поэтому конфигурация применяется, только когда кто-то или что-то запускает плейбук, а не постоянно. На очень больших парках SSH-соединения стоят времени, хотя forks, pipelining и мультиплексирование SSH сильно помогают. Агентные инструменты вроде Puppet сами применяют состояние каждые полчаса. С Ansible похожего поведения добиваются запуском плейбуков по расписанию из CI или AWX.

Q4Как работать с секретами в Ansible?

Встроенный ответ — Ansible Vault: он шифрует целые файлы или отдельные переменные через AES-256, так что секреты можно коммитить рядом с плейбуками. Частая схема — зашифрованный vault.yml рядом с открытым vars.yml, который ссылается на переменные с префиксом vault_, чтобы по именам переменных всё ещё можно было искать. Сам пароль от vault берётся из запроса, из файла с паролем вне репозитория или из скрипта, читающего его из хранилища секретов; в CI это защищённый секрет. В больших системах lookup-плагины получают секреты во время запуска из HashiCorp Vault или облачных менеджеров секретов, так что в Git их нет вовсе. Ставьте no_log: true на задачах, которые могли бы вывести секреты.

Q5Как выкатить изменение на 50 веб-серверов без простоя?

Используйте rolling-обновление в одном play. Задайте serial: размер пачки, например 5 или 10%, чтобы Ansible завершал все задачи на одной пачке перед следующей. В pre_tasks выведите хост из балансировщика, в tasks задеплойте и перезапустите, затем запустите health check через ansible.builtin.uri, пока приложение не ответит, и верните хост в post_tasks. Задайте max_fail_percentage, чтобы раскатка остановилась при падении пачки, а не сломала весь парк. Сначала проверьте с --check --diff на staging и для канарейки ограничьтесь одним хостом через --limit. Так рискованное изменение превращается в контролируемый и наблюдаемый процесс.

Вопросы уровня senior

S1Изменяемая конфигурация через Ansible или immutable-образы: что вы предпочитаете?

Immutable означает собирать образ сервера со всей запечённой конфигурацией, например через Packer, и заменять серверы, а не менять их. Это даёт идентичные машины, быстрое масштабирование, простой откат к предыдущему образу и отсутствие дрейфа. Изменяемая конфигурация через Ansible меняет работающие серверы на месте: дешевле на старте, работает с существующими долгоживущими серверами и stateful-системами вроде баз данных и быстрее для небольших изменений. Многие зрелые системы совмещают оба подхода: Ansible запускается внутри сборки Packer, чтобы получить образ, так что одни и те же роли служат обоим. Для stateless-серверов приложений в autoscaling-группе я предпочитаю immutable; для баз данных, bastion-хостов и нескольких долгоживущих VM прагматичен Ansible на месте. Настоящий враг — неуправляемые ручные изменения, какую бы модель вы ни выбрали.

S2Как бы вы организовали Ansible для нескольких команд и окружений?

Отделите переиспользуемый код от данных окружений. Роли и модули живут в версионированных коллекциях в собственных репозиториях, тестируются через Molecule и выпускаются с семантическими версиями. У каждого окружения есть репозиторий или каталог inventory с group_vars, host_vars и requirements.yml, фиксирующим версии коллекций, так что staging может попробовать новую версию роли раньше production. Execution Environments замораживают ansible-core и Python-зависимости. Запуски идут из CI или AWX, а не с ноутбуков; credentials хранятся там же, а ролевой доступ определяет, кто может запускать какой job template против какого inventory. Держите приоритет переменных плоским: defaults в ролях, переопределения в group_vars, а extra-vars — только для разовых параметров. Компромисс — церемонии: для команды из двух человек с пятью серверами один репозиторий вполне подходит.

S3Что ограничивает производительность Ansible и как масштабировать его на тысячи хостов?

Узкие места — установка SSH-соединений, раунды на каждую задачу, сбор фактов и CPU control node, поскольку каждый fork — это процесс. Шаги масштабирования: увеличьте forks в соответствии с ядрами и памятью control node; включите pipelining и ControlPersist для переиспользования соединений; кэшируйте факты или ставьте gather_facts: false, где они не нужны; сокращайте число задач, используя модули, принимающие списки, например ставьте много пакетов одним вызовом apt; применяйте async для долгих операций. Делите очень большие запуски по группам или регионам и запускайте их параллельно с нескольких control node или execution node AWX рядом с хостами. Ускорители в стиле Mitogen существуют, но неравномерно поспевают за версиями ansible-core, так что тестируйте осторожно. На очень большом масштабе рассмотрите pull-режим через ansible-pull или immutable-образы вместо push на каждый хост.

Глоссарий

Control node
Машина, на которой установлен Ansible и с которой запускаются плейбуки.
Managed node
Сервер, который настраивает Ansible, доступный по SSH (или WinRM для Windows).
Inventory
Список управляемых хостов с их группами и переменными, статический или динамический.
Playbook
YAML-файл из play, сопоставляющих группы хостов упорядоченным задачам.
Module
Единица работы, которую Ansible выполняет на хосте, например apt, template или service.
Handler
Задача, которая выполняется только по notify от изменившейся задачи, обычно restart или reload.
Role
Переиспользуемая структура папок с задачами, шаблонами, handler-ами и defaults для одной цели.
Collection
Версионированный пакет модулей, ролей и плагинов под namespace, из Galaxy или hub.
Ansible Vault
Встроенное шифрование секретных файлов и значений, которые хранятся рядом с плейбуками.
Facts
Сведения, которые Ansible собирает о каждом хосте: ОС, память, сетевые адреса.

Есть идея продукта?Давайте создадим его.

Расскажите о своей идее, продукте или команде. Обычно отвечаю в течение одного рабочего дня.