Настраивать каждый новый сервер руками — это нормально, пока их у тебя два-три. Но как только счёт идёт на десятки, а конфигурация должна быть одинаковой на всех, ручные действия превращаются в источник ошибок: где-то забыл установить пакет, где-то опечатался в конфиге, где-то настройка «почти такая же, но не совсем». Ansible решает эту проблему: ты один раз описываешь желаемое состояние сервера в виде YAML-плейбука, а дальше применяешь его к любому количеству хостов одинаково и предсказуемо. В этой статье поднимем управляющий узел srv-ansible01 и настроим с его помощью несколько серверов в сети 192.168.10.0/24.

Главное отличие Ansible от той же связки Zabbix-агентов в том, что на управляемых серверах не нужно ничего устанавливать — Ansible работает по SSH и использует Python, который уже есть почти на любом Linux-дистрибутиве. Управляющий узел подключается, выполняет задачи и отключается — постоянно работающего агента на хостах нет.

Шаг 1. Установка Ansible на srv-ansible01

hostnamectl set-hostname srv-ansible01
apt update
apt install -y software-properties-common
add-apt-repository --yes --update ppa:ansible/ansible
apt install -y ansible

ansible --version

Ansible — это не сервис, который постоянно крутится в фоне, а обычная утилита командной строки. Всё, что нужно, — это установленный пакет и SSH-доступ до целевых серверов.

Шаг 2. SSH-ключи для доступа без пароля

Плейбуки удобно запускать без ввода пароля на каждом шаге, поэтому первым делом настраиваем доступ по ключу. На srv-ansible01 генерируем пару ключей и копируем публичный на целевые серверы — например, srv-web01 (192.168.10.21) и srv-db01 (192.168.10.22):

ssh-keygen -t ed25519 -C "ansible@srv-ansible01" -f ~/.ssh/ansible_key -N ""

ssh-copy-id -i ~/.ssh/ansible_key.pub root@192.168.10.21
ssh-copy-id -i ~/.ssh/ansible_key.pub root@192.168.10.22

На проде вместо root правильнее заводить отдельного технического пользователя ansible с sudo без пароля именно на те команды, что нужны для управления, — но для лабораторного стенда можно упростить и работать под root.

Шаг 3. Инвентарь: список серверов

Ansible ничего не знает про твою инфраструктуру, пока ты не опишешь её в инвентарном файле. Создаём /etc/ansible/hosts (или собственный файл inventory.ini рядом с плейбуками):

[webservers]
srv-web01 ansible_host=192.168.10.21

[dbservers]
srv-db01 ansible_host=192.168.10.22

[monitoring]
srv-zbx01 ansible_host=192.168.10.10
srv-mon01 ansible_host=192.168.10.11

[linux:children]
webservers
dbservers
monitoring

[linux:vars]
ansible_user=root
ansible_ssh_private_key_file=~/.ssh/ansible_key

Группы в инвентаре — это не просто список, а рабочий инструмент: плейбук можно нацелить на конкретную группу (webservers), а можно на объединение групп (linux), не переписывая список хостов каждый раз. Проверить, что инвентарь читается и SSH работает, можно модулем ping — это не ICMP, а проверка того, что Ansible может подключиться и выполнить Python на удалённом хосте:

ansible linux -i inventory.ini -m ping

При успехе каждый хост ответит SUCCESS с зелёным «pong» в выводе.

Ad-hoc команды: быстрые разовые действия

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

ansible webservers -i inventory.ini -m shell -a "df -h /"

ansible srv-db01 -i inventory.ini -m service -a "name=mysql state=restarted" --become

Такие ad-hoc команды удобны для разовой диагностики или быстрого вмешательства, но для всего, что должно повторяться регулярно или быть воспроизводимым, лучше сразу оформлять задачу в виде плейбука — так остаётся история изменений и не приходится вспоминать точную команду через месяц.

Facts: что Ansible знает о сервере

