Зачем Ansible, если есть SSH? Разбираемся на двух серверах

—

от автора

Допустим, у нас есть два сервера с Ubuntu. На обоих должен работать Nginx и открываться один и тот же сайт

В какой-то момент приходится что-то менять: обновить пакет, поправить содержимое страницы, перезапустить службу. Мы подключаемся к первому серверу по SSH, выполняем команды, затем повторяем всё на втором. Вроде ничего сложного.

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

Проблема здесь не столько в количестве команд. Проблема в том, что настройки серверов существуют в виде набора действий, которые кто-то когда-то выполнил. Чтобы понять текущее состояние машины, приходится идти и проверять его.

А что, если можно хранить описание нужной конфигурации в обычном файле и применять его сразу к нескольким серверам?

Для этого и нужен Ansible

В статье разберём, где он работает, как добирается до удалённых машин и что именно происходит после запуска Playbook. А затем на одном небольшом примере посмотрим на свойство Ansible, из-за которого к нему вообще стоит присмотреться.

Картинка 1 - Два веб-сервера и управляющая машина Ansible

Картинка 1 — Два веб-сервера и управляющая машина Ansible

В чём идея Ansible?

Начнём с обычного способа настройки Linux-сервера:

sudo apt install nginxsudo systemctl enable --now nginx

Первая команда устанавливает пакет, вторая запускает Nginx и включает его автозапуск.

Теперь предположим, что мы захотели повторить эти действия через неделю. Даже если пакет уже установлен, команды по-прежнему описывают что выполнить, а не какой результат нам нужен.

В Ansible можно сформулировать задачу иначе:

- name: Install nginx  ansible.builtin.apt:    name: nginx    state: present

Пока не будем разбирать каждую строку. Важно значение state: present.

Мы сообщаем Ansible: пакет nginx должен присутствовать на сервере.

Если его нет, модуль установит пакет. Если он уже установлен, повторять установку не потребуется.

Это и есть главное отличие: мы описываем требуемое состояние, а Ansible во время запуска проверяет, нужно ли что-то менять, чтобы к нему прийти.

Обратите внимание: это не значит, что Ansible постоянно наблюдает за сервером. Он проверяет состояние когда мы его запускаем. Между запусками никакого фонового контроля по умолчанию нет.

К этому отличию ещё вернёмся

Картинка 2 - Проверка состояния пакета nginx

Картинка 2 — Проверка состояния пакета nginx

Как Ansible добирается до серверов?

У Ansible довольно простая архитектура. Есть одна машина, на которой установлен сам инструмент, и есть машины, которыми нужно управлять.

Первую называют Control Node — управляющим узлом. Это может быть отдельная VM или ваш Linux-ноутбук.

Вторые называют Managed Nodes — управляемыми узлами. В нашем случае это web1 и web2.

              Control Node             Ansible + YAML                   |                  SSH                /     \               v       v             web1     web2            Ubuntu   Ubuntu

Ansible не требует устанавливать на каждый Linux-сервер отдельный постоянно работающий агент. Обычно он подключается по SSH и выполняет нужные операции на удалённой машине. Для большинства стандартных модулей там также нужен Python.

То есть Ansible работает поверх уже привычного механизма доступа к Linux. Ему не нужен отдельный порт или специальный сервер, который будет ждать его запросов

Но SSH ведь спрашивает пароль?

Может спрашивать. И Ansible не умеет обходить аутентификацию.

В нашем примере будем считать, что с управляющей машины уже можно подключиться к обоим серверам под пользователем ubuntu:

ssh ubuntu@192.0.2.11ssh ubuntu@192.0.2.12

Адреса 192.0.2.11 и 192.0.2.12 здесь условные. Замените их адресами своих машин.

Обычно такой доступ настраивают с помощью SSH-ключей. Приватный ключ остаётся на управляющей машине, а публичный добавляется на сервер в ~/.ssh/authorized_keys. Если ключа ещё нет, его можно создать командой ssh-keygen -t ed25519, а затем передать публичную часть, например через ssh-copy-id ubuntu@192.0.2.11 (при условии, что первоначальный вход на сервер разрешён). Для второго сервера действие повторяется. В облаке публичный ключ часто добавляют при создании VM.

Этого достаточно для понимания: Ansible пользуется тем же SSH-доступом, который работает у нас в терминале. Он не знает никакого «секретного пароля».

Отдельный момент — sudo. Подключиться к серверу и получить право устанавливать системные пакеты — не одно и то же. Если sudo требует пароль, Ansible может запросить его при запуске через ansible-playbook playbook.yml -K. Если SSH настроен на вход по паролю, а не по ключу, для запроса пароля подключения существует ansible-playbook playbook.yml -k.

На SSH больше останавливаться не будем: теперь важнее понять, что Ansible делает после подключения

Картинка 3 - SSH-подключение от Control Node к серверам

Картинка 3 — SSH-подключение от Control Node к серверам

Inventory: как Ansible узнаёт, какие серверы у нас есть?

Мы знаем, что есть web1 и web2. Но Ansible пока ничего о них не знает.

Для этого используется Inventory — файл со списком серверов и параметрами подключения.

Создадим inventory.ini:

[web]web1 ansible_host=192.0.2.11web2 ansible_host=192.0.2.12[web:vars]ansible_user=ubuntu

Здесь всего несколько новых вещей

[web] — группа серверов. Сейчас в ней две машины, но их может быть и двадцать.

web1 и web2 — удобные имена внутри Ansible. ansible_host указывает реальные адреса для подключения.

ansible_user=ubuntu означает, что на обе машины нужно заходить от имени пользователя ubuntu.

Почему вообще нужны группы? Потому что в дальнейшем мы сможем сказать Ansible «выполни задачу на всех веб-серверах», не перечисляя адреса вручную. Появится web3 — добавим ещё одну строку в Inventory. Само описание настройки от этого не изменится.

Именно здесь начинается удобное разделение: Inventory отвечает за вопрос «где?», а Playbook — за вопрос «что должно быть настроено?»

Картинка 4 - Группа серверов в Inventory

Картинка 4 — Группа серверов в Inventory

Playbook: описание нужного состояния

Теперь перейдём к основной части.

Представим, что на обеих машинах мы хотим получить следующее:

  1. Пакет Nginx установлен.

  2. Веб-страница содержит одинаковый текст.

  3. Nginx запущен и стартует после перезагрузки сервера.

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

YAML — текстовый формат, где важны отступы. Строки вида ключ: значение задают параметры, а - обозначает элемент списка. Для начала этого достаточно.

Создадим web.yml:

---- name: Configure web servers  hosts: web  become: true  tasks:    - name: Install nginx      ansible.builtin.apt:        name: nginx        state: present        update_cache: true        cache_valid_time: 3600    - name: Publish homepage      ansible.builtin.copy:        dest: /var/www/html/index.html        content: |          <h1>Hello from Ansible</h1>          <p>This page is managed as code.</p>        mode: "0644"    - name: Start nginx      ansible.builtin.service:        name: nginx        state: started        enabled: true

Это весь пример. Никаких дополнительных шаблонов, ролей и директорий

Разберём его по частям, потому что сам по себе YAML мало что объясняет.

На каких машинах выполнять?

- name: Configure web servers  hosts: web  become: true

hosts: web связывает этот Playbook с группой web из Inventory.

become: true говорит, что для задач нужно повысить привилегии, обычно через sudo. Без этого обычный пользователь не сможет устанавливать системные пакеты и записывать файлы в /var/www/html.

Здесь слово Playbook обозначает файл со сценарием автоматизации. В нём могут быть несколько наборов действий (Play), но у нас один. Список внутри tasks содержит отдельные задачи.

Как Ansible устанавливает Nginx?

- name: Install nginx  ansible.builtin.apt:    name: nginx    state: present    update_cache: true    cache_valid_time: 3600

ansible.builtin.apt — модуль, то есть готовая реализация определённой операции. В данном случае он умеет работать с пакетами APT на Ubuntu и Debian.

name: nginx указывает пакет, state: present — нужное состояние.

update_cache: true разрешает обновлять индекс пакетов, а cache_valid_time: 3600 позволяет не делать этого заново, если индекс обновлялся менее часа назад.

Обратите внимание: мы не пишем sudo apt install nginx внутри YAML. Мы указываем модулю, какого результата ожидаем, а тот уже определяет, какие действия нужны.

Как появляется страница?

- name: Publish homepage  ansible.builtin.copy:    dest: /var/www/html/index.html    content: |      <h1>Hello from Ansible</h1>      <p>This page is managed as code.</p>    mode: "0644"

Модуль copy создаёт на удалённой машине файл с указанным содержимым. В нашем случае HTML записывается в /var/www/html/index.html — стандартную директорию сайта в типичной установке Nginx на Ubuntu.

