Погодин Евгений — ИТ Заметки
🐧Linux Обновлено 04.11.2026

Linux для админа Windows 14. Ansible для начинающих: серверы из одного места

Note

Материал сверен с официальной документацией (Ubuntu Server, man-страницы и документация проектов; ссылки — в конце статьи). Примеры вывода — иллюстрации: на вашем сервере значения и формат могут отличаться. Перед работой на боевом сервере проверьте команды на тестовой машине.

Пока серверов два, их настраивают руками. Когда их десять, начинается дрейф: на одном обновили пакет, на другом забыли правило брандмауэра, и никто не помнит, как должно быть. Ansible описывает желаемое состояние в текстовом файле и приводит к нему любое число серверов по SSH. В этой статье вы:

  • установите Ansible на lnx1 и подключите к нему lnx2;
  • опишете серверы в инвентаре и выполните одну команду на всей группе;
  • составите playbook «базовая настройка сервера» и проверите его на тестовой машине с --check --diff;
  • научитесь хранить секреты в ansible-vault и не потерять SSH-доступ по ошибке.
Задача Windows Ansible
Список управляемых компьютеров OU (подразделения) и группы в AD инвентарь (inventory)
Описание желаемого состояния GPO, DSC (Desired State Configuration) playbook (файл YAML)
Одна команда на многих компьютерах Invoke-Command -ComputerName ad hoc: ansible группа -m модуль
Как доставляется клиент сам применяет GPO узел управления заходит по SSH
Программа на управляемом узле клиентская часть GPO не нужна: SSH и Python
Хранение секретов — ansible-vault

Главное отличие от GPO в направлении: политики применяют сами клиенты домена, а Ansible работает «снаружи» и меняет узлы в момент запуска. Если кто-то правит сервер руками между запусками, Ansible узнает об этом только при следующем запуске, поэтому playbook запускают по расписанию (см. статью про службы и cron) или после каждого изменения. Есть и обратная схема ansible-pull: узлы сами забирают конфигурацию из Git.

Что нужно знать заранее

Warning

Адреса этой серии учебные (lnx1 — 192.168.10.20, lnx2 — 192.168.10.21): подставляйте адреса своей сети. Ansible меняет настоящие серверы, поэтому всё из статьи выполняйте на тестовых виртуальных машинах (ВМ) и держите консоль lnx2 в гипервизоре открытой. Брандмауэр и SSH можно сломать за один запуск.

Note

Состояние стенда (общее для статей части 2, остальные статьи ссылаются на эту врезку). lnx1 — узел управления, ufw на нём включён по статье про сеть, поэтому правила для новых служб нужны. lnx2 должен существовать: вход на него с lnx1 идёт по ключу, sudo просит пароль (отсюда -K в командах), а ufw по умолчанию выключен: его включит наш playbook.

Playbook оставляет открытыми только SSH и HTTP. Если на lnx2 уже работают другие службы, например агент Zabbix на порту 10050/tcp, добавьте для них задачи ufw до state: enabled, иначе включённый брандмауэр закроет их порты. Почта с серверов не настроена: письмо отправляется только при настроенной почтовой службе (отдельная тема); без неё читайте журнал или подключите внешнюю систему мониторинга, например Zabbix.

Как устроен Ansible

Ansible не требует агента: программу на управляемые серверы ставить не нужно. Вы устанавливаете его на один компьютер, узел управления (control node), у нас это lnx1. Он подключается к управляемым узлам (managed nodes), у нас это lnx2, по SSH, передаёт им маленькие программы на Python (модули) и выполняет их. Отсюда требования: на узле управления подойдёт почти любая Unix-подобная система с Python, а Windows без WSL (Windows Subsystem for Linux) как узел управления не поддерживается; на управляемом узле нужны Python и пользователь, который может войти по SSH.

Узел управления хранит доступ ко всем узлам, поэтому lnx1 становится самой ценной целью: кто завладеет им, получит и управляемые серверы. Защитите ключ парольной фразой и ssh-agent, ограничьте вход на lnx1 и не давайте автоматизации больше прав, чем нужно (отдельный пользователь, sudo только на нужные команды: статья про права). В учебном стенде Ansible входит как admin-lab с sudo: это упрощение. Ключ для Ansible держите отдельно от ключа для резервных копий: у копий свой пользователь и ограничение ключа, как в статье про копии и в схеме 3-2-1.

Основные понятия:

  • Инвентарь — список узлов и групп, с которыми работает Ansible.
  • Модуль — единица работы: ansible.builtin.apt ставит пакеты, ansible.builtin.user управляет пользователями. Имя ansible.builtin — это коллекция (набор модулей). Модули из других коллекций, например community.general.ufw, в ansible-core не входят: они есть в пакете ansible или ставятся отдельно.
  • Задача (task) — вызов одного модуля с параметрами. Play связывает группу узлов с задачами, playbook — файл YAML с одним или несколькими play.
  • Обработчик (handler) — задача, которая выполняется только тогда, когда что-то изменилось: например, перезагрузка nginx после правки его конфигурации.
  • Идемпотентность: большинство модулей сначала проверяют текущее состояние и ничего не делают, если оно уже совпадает с описанным. Playbook можно запускать повторно. Но так ведут себя не все модули: command и shell просто выполняют команду.

Установка и первое подключение

sudo apt update
sudo apt install ansible
ansible --version
ansible-community --version

В Ubuntu пакет ansible лежит в репозитории Universe, а он, как сказано в статье про apt, поддерживается сообществом. На 04.11.2026 в Ubuntu 24.04 LTS это пакет ansible 9.2.0 с ядром ansible-core 2.16.3: версию ядра показывает ansible --version, версию пакета — ansible-community --version. Пакет объёмный (около 300 МБ на диске). Для Ubuntu 24.04 LTS; в 26.04 LTS пакет новее: проверьте apt policy ansible и документацию выпуска.

Ссылки в конце статьи ведут на документацию ветки 2.21, которую проект сопровождает. У ветки 2.16 из Ubuntu 24.04 на страницах документации стоит пометка, что она больше не сопровождается; описанное в статье поведение сверено по обеим веткам. Если нужна современная версия, в документации Ansible как вариант указан pipx (менеджер программ на Python); тогда пакет ansible из Ubuntu не ставьте:

sudo apt install pipx
pipx install --include-deps ansible

Если после этого команда ansible не находится, выполните pipx ensurepath и откройте новый сеанс.

Модуль ufw лежит в коллекции community.general. В пакете ansible из Ubuntu 24.04 она уже есть, проверьте:

ansible-galaxy collection list | grep community.general

Если вы поставили только ansible-core, коллекцию придётся добавить, и версию надо взять совместимую с вашим ядром: свежие версии коллекции требуют свежее ядро. Для ядра 2.16 подходит ветка 11:

ansible-galaxy collection install 'community.general:<12'

Её источник — каталог сообщества Ansible Galaxy, поэтому без нужды из него не ставьте.

На lnx2 должен быть Python 3 (python3 --version); модулю apt на узле нужен пакет python3-apt.

Ключ для Ansible

Ansible заходит на узлы по SSH. Создайте для него отдельную пару ключей. Опция -f обязательна: без неё ssh-keygen предложит ~/.ssh/id_ed25519 и может перезаписать ключ, который вы создали по статье про SSH для копирования файлов. Задайте парольную фразу:

ssh-keygen -t ed25519 -f ~/.ssh/id_ansible -C "ansible@lnx1"
ssh-copy-id -i ~/.ssh/id_ansible.pub admin-lab@192.168.10.21
ssh -i ~/.ssh/id_ansible admin-lab@192.168.10.21 hostname

ssh-copy-id работает, пока на lnx2 разрешён вход по паролю; если вы его закрыли, добавьте открытый ключ в ~/.ssh/authorized_keys вручную, как описано в статье про SSH. Эти команды заодно запоминают ключ lnx2 в known_hosts; без этого Ansible может запросить подтверждение ключа, а это неудобно при запуске по расписанию: проверка ключа хоста включена по умолчанию. Не отключайте её, а сверяйте отпечаток, как в статье про SSH.

Ansible не умеет сам спросить парольную фразу, поэтому загрузите ключ в ssh-agent и вводите фразу один раз:

ssh-agent bash
ssh-add ~/.ssh/id_ansible

Проект: ansible.cfg и инвентарь

Заведите каталог проекта:

mkdir -p ~/ansible-lab/templates
cd ~/ansible-lab

ansible.cfg — настройки. Ansible берёт первый найденный файл из списка: переменная ANSIBLE_CONFIG, ansible.cfg в текущем каталоге, ~/.ansible.cfg, /etc/ansible/ansible.cfg:

[defaults]
inventory = ./inventory.ini

inventory.ini — инвентарь в формате INI:

[lab]
lnx2 ansible_host=192.168.10.21

[lab:vars]
ansible_ssh_private_key_file=/home/admin-lab/.ssh/id_ansible

В квадратных скобках имя группы; ansible_host задаёт настоящий адрес для короткого имени, а [lab:vars] — переменные всей группы, здесь путь к ключу. Пользователь для подключения по умолчанию тот же, под которым вы работаете на lnx1, то есть admin-lab. Кроме lab, Ansible всегда создаёт группу all со всеми узлами. Тот же инвентарь в YAML удобнее при росте числа узлов:

lab:
  hosts:
    lnx2:
      ansible_host: 192.168.10.21
  vars:
    ansible_ssh_private_key_file: /home/admin-lab/.ssh/id_ansible

Если выберете YAML, сохраните его как inventory.yml и замените в ansible.cfg строку на inventory = ./inventory.yml.

Не создавайте проект в каталоге, доступном на запись всем пользователям: Ansible не читает ansible.cfg из такого каталога, чтобы посторонний не подложил свой файл. Проверим связь:

ansible-inventory --list
ansible lab -m ansible.builtin.ping

Пример вывода второй команды:

lnx2 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
}

Это не ICMP-пинг (проверка доступности по протоколу ICMP, как ping в Windows): модуль проверяет вход по SSH и наличие Python на узле. pong значит, что всё готово.

Разовые команды (ad hoc)

Формат: ansible <узлы> -m <модуль> -a "<параметры>". Если модуль не указан, выполняется command:

ansible lab -a 'uptime'
ansible lab -b -K -m ansible.builtin.apt -a 'name=tree state=present' -C

Опция -b (--become) выполняет задачу с повышением прав, то есть через sudo, а -K запрашивает пароль sudo. Опция -C (--check) только показывает, что изменилось бы. Уберите -C и выполните команду для установки пакета; повторный запуск покажет "changed": false: пакет уже стоит. Это идемпотентность в действии. Разовые команды хороши для проверок и редких действий. Всё, что вы делаете больше одного раза, пишите в playbook.

Playbook: базовая настройка сервера

Playbook — это файл в формате YAML (текст с отступами): список play, а в каждом play параметры hosts (кого настраиваем), become, vars (переменные), tasks и handlers. Отступы только пробелами, как в Netplan. Playbook выполняется сверху вниз, каждая задача получает имя, оно попадает в вывод. Возьмём типичный набор: nginx, брандмауэр с доступом по SSH и HTTP, страница сайта и сообщение при входе. Файл base.yml:

---
- name: Базовая настройка сервера
  hosts: lab
  become: true
  vars:
    site_name: hello.lab.example

  tasks:
    - name: Установить nginx и ufw
      ansible.builtin.apt:
        name:
          - nginx
          - ufw
        state: present
        update_cache: true
        cache_valid_time: 3600
        lock_timeout: 300

    - name: Разрешить SSH до включения брандмауэра
      community.general.ufw:
        rule: allow
        name: OpenSSH

    - name: Разрешить HTTP
      community.general.ufw:
        rule: allow
        port: '80'
        proto: tcp

    - name: Включить брандмауэр
      community.general.ufw:
        state: enabled

    - name: Каталог сайта
      ansible.builtin.file:
        path: /var/www/hello
        state: directory
        mode: '0755'

    - name: Страница сайта
      ansible.builtin.copy:
        content: "<h1>Hello from the lab</h1>\n"
        dest: /var/www/hello/index.html
        mode: '0644'

    - name: Описание сайта nginx
      ansible.builtin.template:
        src: templates/hello.conf.j2
        dest: /etc/nginx/sites-available/hello
        mode: '0644'
      notify: Reload nginx

    - name: Включить сайт
      ansible.builtin.file:
        src: /etc/nginx/sites-available/hello
        dest: /etc/nginx/sites-enabled/hello
        state: link
      notify: Reload nginx

    - name: Сообщение при входе
      ansible.builtin.template:
        src: templates/motd.j2
        dest: /etc/update-motd.d/99-managed-by-ansible
        mode: '0755'

  handlers:
    - name: Reload nginx
      ansible.builtin.service:
        name: nginx
        state: reloaded

Шаблоны лежат в каталоге templates рядом с playbook. templates/hello.conf.j2:

# {{ ansible_managed }}
server {
    listen 80;
    server_name {{ site_name }};
    root /var/www/hello;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }
}

templates/motd.j2:

#!/bin/sh
# {{ ansible_managed }}
echo "Server {{ inventory_hostname }} is managed by Ansible."
echo "Manual changes may be overwritten."

