1. От Bash-скриптов к Ansible: декларативное управление конфигурацией и SSH-транспорт
От Bash-скриптов к Ansible: декларативное управление конфигурацией и SSH-транспорт
Обслуживание парка серверов часто начинается с простого Bash-скрипта. Сначала это цикл for, который подключается по SSH к пяти машинам и выполняет apt-get update. Затем добавляются условия, проверки операционных систем, обработка ошибок, и скрипт разрастается до сотен строк. Если на семнадцатом сервере из пятидесяти обрывается сеть, выполнение скрипта прерывается. Инженер остается с неизвестным состоянием системы: на каких серверах пакеты обновились, на каких служба перезапустилась, а какие остались нетронутыми? Повторный запуск такого скрипта может привести к дублированию конфигурационных строк, ошибкам создания уже существующих пользователей или непреднамеренным перезапускам баз данных. Это классическая проблема императивного управления инфраструктурой.
Переход к Ansible означает фундаментальную смену парадигмы: от написания инструкций о том, как сделать, к описанию того, что должно получиться в итоге.
Пределы императивного подхода
При написании Bash-скриптов инженер мыслит глаголами: «установи», «скопируй», «перезапусти». Это императивный подход. Главная проблема императивных скриптов — отсутствие встроенного понимания текущего состояния системы.
Чтобы сделать Bash-скрипт безопасным для повторного запуска, приходится писать сложную логику проверок. Например, задача «добавить пользователя deploy» в простом виде выглядит так: useradd deploy. Но если запустить её дважды, на второй раз скрипт завершится с ошибкой. Безопасный вариант требует проверки:
if ! id -u deploy > /dev/null 2>&1; then useradd deploy; fi
Когда необходимо управлять сложными конфигурационными файлами, например, добавлять параметры в postgresql.conf или nginx.conf, логика проверок на Bash (с использованием grep, sed и awk) становится крайне хрупкой. Скрипт превращается в набор костылей, который страшно модифицировать.
Ansible решает эту проблему через декларативный подход. Вы описываете желаемое состояние (state), а система сама вычисляет разницу между текущим и желаемым состоянием, применяя только необходимые изменения.
В Ansible задача создания пользователя выглядит так:
user: name=deploy state=present
Если пользователя нет, Ansible его создаст. Если он уже существует с нужными параметрами, Ansible просто сообщит: ok (ничего менять не нужно).
Идемпотентность без файла состояния
В предыдущем курсе по Terraform мы уже рассматривали концепцию идемпотентности — свойства операции давать один и тот же результат при многократном применении. Математически это выражается как . Если система уже находится в целевом состоянии , то применение конфигурации не приведет к изменениям: .
Однако между Terraform и Ansible есть принципиальная разница в том, как они достигают идемпотентности.
Terraform использует файл состояния (terraform.tfstate), чтобы помнить, какие ресурсы он создал в облаке. Он сравнивает код с этим файлом, а затем сверяет файл с реальным API облака.
Ansible не имеет файла состояния. Он работает напрямую с операционной системой узла. При каждом запуске Ansible опрашивает сервер в реальном времени, проверяет фактическое наличие пакетов, пользователей, хеши файлов и статусы служб.
!Сравнение выполнения Bash-скрипта и Ansible-модуля
Отсутствие локального state-файла делает Ansible идеальным инструментом для управления конфигурациями внутри ОС. Если другой администратор вручную изменит конфигурацию Nginx на сервере (создаст дрейф конфигурации), следующий запуск Ansible немедленно обнаружит расхождение между декларативным описанием и реальностью, и вернет файл к нужному виду.
Архитектура: Agentless и Push-модель
Исторически системы управления конфигурациями (Puppet, Chef) строились на базе Pull-модели и требовали установки специальных агентов на каждый управляемый сервер. В такой архитектуре:
Это создавало высокий порог входа: нужно было развернуть отказоустойчивый мастер-сервер, настроить инфраструктуру открытых ключей (PKI) для взаимной аутентификации агентов и мастера, а также следить за тем, чтобы агенты не потребляли слишком много ресурсов (CPU/RAM) на целевых машинах.
Ansible пошел по другому пути, выбрав Push-модель и безагентную (Agentless) архитектуру.
В Ansible нет понятия «мастер-сервер» в классическом понимании. Есть два типа узлов:
Ansible использует то, что уже есть на любом Linux-сервере «из коробки»: протокол SSH для транспорта и интерпретатор Python для выполнения логики. Если вы можете подключиться к серверу по SSH, вы уже можете управлять им через Ansible.
Анатомия SSH-транспорта: что происходит под капотом
Безагентная архитектура звучит как магия, но под капотом скрывается изящный инженерный механизм. Понимание этого механизма критически важно для отладки, когда «что-то идет не так».
Когда вы запускаете задачу в Ansible, процесс выполнения на Control Node проходит через следующие этапы:
apt для управления пакетами), подставляет в него переданные вами параметры и генерирует на вашей машине временный Python-скрипт.~/.ssh/config и поддерживает мультиплексирование (ControlMaster), чтобы не тратить время на повторные рукопожатия TCP при выполнении множества задач.~/.ansible/tmp/.python3 ~/.ansible/tmp/ansible-tmp-.../script.py).stdout в формате JSON.Этот пайплайн объясняет несколько важных граничных случаев. Например, если на целевом сервере директория /tmp (или домашняя директория пользователя) смонтирована с флагом noexec (запрет на выполнение скриптов в целях безопасности), Ansible не сможет выполнить модули и выдаст ошибку Permission denied. В таком случае в конфигурации Ansible потребуется переопределить параметр remote_tmp на директорию, где выполнение разрешено.
Также из этого пайплайна следует, что целевой сервер должен иметь установленный Python. Для современных дистрибутивов Ubuntu/Debian это не проблема, но если вы управляете минималистичными контейнерами или специфичными сетевыми железками, где Python нет, Ansible использует специальный модуль raw, который передает команды напрямую в оболочку без генерации Python-кода.
Ad-hoc команды: мост между Bash и Ansible
Прежде чем писать полноценные сценарии (плейбуки), взаимодействие с Ansible начинается с Ad-hoc команд. Это однострочные команды в терминале, которые выполняют одну конкретную задачу на одном или нескольких серверах. Они идеально подходят для быстрых операций: проверить доступность серверов, узнать uptime, перезапустить службу при инциденте.
Синтаксис Ad-hoc команды выглядит так:
ansible [целевые_хосты] -m [имя_модуля] -a "[аргументы_модуля]"
Сравним выполнение повседневных задач через чистый SSH (Bash) и через Ansible Ad-hoc.
Задача 1: Проверка доступности серверов
Вместо написания цикла с ping или ssh для проверки доступности, в Ansible используется модуль ping.
ansible all -m ping
Важно понимать, что модуль ping в Ansible не использует ICMP-пакеты. Он выполняет полный цикл подключения по SSH, копирования микро-скрипта на Python и возврата ответа pong. Успешный ansible ping гарантирует, что SSH работает, ключи подходят, а Python на целевой машине функционирует корректно.
Задача 2: Выполнение произвольных команд
Если нужно просто выполнить команду Linux, используется модуль command.
ansible web_servers -m command -a "free -m"
Модуль command является модулем по умолчанию, поэтому флаг -m command можно опустить: ansible web_servers -a "free -m".
Здесь кроется важный нюанс безопасности. Модуль command выполняет команду напрямую, минуя оболочку (shell) на целевом сервере. Это означает, что переменные окружения (например, $HOME), перенаправления потоков (>, <) и конвейеры (|) работать не будут. Команда ansible all -a "cat /var/log/syslog | grep error" завершится ошибкой, так как | будет воспринят как аргумент для утилиты cat.
Если вам действительно нужны возможности оболочки, используется модуль shell:
ansible all -m shell -a "cat /var/log/syslog | grep error"
Однако shell менее безопасен, так как подвержен инъекциям команд, если аргументы передаются динамически. В декларативной парадигме использование command и shell считается антипаттерном и применяется только тогда, когда для задачи не существует специализированного модуля.
Задача 3: Управление пакетами и службами
Вместо императивного apt-get install -y nginx, который при повторном запуске будет тратить время на чтение списков пакетов, мы используем модуль apt:
ansible web_servers -m apt -a "name=nginx state=present" -b
Флаг -b (от слова become) указывает Ansible на необходимость повысить привилегии до root через sudo перед выполнением модуля.
Для управления службами используется модуль systemd (или service):
ansible web_servers -m systemd -a "name=nginx state=started enabled=yes" -b
Этот вызов не только запустит Nginx, если он остановлен, но и добавит его в автозагрузку (enabled=yes). Если Nginx уже работает и находится в автозагрузке, Ansible ничего не сделает и вернет статус SUCCESS с пометкой changed: false.
Переход от Bash-скриптов к Ansible требует изменения мышления. Вы больше не программируете шаги для достижения цели. Вы используете модули — готовые, протестированные сообществом инструменты, которые инкапсулируют в себе всю сложную логику проверок состояний, обработки ошибок и обеспечения идемпотентности. Ad-hoc команды позволяют почувствовать мощь этого подхода в интерактивном режиме, подготавливая почву для создания полноценных инфраструктурных сценариев.