Control node и управляемые узлы: кто кого толкает
Ansible построен на простой и при этом гениальной идее - push-модель без агента. Есть одна управляющая машина (control node) и есть пачка управляемых узлов (managed nodes). Всё движение идёт в одну сторону: control node подключается к узлам и продавливает на них изменения. Обратного канала, демона или постоянного агента нет - это принципиально отличает Ansible от Puppet или Chef, где на каждой машине крутится свой агент.
На control node живёт ansible-core - это и есть сам движок: бинарники ansible, ansible-playbook, ansible-navigator, парсер инвентаря, набор базовых модулей и плагинов. Ставится он на RHEL/Fedora из пакета:
Код: Выделить всё
sudo dnf install ansible-core
ansible --version
# ansible [core 2.20.x]
# python version = 3.12.x
Код: Выделить всё
# control node -> managed node, ничего предварительно не ставя на узел
ansible all -i inventory -m ansible.builtin.ping
# web1 | SUCCESS => { "ping": "pong" }

Модули: ansible modules как единицы работы
Модуль - это атомарная единица работы. Когда ты пишешь задачу "скопировать файл", "создать пользователя", "открыть порт в firewalld", за каждым действием стоит конкретный модуль. Все ansible modules - это, по сути, небольшие программы (чаще всего на Python), которые принимают параметры, выполняют действие на узле и возвращают JSON с результатом: что изменилось, что осталось как было, были ли ошибки.
Ключевое свойство нормального модуля - идемпотентность. Запусти задачу один раз или десять раз подряд - результат на узле одинаковый, а ненужные изменения не делаются. Модуль сам проверяет текущее состояние и меняет систему, только если она не совпадает с желаемой. Поэтому в выводе ты видишь changed=1 при первом прогоне и ok (changed=0) при повторном.
Код: Выделить всё
- name: Положить конфиг на узлы
hosts: webservers
become: true
tasks:
- name: Скопировать nginx.conf
ansible.builtin.copy:
src: files/nginx.conf
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: "0644"
- name: Открыть http в firewalld
ansible.posix.firewalld:
service: http
permanent: true
immediate: true
state: enabled
- name: Положить SSH-ключ деплоера
ansible.posix.authorized_key:
user: deploy
key: "{{ lookup('file', 'files/deploy.pub') }}"
state: present
FQCN и ansible builtin: почему имена стали длинными
FQCN - fully qualified collection name, полностью квалифицированное имя в формате namespace.collection.module. Разберём ansible.builtin.file по частям:
- ansible - namespace (пространство имён, по сути вендор/команда)
- builtin - имя коллекции
- file - имя модуля внутри коллекции
Почему перешли на длинные имена? Раньше писали просто copy или file. Но модулей с одинаковыми короткими именами в разных коллекциях стало много, и возникали коллизии: какой именно user ты имел в виду? FQCN снимает любую двусмысленность - ты явно указываешь, из какой коллекции брать модуль. Короткие имена пока работают для встроенных модулей, но это считается устаревшим стилем, и для community-модулей короткое имя может вообще не разрешиться.
Это прямо влияет на экзамен. На RHCE/EX294 проверяют, что ты понимаешь модули и пишешь их полными именами. Срезаются на том, что используют, например, posix-модуль firewalld или authorized_key по короткому имени - а они живут не в ansible.builtin, а в коллекции ansible.posix, которая идёт отдельным контентом. Запомни границу: ansible.posix.firewalld и ansible.posix.authorized_key - это НЕ builtin. Если их короткое имя не разрешается, плейбук падает с "couldn't resolve module". На реальном экзамене это потерянные баллы на ровном месте.
Быстрый способ узнать FQCN и параметры любого модуля - встроенная документация:
Код: Выделить всё
ansible-doc ansible.builtin.file
ansible-doc -l | grep firewalld # найти полное имя
ansible-doc ansible.posix.firewalld
Кроме модулей в ansible core есть плагины - это код, который расширяет сам движок, а не делает работу на узле. Их легко перепутать с модулями, но разница важная:
- connection - как подключаться к узлу: ssh (по умолчанию), local, winrm, podman, docker
- inventory - откуда брать список хостов: статический ini/yaml-файл или динамический (AWS, GCP, скрипты)
- lookup - подтянуть данные на control node во время рендера: lookup('file', ...), lookup('env', ...), lookup('password', ...)
- filter - Jinja2-фильтры для преобразования данных: {{ myvar | default('x') }}, | to_json, | regex_replace(...)
- плюс callback (вывод), strategy (как раскатывать задачи), become (повышение прав) и другие
Теперь про упаковку. Коллекция (collection) - это пакет контента: модули, плагины, роли и playbooks, собранные вместе и распространяемые как единое целое. Ставятся коллекции из Ansible Galaxy или приватного хаба:
Код: Выделить всё
ansible-galaxy collection install ansible.posix
ansible-galaxy collection install community.general
ansible-galaxy collection list
- ansible-core - голый движок плюс коллекция ansible.builtin. Минимальная сборка, версии вида 2.20.x. Это то, что ставишь через dnf install ansible-core.
- Ansible community package - тот же ansible-core, но в комплекте с сотнями проверенных коллекций (ansible.posix, community.general и так далее). Версии другие, крупные - 13.x в 2026 году. Удобно для старта, когда не хочешь руками доставлять коллекции.
- Ansible Automation Platform (AAP) - коммерческий продукт Red Hat: веб-интерфейс и API (раньше Tower/AWX), Execution Environments (контейнеры с зафиксированным набором ansible-core и коллекций), RBAC, расписания, аналитика. Это уже про управление автоматизацией в команде на масштабе.
Как модуль попадает на узел и исчезает
Самое интересное - жизненный цикл модуля. Многие думают, что Ansible "удалённо вызывает команды". Нет. Происходит вот что: для каждой задачи Ansible на control node собирает код модуля с подставленными параметрами в один самодостаточный Python-пакет, по SSH копирует его во временную директорию на узле (обычно ~/.ansible/tmp/), запускает там через Python, ловит JSON-результат, а затем удаляет временные файлы. Узел остаётся чистым - никакого мусора и никаких следов агента.
Отсюда вытекает то самое требование "только Python + SSH" на управляемом узле. Питон нужен, чтобы исполнить доставленный модуль, SSH - чтобы доставить и забрать результат. Всё. Это и делает Ansible безагентным.
Типичные грабли
- Используешь firewalld, authorized_key, parted по короткому имени и удивляешься "module not found". Они в коллекциях (ansible.posix, community.general), а не в builtin. Лечится FQCN и установкой коллекции.
- Ставишь ansible-core и ждёшь, что community-модули заработают сами. Нет - либо ставь community package, либо доставляй коллекции через ansible-galaxy.
- Путаешь lookup и slurp. lookup('file', ...) читает файл с control node при рендере. Чтобы прочитать файл С УЗЛА, нужен модуль ansible.builtin.slurp, который исполняется на узле.
- Думаешь, что command и shell - модули с проверкой состояния. Нет, они не идемпотентны сами по себе. По возможности бери специализированный модуль (dnf вместо "shell: dnf install"), он умеет проверять состояние.
- На голой Debian-системе ping падает, потому что нет python3. Доставь его модулем raw до основного плейбука.
Повтори руками на тестовой RHEL/Fedora-машине (или RED OS):
- ansible --version - запиши версию ansible-core и Python.
- ansible all -i inventory -m ansible.builtin.ping - убедись, что узел отвечает pong.
- ansible-doc -l | grep -E "firewalld|authorized_key" - найди полные FQCN этих модулей.
- ansible-galaxy collection list - посмотри, какие коллекции уже стоят. Поставь ansible.posix, если её нет.
- Напиши плейбук из трёх задач выше (copy, firewalld, authorized_key), прогони дважды подряд и сравни changed в первом и втором запуске - так ты увидишь идемпотентность вживую.
- Что должно стоять на управляемом узле, чтобы Ansible им управлял, и почему этого достаточно?
- Разбери ansible.builtin.file на namespace, collection и module. Какой из модулей НЕ из builtin: copy, firewalld, template, user?
- Чем модуль отличается от плагина? Где исполняется lookup, а где copy?
- В чём разница между ansible-core, Ansible community package и AAP?
Ansible - это безагентная push-система: control node с ansible-core продавливает изменения на узлы, которым нужны только Python и SSH. Работу делают модули (ansible modules), движок расширяют плагины, всё это пакуется в коллекции. FQCN вида ansible.builtin.copy - современный стандарт, и на RHCE именно непонимание границы builtin/posix/community чаще всего стоит баллов. Держи в голове цепочку "control node собрал модуль -> доставил по SSH -> Python исполнил -> результат вернулся -> временные файлы удалены" - и поведение Ansible перестанет быть магией.