Что важно в этом файле:

  • Модули называют полностью (ansible.builtin.apt): с Ansible 2.10 это рекомендуется, потому что одноимённые модули бывают в разных коллекциях. Параметр state указывают явно, даже если он есть по умолчанию.
  • become: true на уровне play выполняет все задачи с правами root. Без -K Ansible не получит пароль sudo.
  • lock_timeout: 300 — сколько секунд модуль apt ждёт, если пакетную базу занял другой процесс (по умолчанию 60). На только что созданном lnx2 её часто держат фоновые обновления из статьи про apt.
  • Порядок задач важен. Правило allow OpenSSH стоит до state: enabled, как в статье про ufw: иначе включённый брандмауэр может оборвать соединение, и вы потеряете доступ к узлу. Одно предупреждение из документации модуля: если убрать задачу rule: allow, правило на сервере останется; убирать правила нужно задачей с delete: true.
  • Шаблоны (template) обрабатывает шаблонизатор Jinja2 на узле управления: места вида {{ site_name }} заменяются значениями переменных. Переменная ansible_managed вставляет пометку о том, что файл создан Ansible. Права mode пишут в кавычках как строку, '0644': число без кавычек может быть прочитано как десятичное, и права получатся неожиданными.
  • Обработчик Reload nginx выполняется один раз в конце play и только если нотифицировавшая его задача что-то изменила. Если конфигурацию nginx не трогали, перезагрузки не будет.

Несколько модулей, которых в playbook нет:

  • user: когда добавляете пользователя в группы (groups), всегда указывайте append: true. Без него модуль удалит пользователя из всех остальных групп, в том числе из sudo. Параметр password принимает хеш пароля, а не сам пароль.
  • lineinfile правит одну строку файла. Если regexp не нашёл строку, она добавляется в конец, поэтому шаблон должен совпадать и с исходной, и с новой строкой. Для целых файлов надёжнее template или copy.
  • service (параметры state: started или reloaded, enabled: true) управляет службами так же, как systemctl.

Безопасный запуск: сначала проверка

Порядок для нового playbook такой:

ansible-playbook base.yml --syntax-check
ansible-playbook base.yml --list-tasks
ansible-playbook base.yml --check --diff --limit lnx2 -K
ansible-playbook base.yml --limit lnx2 -K
ansible-playbook base.yml --limit lnx2 -K
  1. --syntax-check ловит ошибки структуры YAML, но ничего не выполняет, поэтому значения параметров модулей не проверяет; --list-tasks показывает список задач.
  2. --check --diff — режим симуляции: модули сообщают, что бы они изменили, а --diff показывает различия в файлах. --limit lnx2 ограничивает запуск одним узлом. У симуляции есть пределы: модули без поддержки проверки ничего не показывают, а задачи, зависящие от результата предыдущих, могут упасть. На чистой lnx2 проверка, скорее всего, остановится на задаче «Включить сайт»: модуль file по умолчанию не создаёт ссылку на несуществующий файл, а описание сайта hello в режиме --check ещё не записано (параметр force разрешает ссылку на файл, который «появится позже»). Это вывод из документации и кода модулей; на тестовой ВМ сверьте его по факту. Проверка полезнее на уже настроенном узле, после первого настоящего запуска. Для задач, которым не хватает результата предыдущих, в документации есть приём ignore_errors: "{{ ansible_check_mode }}".
  3. Четвёртая команда выполняет настройку. Пятая нужна для проверки идемпотентности: все задачи должны завершиться без изменений.

Не нужно запускать playbook на всех узлах сразу: сначала один узел с --limit, потом группа. В конце Ansible печатает сводку по узлам, PLAY RECAP (пример, значения вымышленные):

PLAY RECAP ***********************************************************
lnx2 : ok=11 changed=10 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

Столбцы сводки читаются так: ok — задача выполнена (в том числе когда состояние уже совпадало), changed — модуль что-то изменил (в режиме --check — изменил бы), unreachable — до узла не удалось дозвониться (проверьте адрес, SSH и ключ), failed — задача завершилась ошибкой. Если причина неясна, повторите запуск с -v: Ansible покажет подробности, а уровень можно поднять до -vvv (для отладки подключения — -vvvv).

Первой в каждом play идёт неявная задача Gathering Facts: Ansible собирает сведения об узле (систему, адреса), и они доступны в playbook как переменные. После первого запуска changed не ноль, после второго должно быть changed=0. Проверьте результат на lnx2:

curl -H 'Host: hello.lab.example' http://192.168.10.21/
ansible lab -b -K -a 'ufw status verbose'

Войдите на lnx2 по SSH и убедитесь, что при входе появилось сообщение. Теперь поймаем дрейф: допишите любую строку в файл /etc/update-motd.d/99-managed-by-ansible на lnx2 (sudo nano) и снова выполните --check --diff. Ansible покажет разницу между файлом и шаблоном, а при настоящем запуске вернёт файл в описанный вид.