Перед выполнением задач Ansible автоматически собирает facts — сведения об удалённом хосте: версия ОС, объём памяти, список сетевых интерфейсов, архитектура процессора. Эти данные можно использовать прямо в плейбуке для условной логики:

- name: Установить пакет только на Ubuntu
  apt:
    name: htop
    state: present
  when: ansible_facts['distribution'] == 'Ubuntu'

- name: Вывести объём оперативной памяти
  debug:
    msg: "На {{ inventory_hostname }} доступно {{ ansible_facts['memtotal_mb'] }} МБ памяти"

Это особенно полезно, если инфраструктура не полностью однородная — часть серверов на Ubuntu, часть на Debian или RHEL, и один и тот же плейбук должен вести себя чуть по-разному в зависимости от дистрибутива.

Шаг 4. Первый плейбук

Плейбук — это YAML-файл, описывающий, что должно быть настроено на хостах. Напишем простой пример: обновление пакетов, установка полезного набора утилит и включение автоматических security-обновлений.

---
- name: Базовая настройка Linux-серверов
  hosts: linux
  become: true

  tasks:
    - name: Обновить кэш пакетов
      apt:
        update_cache: true
        cache_valid_time: 3600

    - name: Установить базовые утилиты
      apt:
        name:
          - htop
          - curl
          - vim
          - unattended-upgrades
        state: present

    - name: Включить автоматические security-обновления
      copy:
        dest: /etc/apt/apt.conf.d/20auto-upgrades
        content: |
          APT::Periodic::Update-Package-Lists "1";
          APT::Periodic::Unattended-Upgrade "1";

    - name: Убедиться, что часовой пояс выставлен верно
      timezone:
        name: Europe/Moscow

Запускается плейбук одной командой:

ansible-playbook -i inventory.ini base-setup.yml

Ansible пройдётся по всем хостам группы linux, покажет, какие задачи изменили состояние (changed), какие ничего не делали, потому что всё уже настроено (ok), а какие завершились с ошибкой (failed). Это ключевое свойство — идемпотентность: повторный запуск того же плейбука на уже настроенном сервере не сломает и не изменит ничего лишнего, Ansible просто увидит, что пакеты уже стоят, и пропустит установку.

Шаг 5. Роли — структурируем плейбуки

Когда плейбуков становится больше одного, всё в одном файле быстро превращается в нечитаемую простыню. Ansible предлагает роли — стандартную структуру каталогов для переиспользуемых наборов задач. Например, роль для установки node_exporter (того самого, что мы ставили в статье про Prometheus и Grafana) может выглядеть так:

roles/
  node_exporter/
    tasks/
      main.yml
    templates/
      node_exporter.service.j2
    handlers/
      main.yml
    defaults/
      main.yml

В defaults/main.yml задаётся версия по умолчанию, в tasks/main.yml — сама логика скачивания и установки, а в handlers/main.yml — обработчик, который перезапускает сервис только тогда, когда файл конфигурации реально изменился. Подключается роль в плейбуке одной строкой:

- name: Установить мониторинг на все серверы
  hosts: linux
  become: true
  roles:
    - node_exporter

Такой подход особенно хорошо ложится на серию из наших статей: одна роль ставит node_exporter, другая — Zabbix-агент, третья настраивает firewall. Комбинируя роли в разных плейбуках, ты получаешь конструктор, а не копипасту одного и того же кода в десяти файлах.

Шаг 6. Шаблоны и переменные

Конфиги на разных серверах часто отличаются в мелочах — например, IP-адрес сервера мониторинга, к которому подключается Zabbix-агент. Вместо того чтобы копировать статический файл, используются Jinja2-шаблоны с расширением .j2:

# templates/zabbix_agentd.conf.j2
Server={{ zabbix_server_ip }}
ServerActive={{ zabbix_server_ip }}
Hostname={{ inventory_hostname }}

В задаче шаблон применяется модулем template, а переменная zabbix_server_ip задаётся один раз в group_vars или прямо в плейбуке:

- name: Настроить конфиг Zabbix-агента
  template:
    src: templates/zabbix_agentd.conf.j2
    dest: /etc/zabbix/zabbix_agentd.conf
  notify: restart zabbix-agent

Переменная inventory_hostname — встроенная, она автоматически подставляет имя хоста из инвентаря (srv-web01, srv-db01 и так далее), поэтому один и тот же шаблон корректно разворачивается на всех серверах с их собственным именем в поле Hostname.

Обрати внимание на директиву notify: restart zabbix-agent в конце задачи — это ссылка на handler, обработчик, описанный отдельно:

handlers:
  - name: restart zabbix-agent
    service:
      name: zabbix-agent
      state: restarted

Handler срабатывает только один раз в конце прогона и только если задача, которая его вызвала, реально что-то изменила. Это удобно: если шаблон конфига применяется в пяти разных задачах, а notify указан у каждой, сервис всё равно перезапустится единожды, а не пять раз подряд.

Шаг 7. Ansible Vault для секретов

Пароли от баз данных или API-токены нельзя хранить в плейбуках открытым текстом, особенно если репозиторий с плейбуками лежит в общем git. Ansible Vault шифрует такие файлы:

ansible-vault create group_vars/dbservers/vault.yml
# откроется редактор, вписываешь: db_root_password: SuperSecret123!

ansible-playbook -i inventory.ini db-setup.yml --ask-vault-pass

Зашифрованный файл можно спокойно коммитить в git — без пароля от vault его содержимое не прочитать. При запуске плейбука Ansible просто попросит этот пароль или возьмёт его из файла, путь к которому передан флагом --vault-password-file.

Теги: запускаем только часть плейбука

Когда плейбук разрастается и включает установку пакетов, настройку firewall и раскатку мониторинга в одном файле, часто нужно применить только одну часть, не прогоняя весь сценарий заново. Для этого задачам присваиваются теги:

tasks:
  - name: Установить node_exporter
    apt:
      name: node-exporter
      state: present
    tags: [monitoring]

  - name: Настроить UFW
    ufw:
      rule: allow
      port: "22"
    tags: [firewall]

Запустить только задачи с определённым тегом можно флагом при вызове:

ansible-playbook -i inventory.ini base-setup.yml --tags monitoring

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

Динамический инвентарь

Статический файл inventory.ini отлично работает, пока серверы не появляются и не исчезают часто. Если инфраструктура частично живёт в облаке или в виртуализации с автоматическим созданием машин, список хостов стоит получать динамически — через inventory-плагин, который опрашивает API облачного провайдера или гипервизора и сам формирует актуальный список групп и адресов на момент запуска плейбука. Тогда не приходится вручную дописывать новый сервер в inventory.ini каждый раз, когда он создаётся — а создаваться серверы, как мы увидим в следующей статье серии, будут через Terraform.

Частые ошибки и как их избежать

  • Использование shell/command вместо модулей. Соблазн написать shell: apt-get install -y nginx велик, но такой подход теряет идемпотентность — задача будет «changed» при каждом запуске, даже если nginx уже стоит. Всегда, когда есть готовый модуль (apt, service, copy, template, user), используй его вместо сырой команды.
  • Плейбук проверяется сразу на проде. Ansible применяет изменения сразу и без подтверждения на каждом шаге. Перед реальным запуском полезно прогнать плейбук с флагом --check (dry-run) и --diff, чтобы увидеть, что именно изменится, не трогая сервер по-настоящему.
  • Инвентарь и переменные хранятся не в git. Если плейбуки лежат в репозитории, а инвентарь и group_vars — только на локальной машине инженера, при его уходе или поломке ноутбука восстановить конфигурацию будет нечем. Держи весь каталог, включая зашифрованные vault-файлы, под версионным контролем.

Что дальше

Автоматизация и мониторинг обычно идут рука об руку: