Ansible: первые шаги и автоматизация настройки Linux
Настраивать каждый новый сервер руками — это нормально, пока их у тебя два-три. Но как только счёт идёт на десятки, а конфигурация должна быть одинаковой на всех, ручные действия превращаются в источник ошибок: где-то забыл установить пакет, где-то опечатался в конфиге, где-то настройка «почти такая же, но не совсем». 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-файлы, под версионным контролем.
Что дальше
Автоматизация и мониторинг обычно идут рука об руку: