Классический Ansible чаще используют в модели push: один управляющий узел подключается по SSH и применяет плейбуки к хостам. Но если у вас разбросанный парк серверов, узлы за NAT, минималистичная инфраструктура или политика «самообслуживания» конфигурации, удобнее модель pull. В ней каждый сервер сам периодически подтягивает конфиг из Git и запускает плейбук локально. Самый простой и надежный способ организовать такие проверки — системные таймеры systemd.
Если только строите парк, удобнее сразу завести окружение на VDS: можно быстро подготовить образ, включить ansible-pull и масштабировать конфигурацию без выделенного управляющего узла.
Когда pull лучше push
Выигрыши pull-подхода очевидны для админов и DevOps:
- Не нужен постоянный централизованный управляющий хост. Достаточно Git-репозитория и SSH-доступа для чтения.
- Подходит для узлов за NAT или с односторонней связью: исходящий SSH к Git есть почти всегда.
- Простая масштабируемость: каждый сервер сам проверяет обновления, нет «штормов» соединений от управляющей ноды.
- Надежная модель с idempotency: если хост был офлайн, он синхронизируется при первом же запуске таймера благодаря
Persistent=true.
Минусы тоже есть: наблюдаемость нужно продумать отдельно (логи, алерты), а также критично следить за безопасностью Git-ключей и целостностью репозитория.
Архитектура: ansible-pull + systemd timers
ansible-pull — это утилита, входящая в Ansible, которая:
- Клонирует или обновляет указанный Git-репозиторий на самом узле.
- Запускает указанный плейбук локально (connection=local).
- Поддерживает привычные механики Ansible: роли,
group_vars,host_vars, Vault.
systemd timers обеспечивают надежный запуск по расписанию, учитывают офлайн-периоды (Persistent=true), позволяют рандомизировать старт, задавать лимиты ресурсов и централизованно логировать в journald.
Идея проста: Git — единый источник правды, ansible-pull — исполнитель на каждом хосте, systemd — расписание и контроль.
Подготовка: установка Ansible и базовая структура репозитория
Установка Ansible
На целевом сервере нужен Ansible. Для Debian/Ubuntu:
apt update
apt install -y ansible git
Для RHEL/CentOS/AlmaLinux/Rocky:
dnf install -y ansible git
Структура репозитория
Рекомендуемая структура минимального репо для pull-модели:
infra/
ansible.cfg
site.yml
inventory/
hosts.yml
group_vars/
all.yml
web.yml
db.yml
host_vars/
host1.yml
roles/
common/
web/
requirements.yml
files/
templates/
Ключевые моменты:
site.yml— «входной» плейбук, который исполняет ansible-pull.inventory/hosts.yml— инвентори может описывать группы и паттерны. В pull-модели это условность: запускается локально, но группировки полезны для логики.group_varsиhost_vars— храните параметры ролей и окружений.requirements.yml— внешний список ролей дляansible-galaxy(по необходимости).
Пример ansible.cfg
[defaults]
inventory = ./inventory/hosts.yml
retry_files_enabled = False
stdout_callback = yaml
stdout_callback_task_overrides = True
host_key_checking = True
roles_path = ./roles
[privilege_escalation]
become = True
become_method = sudo
become_ask_pass = False
Пример inventory/hosts.yml
Даже если это «локальный» запуск, инвентори помогает задать группы:
all:
children:
web:
hosts:
localhost:
ansible_connection: local
db:
hosts: {}
В реальных сценариях вместо localhost можно использовать динамическое определение роли из локальных фактов или окружения.
Локальные факты: выбор ролей на хосте
В pull-модели удобно дать каждому серверу «метку» роли и окружения через локальные факты:
mkdir -p /etc/ansible/facts.d
printf '{"role":"web","env":"prod"}\n' > /etc/ansible/facts.d/role.fact
Тогда в плейбуке можно отталкиваться от ansible_local.role.role и ansible_local.role.env.
Базовый playbook для ansible-pull
---
- name: Site entry point
hosts: localhost
gather_facts: true
connection: local
pre_tasks:
- name: Decide roles from local facts
set_fact:
node_role: "{{ ansible_local.role.role | default('common') }}"
node_env: "{{ ansible_local.role.env | default('dev') }}"
roles:
- role: common
- role: "{{ node_role }}"
vars:
allow_purge: true
Такой подход позволяет одной кодовой базой обслуживать разные типы узлов.
Безопасный доступ к Git
Для ansible-pull обычно используют SSH-доступ «только чтение» с деплой-ключом. Настройте отдельную пару ключей, добавьте публичную часть в репозиторий как read-only ключ, приватную — в /root/.ssh/id_ed25519 на хосте. Дополнительно закрепите known_hosts и задайте строгие опции:
mkdir -p /root/.ssh
chmod 700 /root/.ssh
# Скопируйте приватный ключ в /root/.ssh/id_ed25519 и поставьте 600
chmod 600 /root/.ssh/id_ed25519
# Опционально: зафиксировать fingerprint хостинга Git
ssh-keyscan -t ed25519 github.com >> /root/.ssh/known_hosts
chmod 644 /root/.ssh/known_hosts
# Ограничить ключи и включить строгую проверку
cat > /root/.ssh/config <<'EOF'
Host github.com
HostName github.com
IdentityFile /root/.ssh/id_ed25519
IdentitiesOnly yes
StrictHostKeyChecking yes
UserKnownHostsFile /root/.ssh/known_hosts
EOF
chmod 600 /root/.ssh/config
Хранить секреты в репозитории не нужно — используйте Ansible Vault. Для pull-модели подойдет локальный файл пароля Vault на хосте с правами 600 или интерактивное подтверждение, если запуск не автоматический.
Юнит systemd: ansible-pull как oneshot-сервис
Создадим юнит /etc/systemd/system/ansible-pull.service:
[Unit]
Description=Ansible pull configuration update
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
User=root
WorkingDirectory=/opt/ansible
Environment=ANSIBLE_CONFIG=/opt/ansible/ansible.cfg
# Защита от параллельных запусков
ExecStartPre=/usr/bin/mkdir -p /run/ansible
ExecStart=/usr/bin/flock -n /run/ansible/pull.lock /bin/sh -c "ANSIBLE_NOCOLOR=1 /usr/bin/ansible-pull -U git@github.com:org/infra.git -C main -d /opt/ansible -i inventory/hosts.yml -e ansible_pull=true --purge --accept-host-key && test -f /opt/ansible/requirements.yml && /usr/bin/ansible-galaxy install -r /opt/ansible/requirements.yml --force || true"
Nice=10
IOSchedulingClass=idle
TimeoutStartSec=30m
[Install]
WantedBy=multi-user.target
Комментарии к настройкам:
Type=oneshot— юнит выполняется один раз за вызов таймером.WorkingDirectoryиANSIBLE_CONFIGуказывают на каталог, кудаansible-pullклонирует репозиторий и где лежитansible.cfg.flockспасает от пересечений, если предыдущий прогон еще не завершился.--purgeпомогает убирать удаленные из Git файлы, если логика деплоя это допускает.ansible-galaxy install— по желанию, если используете внешние роли. Проверка наличияrequirements.ymlделает шаг безопасным.
Обратите внимание: для SSH-доступа ключ должен быть доступен пользователю root (или задайте иного пользователя и ключи).
Таймер systemd: расписание и устойчивость
Создадим таймер /etc/systemd/system/ansible-pull.timer:
[Unit]
Description=Run ansible-pull periodically
[Timer]
RandomizedDelaySec=3m
Persistent=true
Unit=ansible-pull.service
[Install]
WantedBy=timers.target
Такой таймер стартует через 2 минуты после загрузки, далее раз в 15 минут с рандомной задержкой до 3 минут, что снижает пики нагрузки на Git. Persistent=true гарантирует отложенный запуск, если сервер был выключен во время планового окна.
Альтернатива — календарное выражение:
[Timer]
RandomizedDelaySec=3m
Persistent=true
Активация
systemctl daemon-reload
systemctl enable --now ansible-pull.timer
systemctl status ansible-pull.timer
journalctl -u ansible-pull.service -n 200 -f

Проверка, отладка и наблюдаемость
Для ручной проверки:
systemctl start ansible-pull.service
systemctl status ansible-pull.service
journalctl -u ansible-pull.service -b
Полезные флаги для временной отладки: -vvv в ansible-pull и вывод diff (ANSIBLE_DIFF_ALWAYS=1). Но в постоянной эксплуатации не злоупотребляйте максимально подробным логированием.
Если прогон занимает много времени, проверьте TimeoutStartSec, сеть, зеркала ролей и таски, которые можно сделать более идемпотентными. Старайтесь избегать «дрожащих» тасков: они создают шум и иногда маскируют настоящие изменения.
Варианты инвентори и выбор окружения
В pull-подходе часто встречаются схемы:
- Локальные факты определяют роль и окружение (prod, stage, dev), а плейбук подключает роли условно.
- Разные ветки Git для окружений:
mainдля prod,developдля stage. Тогда в юните меняется флаг-C. - Один плейбук, разные
group_vars, выбираемые по hostname или тегам вhost_vars.
Главное — обеспечить предсказуемость: хост должен однозначно определить, какой набор ролей и параметров он применяет.
Безопасность и целостность
- Ключи SSH держите с правами 600, включайте
StrictHostKeyCheckingи фиксируйтеknown_hosts. - Ограничьте ключ на стороне Git как read-only, включите защищенные ветки и ревью.
- Используйте Ansible Vault для секретов, минимизируйте их количество на узлах. Для GitOps‑подхода посмотрите материал про sops и age для GitOps‑секретов.
- Логируйте прогоны и настраивайте оповещения по провалам таймера (например, через внешний мониторинг journald).
Идемпотентность и защита от гонок
flock в юните предотвращает одновременные запуски. Если у вас есть долгие таски (например, сборка пакетов или длительные миграции), дополнительно проверьте таймауты systemd и семантику ролей: каждый шаг должен быть идемпотентным, а состояние — предсказуемым при повторе.
Если вы используете блокировки на уровне Ansible, применяйте их аккуратно и обязательно освобождайте в любых код-путях.
Чистка и откаты
--purge уместен, когда файловая иерархия полностью контролируется ролями. Если есть артефакты, создаваемые вне Ansible, включайте их в игнор или храните отдельно. Для быстрых откатов в pull-модели достаточно переключить ветку на предыдущий коммит и дождаться следующего срабатывания таймера или форсировать запуск сервиса.
Расширения: лимиты ресурсов и изоляция
systemd позволяет ограничивать ресурсы процесса ansible-прохода:
CPUQuota,MemoryMax— чтобы не выедать ресурсы продовых сервисов.ProtectSystem,PrivateTmp,NoNewPrivileges— осторожно, учитывая, что Ansible выполняет административные действия.IOSchedulingClass=idle,Nice=10— смягчают влияние на производительность.
Степень «закручивания гаек» зависит от задач ролей. Для серверов с критичными SLA планируйте окно обновлений и частоту таймера так, чтобы не мешать рабочим нагрузкам.

Типичные проблемы и их решение
- Не сходятся роли: проверьте
roles_path, установку черезansible-galaxy, права на каталог/opt/ansible. - SSH ошибки: убедитесь в валидном ключе,
known_hostsи что аккаунт имеет доступ к репозиторию. - Конфликты запуска: используйте
flock, проверьте длительные таски и таймерOnUnitActiveSec. - Случайные изменения на каждом прогоне: ищите «дрожащие» таски (шаблоны без стабильного порядка, команды без проверок состояния, нефиксированные версии пакетов).
Итоги
Модель ansible-pull с таймерами systemd — простой, надежный и малозатратный способ автоматизации конфигурации. Вы получаете декларативность Ansible, а Git становится единым источником правды. При грамотной настройке безопасности, идемпотентности и наблюдаемости такой подход спокойно масштабируется от пары серверов до больших парков, где push уже не столь удобен.
Начните с базового юнита и таймера, добавьте локальные факты для выбора ролей, включите Vault для секретов и постепенно улучшайте роли. Тогда обновления конфигурации будут происходить незаметно, регулярно и без ручной рутины.


