Блог "Дети ОИТа"
/поиск

Ansible Pull: от cron к systemd и мониторингу — расширенное руководство

Пошаговое руководство по переходу от cron к systemd для Ansible Pull с добавлением observability через Prometheus и alerting.

Ansible Pull: от cron к systemd и мониторингу

Введение

Ansible Pull — это режим работы, при котором узлы самостоятельно инициируют получение и применение конфигураций из Git-репозитория. В отличие от классической схемы с управляющим узлом (control node), здесь каждый хост сам по расписанию забирает плейбуки и исполняет их. Это особенно полезно для edge-устройств, временных окружений и сценариев, где централизованный push нежелателен или невозможен.

Архитектура и сравнение подходов

Параметр Ansible Pull Классический Ansible (Push)
Инициатор Узел (по расписанию) Управляющий узел
Требования к узлу Python, git, ansible Минимальные (только Python)
Отказоустойчивость Высокая (узлы автономны) Зависит от доступности control node
Масштабируемость Линейная Ограничена производительностью control node

Шаг 1. Запуск через cron (базовый сценарий)

Самый простой способ — поставить в cron задачу, которая раз в N минут выполняет:

*/5 * * * * /usr/bin/ansible-pull -U https://git.example.com/infra.git main.yml

Плюсы: простота, работает везде, где есть cron.
Минусы: логи cron скудны, сложно отслеживать длительность, нет встроенного мониторинга статуса.

Шаг 2. Переход на systemd — таймеры и сервисы

Systemd даёт три ключевых преимущества: структурированные логи (journald), зависимости между юнитами и встроенный мониторинг.

Юнит сервиса /etc/systemd/system/ansible-pull.service

[Unit]
Description=Ansible Pull — применение конфигурации
Wants=network-online.target
After=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/bin/ansible-pull -U https://git.example.com/infra.git main.yml --purge
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target

Юнит таймера /etc/systemd/system/ansible-pull.timer

[Unit]
Description=Запуск Ansible Pull каждые 5 минут

[Timer]
OnBootSec=2min
OnUnitActiveSec=5min
Persistent=true

[Install]
WantedBy=timers.target

Активация:

systemctl daemon-reload
systemctl enable --now ansible-pull.timer

Что это даёт:

  • journalctl -u ansible-pull.service — полные логи с метаданными
  • systemctl status ansible-pull.timer — состояние расписания
  • systemd-analyze calendar '*-*-* *:*:00/300' — проверка синтаксиса

Шаг 3. Мониторинг и observability

Экспортер для Prometheus

Создайте простой скрипт, который проверяет время последнего успешного запуска и экспортирует метрики:

#!/usr/bin/env python3
# /usr/local/bin/ansible-pull-exporter.py
from http.server import HTTPServer, BaseHTTPRequestHandler
import subprocess
import time
import os

LAST_RUN_FILE = '/var/lib/ansible-pull/last_success'

class MetricsHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        if self.path != '/metrics':
            self.send_response(404)
            return
        
        last_success = 0
        if os.path.exists(LAST_RUN_FILE):
            with open(LAST_RUN_FILE) as f:
                try:
                    last_success = float(f.read().strip())
                except:
                    pass
        
        # Проверяем, есть ли активные задачи
        try:
            result = subprocess.run(['systemctl', 'is-active', 'ansible-pull.service'], 
                                  capture_output=True, text=True)
            active = 1 if result.stdout.strip() == 'active' expanded' else 0
        except:
            active = 0
        
        response = (
            f'# HELP ansible_pull_last_success_timestamp Unix timestamp последнего успеха\n'
            f'# TYPE ansible_pull_last_success_timestamp gauge\n'
            f'ansible_pull_last_success_timestamp {last_success}\n'
            f'# HELP ansible_pull_service_active 1 если сервис активен\n'
            f'# TYPE ansible_pull_service_active gauge\n'
            f'ansible_pull_service_active {active}\n'
        )
        
        self.send_response(200)
        self.send_header('Content-type', 'text/plain; version=0.0.4')
        self.end_headers()
        self.wfile.write(response.encode())

if __name__ == '__main__':
    HTTPServer(('127.0.0.1', 9123), MetricsHandler).serve_forever()

Systemd service для экспортера

[Unit]
Description=Ansible Pull Prometheus Exporter
After=network-online.target

[Service]
Type=simple
ExecStart=/usr/bin/python3 /usr/local/bin/ansible-pull-exporter.py
Restart=on-failure

[Install]
WantedBy=multi-user.target

В Prometheus добавляем job:

scrape_configs:
  - job_name: 'ansible-pull'
    static_configs:
      - targets: ['node1:9123', 'node2:9123']

Alerting rules

groups:
  - name: ansible-pull.rules
    rules:
      - alert: AnsiblePullNotRunning
        expr: ansible_pull_service_active == 0
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "Ansible Pull не активен на {{ $labels.instance }}"
          description: "Сервис ansible-pull.service не в статусе active более 10 минут."
      
      - alert: AnsiblePullStale
        expr: time() - ansible_pull_last_success_timestamp > 900
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "Ansible Pull устарел на {{ $labels.instance }}"
          description: "Последний успешный запуск был более 15 минут назад."

Шаг 4. Безопасность и production-ready практики

  1. SSH-less подход: используйте HTTPS с deploy key или token. Для token лучше использовать секреты в systemd:

    systemctl edit ansible-pull.service
    

    Добавьте:

    [Service]
    Environment="GIT_TOKEN=your_token_here"
    ExecStart=/usr/bin/ansible-pull -U https://%GIT_TOKEN%@git.example.com/infra.git main.yml
    
  2. Идемпотентность: плейбуки должны быть идемпотентными. Используйте --check и --diff в preflight:

    ExecStartPre=/usr/bin/ansible-pull -U https://git.example.com/infra.git main.yml --check --diff
    
  3. Логирование: направляйте stdout/stderr в journal, но для детального аудита настройте callback plugin в Ansible, который пишет в структурированный лог (JSON) в /var/log/ansible-pull/.

  4. Ротация логов: добавьте файл в /etc/logrotate.d/ansible-pull:

    /var/log/ansible-pull/*.log {
        weekly
        rotate 12
        compress
        delaycompress
        missingok
        notifempty
        create 0640 root adm
    }
    

Заключение

Переход от cron к systemd для Ansible Pull — это не просто смена планировщика. Это переход к enterprise-уровню observability и надёжности. Systemd даёт:

  • Единый источник истины для логов (journalctl)
  • Зависимости и ordering
  • Интеграцию с мониторингом
  • Возможность декларативного управления через таймеры

Добавление Prometheus-экспортера и алертов превращает автономные узлы в наблюдаемую, контролируемую инфраструктуру, где отклонения детектируются автоматически.

Чек-лист production-ready

  • Юниты systemd с правильными зависимостями (network-online)
  • Persistent=true в таймере для компенсации пропущенных запусков
  • Preflight с --check и --diff
  • Prometheus-экспортер с метриками статуса и свежести
  • Alerting rules для downtime и stale
  • Структурированное логирование
  • Ротация логов
  • Управление секретами через systemd Environment или Vault
  • Тестирование на staging перед rollout

Дата: 2026-10-12 | Категории: Автоматизация / Ansible, Системное администрирование | Теги: ansible, ansible-pull, cron, monitoring, production, pull, scalability, systemd