Символ | после content: означает, что дальше идёт многострочный текст. Здесь это обычный HTML, ничего особенного.

mode: "0644" задаёт права доступа: владелец может читать и изменять файл, остальные — читать.

Если файла нет, Ansible создаст его. Если содержимое отличается, перезапишет. Если всё уже совпадает, оставит в покое

И да, для этого не понадобился отдельный HTML-файл в проекте. Мы специально оставили страницу прямо в Playbook, чтобы не вводить новые механизмы раньше времени.

Что с самим сервисом?

- name: Start nginx  ansible.builtin.service:    name: nginx    state: started    enabled: true

Модуль service отвечает за службы системы.

state: started означает, что Nginx должен работать сейчас.

enabled: true — что он должен запускаться при старте системы.

Это две разные настройки. Служба может работать прямо сейчас, но не стартовать после перезагрузки. В нашем описании мы учитываем оба случая

Картинка 5 - Три задачи Ansible Playbook

Картинка 5 — Три задачи Ansible Playbook

Что произойдёт после ansible-playbook?

Теперь у нас два файла:

ansible-demo/├── inventory.ini└── web.yml

Ansible должен быть установлен на управляющей Linux-машине. Например, в Ubuntu его можно поставить через pipx:

sudo apt updatesudo apt install -y pipxpipx ensurepathpipx install --include-deps ansible

После pipx ensurepath может потребоваться открыть новый терминал. Установка на сами web1 и web2 не нужна. Предполагается, что на них уже работают SSH и Python 3, а пользователь ubuntu может использовать sudo.

Сначала проверим, что Ansible видит серверы:

ansible web -i inventory.ini -m ansible.builtin.ping

Этот ping — не сетевой ICMP-запрос. Ansible проверяет SSH-подключение и возможность выполнить модуль на удалённой машине.

При успешном соединении получим примерно:

web1 | SUCCESS => {    "changed": false,    "ping": "pong"}web2 | SUCCESS => {    "changed": false,    "ping": "pong"}

Теперь запускаем сам Playbook:

ansible-playbook -i inventory.ini web.yml -K

Флаг -K нужен, если sudo запрашивает пароль. При настроенном беспарольном sudo его можно убрать.

Что Ansible делает после этой команды?

  1. Читает inventory.ini и находит серверы группы web.

  2. Загружает web.yml и видит три задачи, которые нужно выполнить на этих серверах.

  3. Подключается к каждому серверу по SSH.

  4. Выполняет модули apt, copy и service. Модули проверяют состояние и применяют необходимые изменения.

  5. Получает результат выполнения каждой задачи и показывает его в терминале.

Задачи внутри одного Play идут по порядку. При этом несколько серверов Ansible может обрабатывать параллельно

Успешный вывод будет содержать статусы вроде:

TASK [Install nginx]changed: [web1]changed: [web2]TASK [Publish homepage]changed: [web1]changed: [web2]TASK [Start nginx]ok: [web1]ok: [web2]

Здесь changed означает, что задача изменила что-то на сервере. ok — что задача выполнена успешно, но менять ничего не потребовалось. Конкретные статусы зависят от исходного состояния машин: например, установка пакета могла уже запустить Nginx.

Проверим результат:

curl http://192.0.2.11curl http://192.0.2.12

Обе команды должны вернуть HTML нашей страницы. Если с управляющей машины HTTP не доступен, проверьте правила firewall и доступ к порту 80.

Картинка 6 - Выполнение Playbook на двух серверах

Картинка 6 — Выполнение Playbook на двух серверах

Самое интересное начинается при повторном запуске

Итак, оба сервера настроены. Запустим ту же команду ещё раз:

ansible-playbook -i inventory.ini web.yml -K

При неизменной конфигурации мы увидим, что основным задачам больше нечего менять. HTML уже лежит там, где нужно, Nginx установлен и запущен

Это называется идемпотентностью. Если нужное состояние уже достигнуто, повторный запуск правильно написанной задачи не должен снова изменять сервер.

Есть небольшая оговорка: в нашем примере apt может обновить индекс пакетов, когда истечёт cache_valid_time. Поэтому не стоит считать любой changed ошибкой. Важно понимать, почему произошло изменение.

Пока всё предсказуемо. Но гораздо показательнее другая ситуация.

Что, если кто-то изменил один сервер вручную?

Представим, что на web2 удалили главную страницу. На тестовом сервере это легко воспроизвести:

ssh ubuntu@192.0.2.12 'sudo rm /var/www/html/index.html'

На web1 файл остался, а на web2 исчез

Снова запускаем тот же Playbook:

ansible-playbook -i inventory.ini web.yml -K

Теперь нас интересует задача Publish homepage. Пример ожидаемого результата:

TASK [Publish homepage]ok:      [web1]changed: [web2]

Почему так?

На web1 нужный файл уже существует и содержит правильный текст. Ничего менять не нужно.

На web2 файла нет. Модуль copy обнаружит это и создаст его заново.

Мы не писали отдельную инструкцию «если файл удалён, восстанови его». Не проверяли вручную, на какой машине проблема. Просто ещё раз применили описание нужного состояния

Мне кажется, именно в этот момент Ansible становится понятнее любых определений. Мы перестаём думать о настройке как об одноразовой последовательности команд и начинаем работать с воспроизводимым результатом.

Картинка 7 - Восстановление файла на web2

Картинка 7 — Восстановление файла на web2

Почему бы тогда просто не написать Bash-скрипт?

Вполне можно. Особенно если нужно один раз выполнить пару команд на одном сервере

Например, мы можем пройтись циклом по списку адресов и выполнить apt install nginx через SSH. Это рабочий вариант.

Но постепенно в таком скрипте появляются дополнительные условия:

  • Проверить, установлен ли пакет.

  • Убедиться, что файл существует и содержит нужные данные.

  • Записывать файл только при изменении содержимого.

  • Узнать, запущен ли сервис.

  • Понятно сообщить, что изменилось на каждом сервере.

Иными словами, нам приходится самостоятельно реализовывать ту логику проверки состояния, которую готовые модули Ansible уже умеют выполнять. При этом идемпотентность не гарантирована для любого Playbook: если вместо специализированных модулей бездумно запускать произвольные shell-команды, повторный запуск вполне может изменять систему каждый раз.

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

При этом Ansible не отменяет Bash и не запрещает выполнять обычные команды. Просто если для действия уже есть специализированный модуль, чаще разумнее начать с него.

А если сервер сломается через час?

Это важный момент, который легко неправильно понять после примера с восстановлением файла.

Ansible не следит за нашими серверами постоянно.

Если кто-то удалит index.html после запуска Playbook, файл останется удалённым, пока автоматизация не выполнится снова. Если Nginx упадёт, перезапустить его может systemd благодаря собственной политике восстановления сервиса, но не какой-то постоянно работающий Ansible-процесс.

Ansible можно запускать вручную, из CI/CD или по расписанию. Однако сам по себе написанный YAML ещё не означает непрерывного контроля.

И второе ограничение: желаемое состояние должно быть описано достаточно полно. Если наш Playbook ничего не говорит о настройках firewall, Ansible их и не исправит. Он не угадывает, как должен выглядеть весь сервер, а выполняет только то, что мы ему поручили.

Это не недостаток, а понятная граница ответственности инструмента

Что из этого стоит унести с собой?

Попробуем собрать разобранную схему в одну картину.

inventory.ini  ->  где находятся серверыweb.yml        ->  какого состояния мы хотим       |       v     Ansible       |       v   Подключение SSH       |       v  Проверка и изменение       |       v    ok / changed

Теперь слова Inventory, Playbook, Task и Module уже не выглядят четырьмя названиями одного и того же.

Inventory определяет, на каких машинах работать.

Playbook хранит описание автоматизации

Task — отдельная задача внутри Playbook.

Module — конкретный инструмент, которым Ansible выполняет эту задачу: управляет пакетом, файлом или сервисом.

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

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

Уже после этого легко понять, зачем в больших проектах появляются переменные, шаблоны, роли и другие механизмы. Они помогают организовать тот же подход, но не меняют его главную идею.

В результате у нас остаются не только два настроенных сервера, но и обычный текстовый файл, по которому можно понять, как они должны быть устроены. Его можно хранить в Git, просматривать изменения и использовать для настройки следующей машины.

В этом и состоит практическая ценность подхода: конфигурация перестаёт быть историей ручных действий и становится воспроизводимым описанием системы.

Картинка 8 - Итоговая архитектура Ansible

Картинка 8 — Итоговая архитектура Ansible

ссылка на оригинал статьи https://habr.com/ru/articles/1098084/