Выберите продукт

Ansible pull: обновления конфигурации с таймеров systemd

Пошаговое руководство по ansible-pull с запуском через systemd timers. Разбираем, когда pull лучше push, как сверстать Git‑репозиторий, сделать юнит и таймер, защитить ключи, убрать гонки, обеспечить идемпотентность и наблюдаемость.
Ansible pull: обновления конфигурации с таймеров systemd

Классический 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, которая:

  1. Клонирует или обновляет указанный Git-репозиторий на самом узле.
  2. Запускает указанный плейбук локально (connection=local).
  3. Поддерживает привычные механики 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

Схема systemd: сервис ansible-pull и таймер

Проверка, отладка и наблюдаемость

Для ручной проверки:

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, сеть, зеркала ролей и таски, которые можно сделать более идемпотентными. Старайтесь избегать «дрожащих» тасков: они создают шум и иногда маскируют настоящие изменения.

FastFox VDS
Облачный VDS-сервер в России
Аренда виртуальных серверов с моментальным развертыванием инфраструктуры от 195₽ / мес

Варианты инвентори и выбор окружения

В 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 планируйте окно обновлений и частоту таймера так, чтобы не мешать рабочим нагрузкам.

Наблюдаемость ansible-pull через journald и алерты

Типичные проблемы и их решение

  • Не сходятся роли: проверьте roles_path, установку через ansible-galaxy, права на каталог /opt/ansible.
  • SSH ошибки: убедитесь в валидном ключе, known_hosts и что аккаунт имеет доступ к репозиторию.
  • Конфликты запуска: используйте flock, проверьте длительные таски и таймер OnUnitActiveSec.
  • Случайные изменения на каждом прогоне: ищите «дрожащие» таски (шаблоны без стабильного порядка, команды без проверок состояния, нефиксированные версии пакетов).

Итоги

Модель ansible-pull с таймерами systemd — простой, надежный и малозатратный способ автоматизации конфигурации. Вы получаете декларативность Ansible, а Git становится единым источником правды. При грамотной настройке безопасности, идемпотентности и наблюдаемости такой подход спокойно масштабируется от пары серверов до больших парков, где push уже не столь удобен.

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

Поделиться статьей

Вам будет интересно

Linux: Read-only file system (ro) — почему ext4/XFS внезапно становятся только для чтения и как восстановить OpenAI Статья написана AI (GPT 5)

Linux: Read-only file system (ro) — почему ext4/XFS внезапно становятся только для чтения и как восстановить

Файловая система внезапно стала read-only (ro): приложения падают, обновления не ставятся, данные не пишутся. Разберём, что вызыва ...
Kubernetes Ingress: 413 Request Entity Too Large — увеличиваем лимит загрузки в Nginx Ingress и на backend OpenAI Статья написана AI (GPT 5)

Kubernetes Ingress: 413 Request Entity Too Large — увеличиваем лимит загрузки в Nginx Ingress и на backend

Ошибка 413 Request Entity Too Large при загрузке файлов через Kubernetes Ingress обычно появляется из‑за лимитов на размер тела за ...
CAA и ACME: как DNS ограничивает выпуск SSL (issue/issuewild) и что ломает Let's Encrypt OpenAI Статья написана AI (GPT 5)

CAA и ACME: как DNS ограничивает выпуск SSL (issue/issuewild) и что ломает Let's Encrypt

CAA-записи в DNS задают, какие центры сертификации могут выпускать сертификаты для домена. Разберём теги issue/issuewild, как CA и ...