Откатить playbook кнопкой нельзя: пишут обратный playbook с state: absent и delete: true для правил ufw. На тестовой виртуальной машине проще вернуть снимок. Чтобы не потерять доступ, держите в уме три правила: правило SSH идёт до включения брандмауэра, сначала --check --diff на тестовом узле, настройки sshd через Ansible не трогайте, пока не освоитесь. Если будете менять такие файлы, используйте параметр validate модуля (он проверяет файл до записи) и держите вторую SSH-сессию открытой, как в статье про SSH.

Секреты: ansible-vault

Пароли, токены и ключи нельзя хранить в playbook открытым текстом: файлы живут в репозитории. ansible-vault шифрует файл целиком. Допустим, приложению нужен пароль базы данных (как в статье про базы данных):

ansible-vault create secrets.yml
ansible-vault view secrets.yml
ansible-playbook base.yml -e @secrets.yml --ask-vault-pass --limit lnx2 -K

Первая команда спросит пароль vault и откроет редактор ($EDITOR, по умолчанию vi), в который вы впишете app_db_password: <ПАРОЛЬ>. Файл сохранится в зашифрованном виде, и его можно хранить рядом с playbook. Третья команда подставляет переменные из файла при запуске (в нашем base.yml эта переменная не используется, так выглядит сам запуск). Что нужно помнить:

  • пароль vault храните отдельно от зашифрованных файлов, например в менеджере паролей; если используете файл с паролем (--vault-password-file), права 600 и не добавляйте его в репозиторий;
  • ansible-vault encrypt_string с секретом прямо в командной строке оставляет секрет в истории оболочки: вводите его через приглашение;
  • режим --diff может показать содержимое файлов с секретами: для таких задач ставят diff: false.

Роли и Windows: обзор

Когда playbook разрастается, его делят на роли: каталоги с задачами, обработчиками, шаблонами и переменными по готовой схеме. Заготовку создаёт ansible-galaxy role init имя_роли. Windows-узлы Ansible тоже умеет настраивать (по WinRM или SSH, модули ansible.windows.*, например win_ping и win_service): это отдельная тема. А управляющим узлом Windows может быть только внутри WSL.

Типичные ошибки и недоразумения

  • ssh-keygen без -f. Новый ключ может перезаписать ключ для копирования из статьи про SSH. Всегда давайте ключу своё имя.
  • Первая задача apt упала на новом lnx2. Фоновые обновления держат блокировку пакетной базы дольше lock_timeout: подождите несколько минут и повторите запуск.
  • Отступы и кавычки в YAML. Только пробелы, как в Netplan. Значение, которое начинается с {{ переменная }}, берите в кавычки целиком, иначе YAML прочитает его как словарь.
  • Нет -K при become. Ansible не получит пароль sudo, и задачи с повышением прав не выполнятся; запуск при этом может выглядеть зависшим, потому что ждёт пароль.
  • groups без append: true. Пользователь теряет все прочие группы, включая sudo.
  • Убрали задачу с ufw, а правило осталось. Брандмауэр хранит своё состояние, удаляйте правила явно (delete: true).
  • --check на чистом узле остановился на ошибке. Зависимая задача опирается на то, что симуляция не создала: это ограничение проверки, а не поломка.
  • Пароль vault лежит рядом с файлом или в git. Тогда шифрование бесполезно.

Проверьте себя

  1. Что нужно на управляемом узле и на узле управления? На управляемом — SSH-доступ пользователя и Python; программу Ansible ставить не нужно. На узле управления — Ansible и Python; Windows без WSL не поддерживается.
  2. Чем --check --diff отличается от обычного запуска и каковы его пределы? Он ничего не меняет, а показывает, что изменилось бы, и различия в файлах. Модули без поддержки проверки ничего не показывают, зависимые задачи на чистом узле могут упасть.
  3. Зачем правило allow OpenSSH стоит перед state: enabled? Включённый брандмауэр без разрешающего правила для SSH закроет вход по SSH, и вы потеряете доступ к узлу.
  4. Почему ключ для Ansible создают с опцией -f и отдельно от ключа для копий? Без -f ssh-keygen может перезаписать существующий ключ. А lnx1 хранит доступ ко всем узлам: ключи разных задач разделяют, чтобы компрометация одного не открыла остальное.
  5. Где хранить пароль vault и что делать, чтобы --diff не показал секрет? Отдельно от зашифрованных файлов (не в репозитории); для задачи ставят diff: false.

Что дальше

Источники

Ещё по теме «Linux»