- автообнаружение Docker Airflow через docker inspect и .env - мониторинг failed и long-running DAG-запусков с автоматическим retry - экспорт данных в файлы для Zabbix Agent через UserParameter - офлайн-сборка ZIP-архива для закрытых контуров - Zabbix шаблоны для 5.x (XML) и 6.x+ (YAML) - systemd сервис с graceful shutdown и lock file
36 KiB
Airflow DAG Monitor
Автоматизированная система мониторинга Apache Airflow DAG-ов с интеграцией в Zabbix. Обнаруживает проблемные DAG-запуски (ошибки, зависания), выполняет автоматический перезапуск и эскалирует нерешённые инциденты на дашборд администратора.
Оглавление
- Возможности
- Архитектура
- Требования
- Установка
- Автономная сборка (offline)
- Auto-discovery Docker Airflow
- Конфигурация
- Запуск
- Настройка Zabbix
- Логика работы
- Логирование и диагностика
- Безопасность
- Структура проекта
Возможности
- Auto-discovery Docker: автоматическое определение URL, порта и учётных данных Airflow из Docker-контейнера,
.envиdocker-compose.yml - Универсальность: один и тот же архив работает в разных регионах/контурах без ручной правки подключения к Airflow
- Автоматическое обнаружение всех активных (не приостановленных) DAG-ов через Airflow REST API
- Детекция проблем: упавшие (
failed) и зависшие (выполнение > 30 мин) DAG-запуски - Самовосстановление: однократная попытка перезапуска с контролем результата
- Эскалация в Zabbix: при неуспешном перезапуске данные отправляются через
zabbix_sender - Heartbeat-мониторинг: Zabbix отслеживает работоспособность самого монитора
- Идемпотентность: персистентное состояние исключает дублирование перезапусков и алертов
- Автодетекция API: поддержка Airflow REST API v1 и experimental API
- Graceful shutdown: корректное завершение по SIGTERM/SIGINT с сохранением состояния
Архитектура
┌──────────────────────────────────────────────────────────────────┐
│ Airflow DAG Monitor │
│ │
│ ┌─────────┐ ┌──────────┐ ┌──────────────┐ ┌────────┐ │
│ │ Airflow │───>│ Analyzer │───>│ActionHandler │───>│ Zabbix │ │
│ │ Client │ │ │ │ │ │ Sender │ │
│ └─────────┘ └──────────┘ └──────────────┘ └────────┘ │
│ │ │ │ │
│ v v v │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ State Manager │ │
│ │ (retry counts, alert flags) │ │
│ └──────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────┘
│ │
v v
┌─────────┐ ┌──────────────┐
│ Airflow │ │Zabbix Server │
│ API │ │ Dashboard │
└─────────┘ └──────────────┘
Требования
| Компонент | Версия |
|---|---|
| Python | 3.10+ |
| Apache Airflow | 2.x (REST API v1) |
| Zabbix Agent | 5.x / 6.x / 7.x (уже установлен на хосте) |
| Docker | для auto-discovery (опционально) |
| ОС | Linux (systemd) |
Установка
Автоматическая
git clone <repository-url> /opt/airflow-monitor
cd /opt/airflow-monitor
./setup.sh
Скрипт setup.sh выполнит:
- Копирование файлов в
/opt/airflow-monitor(если запущен из другого каталога) - Создание Python virtual environment в
/opt/airflow-monitor/venv - Установку зависимостей из
requirements.txt(требуется интернет) - Создание рабочих директорий (
/var/lib/airflow-monitor,/var/log/airflow-monitor) - Установку systemd unit файла
Ручная
cd /opt/airflow-monitor
# Виртуальное окружение
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
# Директории
sudo mkdir -p /var/lib/airflow-monitor /var/log/airflow-monitor
sudo chown $(whoami):$(id -gn) /var/lib/airflow-monitor /var/log/airflow-monitor
# Systemd (опционально)
sudo cp airflow-monitor.service /etc/systemd/system/
sudo systemctl daemon-reload
Автономная сборка (offline)
Для развёртывания в закрытом контуре без доступа в интернет используется скрипт build.sh.
Он собирает ZIP-архив, содержащий весь исходный код, wheel-пакеты зависимостей и скрипт
офлайн-установки.
Сборка архива
На машине с доступом в интернет:
# Сборка для текущей платформы
./build.sh
# Сборка для конкретной платформы (если целевой сервер отличается)
./build.sh --platform manylinux2014_x86_64
Результат: dist/airflow-monitor-1.0.0.zip
Содержимое архива
airflow-monitor-1.0.0/
├── airflow_monitor/ # Исходный код
├── wheels/ # Wheel-пакеты всех зависимостей
│ ├── requests-2.32.3-py3-none-any.whl
│ ├── PyYAML-6.0.2-cp312-...-linux_x86_64.whl
│ ├── urllib3-2.2.3-py3-none-any.whl
│ ├── charset_normalizer-...whl
│ ├── idna-3.10-py3-none-any.whl
│ └── certifi-2024.8.30-py3-none-any.whl
├── config.yaml # Шаблон конфигурации
├── requirements.txt
├── airflow-monitor.service # Systemd unit
├── install.sh # Скрипт офлайн-установки
└── README.md
Развёртывание на целевом сервере
На машине без интернета:
# 1. Скопировать архив на сервер
scp dist/airflow-monitor-1.0.0.zip user@target:/tmp/
# 2. Распаковать
unzip /tmp/airflow-monitor-1.0.0.zip -d /tmp/
# 3. Установить (по умолчанию в /opt/airflow-monitor)
cd /tmp/airflow-monitor-1.0.0
./install.sh
Сервис будет установлен в /opt/airflow-monitor.
Параметры install.sh
./install.sh [--prefix INSTALL_DIR] [--user SERVICE_USER]
--prefix Каталог установки (по умолчанию: /opt/airflow-monitor)
--user Пользователь для запуска сервиса (по умолчанию: текущий)
Примеры:
# Стандартная установка в /opt/airflow-monitor
./install.sh
# Указать пользователя для запуска сервиса
./install.sh --user airflow
# Установить в альтернативный каталог
./install.sh --prefix /srv/airflow-monitor --user airflow
Скрипт install.sh выполнит:
- Копирование файлов в
/opt/airflow-monitor(или--prefix) - Создание Python virtual environment
- Установку зависимостей из локальных wheel-файлов (
pip install --no-index --find-links wheels/) - Создание рабочих директорий (
/var/lib/airflow-monitor,/var/log/airflow-monitor) - Установку systemd unit с автоподстановкой путей и пользователя
Важно: На целевом сервере должен быть установлен Python 3.10+ и утилита
zabbix_sender. Доступ в интернет не требуется — все зависимости включены в архив.
Кросс-платформенная сборка
Если архитектура машины сборки отличается от целевого сервера:
# Целевой сервер: x86_64
./build.sh --platform manylinux2014_x86_64
# Целевой сервер: aarch64
./build.sh --platform manylinux2014_aarch64
При использовании --platform скачиваются только бинарные wheel-пакеты для указанной
платформы. Если зависимость доступна только в виде source distribution, сборка завершится
ошибкой — в таком случае соберите на машине с аналогичной архитектурой.
Auto-discovery Docker Airflow
Монитор умеет автоматически находить Airflow в Docker на текущем сервере.
Флаг --discover делает скрипт универсальным: один и тот же архив разворачивается
в любом регионе/контуре без ручной правки URL, порта и учётных данных.
Что делает discovery
docker ps → найти контейнер airflow-webserver
docker inspect <container> → получить compose project directory
docker port <container> 8080 → получить host-порт webserver
<compose_dir>/.env → прочитать переменные окружения
<compose_dir>/docker-compose.yml → прочитать конфигурацию сервисов
Из этих данных извлекаются:
- URL:
http://localhost:<host_port>(изdocker port) - Username: из
.env(_AIRFLOW_WWW_USER_USERNAMEилиAIRFLOW_WWW_USER_USERNAME) - Password: из
.env(_AIRFLOW_WWW_USER_PASSWORDилиAIRFLOW_WWW_USER_PASSWORD), с разрешением${VAR:-default}из docker-compose
Использование
# Проверить что discovery находит (без запуска монитора)
/opt/airflow-monitor/venv/bin/python -m airflow_monitor --discover --dry-run
# Запуск с auto-discovery (секция airflow в config.yaml перезаписывается)
/opt/airflow-monitor/venv/bin/python -m airflow_monitor --discover -c /opt/airflow-monitor/config.yaml
# Discovery без config.yaml (все параметры по умолчанию + Zabbix не настроен)
/opt/airflow-monitor/venv/bin/python -m airflow_monitor --discover
Пример вывода --dry-run:
Discovered Airflow at http://localhost:80
Compose dir: /opt/airflow
Container: airflow-airflow-webserver-1
User: airflow
--- Effective Airflow config ---
{
"base_url": "http://localhost:80",
"username": "airflow",
"password": "P@ssw0rd1",
"api_version": "v1",
"timeout": 30,
"verify_ssl": false,
"request_delay": 0.5
}
Приоритет настроек
При запуске с --discover --config config.yaml:
- Загружается
config.yaml(секцииmonitor,zabbix,logging) - Секция
airflowперезаписывается данными из Docker discovery - Это позволяет настроить Zabbix и пороги в конфиге, а подключение к Airflow определять автоматически
Требования для discovery
- Docker должен быть доступен текущему пользователю (
docker psбез sudo) - Airflow webserver контейнер должен быть запущен
.envфайл должен быть в директории docker-compose проекта
Конфигурация
Все параметры задаются в config.yaml. Скопируйте шаблон и отредактируйте:
cp config.yaml config.yaml.bak
vim config.yaml
Основные параметры
airflow — подключение к Airflow
| Параметр | По умолчанию | Описание |
|---|---|---|
base_url |
http://localhost:8080 |
URL Airflow webserver |
username |
airflow |
Логин для Basic Auth |
password |
airflow |
Пароль для Basic Auth |
api_version |
auto |
Версия API: auto, v1, experimental |
timeout |
30 |
Таймаут HTTP-запросов (секунды) |
verify_ssl |
true |
Проверка SSL-сертификатов |
request_delay |
0.5 |
Задержка между запросами к API (секунды) |
monitor — параметры мониторинга
| Параметр | По умолчанию | Описание |
|---|---|---|
cycle_interval |
300 |
Интервал между циклами мониторинга (секунды) |
long_running_threshold |
1800 |
Порог длительности выполнения DAG (секунды) |
retry_wait |
120 |
Ожидание после перезапуска перед проверкой |
max_retries |
1 |
Максимум попыток перезапуска на один DAG run |
state_file |
/var/lib/airflow-monitor/state.json |
Файл персистентного состояния |
state_max_age |
86400 |
Время жизни записей состояния (секунды) |
zabbix — интеграция с Zabbix Agent
| Параметр | По умолчанию | Описание |
|---|---|---|
enabled |
true |
Включить/выключить экспорт данных в файлы |
data_dir |
/var/lib/airflow-monitor |
Каталог для файлов данных |
problems_file |
problems.json |
Файл со списком проблем (JSON) |
heartbeat_file |
heartbeat |
Файл с timestamp последнего цикла |
status_file |
status.json |
Файл со статусом монитора (JSON) |
logging — логирование
| Параметр | По умолчанию | Описание |
|---|---|---|
level |
INFO |
Уровень: DEBUG, INFO, WARNING, ERROR |
file |
/var/log/airflow-monitor/monitor.log |
Путь к файлу логов |
max_bytes |
10485760 |
Размер файла до ротации (10 МБ) |
backup_count |
5 |
Количество ротированных файлов |
Запуск
Тестовый запуск (ручной)
/opt/airflow-monitor/venv/bin/python -m airflow_monitor --config /opt/airflow-monitor/config.yaml
Остановка: Ctrl+C
Production (systemd)
# Включить автозапуск и стартовать
sudo systemctl enable --now airflow-monitor
# Проверить статус
sudo systemctl status airflow-monitor
# Просмотр логов в реальном времени
journalctl -u airflow-monitor -f
# Перезапуск после изменения config.yaml
sudo systemctl restart airflow-monitor
# Остановка
sudo systemctl stop airflow-monitor
Параметры CLI
usage: python -m airflow_monitor [-h] [--config CONFIG] [--discover] [--dry-run]
--config, -c Путь к config.yaml (по умолчанию: config.yaml в текущей директории)
--discover, -d Автообнаружение Airflow Docker (перезаписывает секцию airflow)
--dry-run С --discover: показать найденную конфигурацию и выйти
Настройка Zabbix
Схема доставки данных
Прямого доступа к Zabbix Server с сервера Airflow нет. Данные передаются через локальный Zabbix Agent, который уже подключён к серверу (напрямую или через Zabbix Proxy).
┌──────────────────┐ файлы ┌──────────────┐ сеть ┌──────────────┐
│ airflow-monitor │ ──────────────> │ Zabbix Agent │ ────────────> │Zabbix Server │
│ (systemd) │ /var/lib/... │ UserParameter│ │ Dashboard │
└──────────────────┘ └──────────────┘ └──────────────┘
или
┌──────────────┐
│ Zabbix Proxy │
└──────────────┘
- Монитор пишет результаты в файлы
/var/lib/airflow-monitor/ - Zabbix Agent читает файлы через UserParameter (конфиг
airflow-monitor.conf) - Zabbix Agent передаёт данные на Zabbix Server/Proxy по своему стандартному каналу
1. Конфиг Zabbix Agent (устанавливается автоматически)
Файл /etc/zabbix/zabbix_agentd.conf.d/airflow-monitor.conf устанавливается при
запуске setup.sh или install.sh. Содержимое:
# Список проблемных DAG-ов (JSON)
UserParameter=airflow.dag.problems,cat /var/lib/airflow-monitor/problems.json 2>/dev/null || echo '[]'
# Количество проблемных DAG-ов
UserParameter=airflow.dag.problems.count,python3 -c "import json,sys; print(len(json.load(open('/var/lib/airflow-monitor/problems.json'))))" 2>/dev/null || echo 0
# Heartbeat — epoch timestamp
UserParameter=airflow.monitor.heartbeat,cat /var/lib/airflow-monitor/heartbeat 2>/dev/null || echo 0
# Статус монитора (JSON)
UserParameter=airflow.monitor.status,cat /var/lib/airflow-monitor/status.json 2>/dev/null || echo '{}'
# Сервис запущен (1/0)
UserParameter=airflow.monitor.alive,systemctl is-active airflow-monitor >/dev/null 2>&1 && echo 1 || echo 0
2. Проверка на сервере
После установки проверьте, что Zabbix Agent корректно читает данные:
# Тест UserParameter через агент
zabbix_agentd -t airflow.dag.problems
zabbix_agentd -t airflow.dag.problems.count
zabbix_agentd -t airflow.monitor.heartbeat
zabbix_agentd -t airflow.monitor.alive
3. Импорт шаблона (рекомендуется)
Вместо ручного создания items и триггеров импортируйте готовый шаблон:
| Файл | Zabbix версия |
|---|---|
zabbix/zbx_template_airflow_monitor.yaml |
6.x+ |
zabbix/zbx_template_airflow_monitor_5x.xml |
5.x |
Импорт: Configuration → Templates → Import → выбрать файл.
После импорта привяжите шаблон "Airflow DAG Monitor" к хосту.
Шаблон содержит:
- 8 items (5 основных + 3 dependent из JSON)
- 4 триггера (проблемы DAG, heartbeat, сервис, cycle time)
- 2 графика (проблемы, время цикла)
- 2 макроса (пороги, переопределяются на уровне хоста)
4. Ручное создание items (если без шаблона)
На Zabbix Server создайте items для хоста (хост определяется по HostnameItem=system.hostname
из zabbix_agentd.conf):
| Item | Тип | Key | Тип данных | Интервал |
|---|---|---|---|---|
| Airflow DAG Problems | Zabbix agent | airflow.dag.problems |
Text | 1m |
| Airflow DAG Problems Count | Zabbix agent | airflow.dag.problems.count |
Numeric (unsigned) | 1m |
| Airflow Monitor Heartbeat | Zabbix agent | airflow.monitor.heartbeat |
Numeric (unsigned) | 1m |
| Airflow Monitor Status | Zabbix agent | airflow.monitor.status |
Text | 5m |
| Airflow Monitor Alive | Zabbix agent | airflow.monitor.alive |
Numeric (unsigned) | 1m |
4. Создание триггеров
Есть проблемные DAG-и:
Имя: Airflow: Обнаружены проблемные DAG-запуски ({ITEM.LASTVALUE1})
Выражение: last(/host/airflow.dag.problems.count)>0
Важность: High
Монитор не обновляет данные (heartbeat устарел более чем на 10 минут):
Имя: Airflow Monitor: Данные устарели
Выражение: (now()-last(/host/airflow.monitor.heartbeat))>600
Важность: Disaster
Сервис мониторинга остановлен:
Имя: Airflow Monitor: Сервис не запущен
Выражение: last(/host/airflow.monitor.alive)=0
Важность: High
5. Формат данных
Файл problems.json содержит JSON-массив:
[
{
"dag_id": "etl_daily_pipeline",
"dag_run_id": "scheduled__2026-04-08T00:00:00+00:00",
"issue_type": "failed",
"status": "failed",
"duration_seconds": 145.3,
"error_info": "Task 'load_data' failed: ConnectionError: Connection refused",
"retry_count": 1
},
{
"dag_id": "report_generator",
"dag_run_id": "scheduled__2026-04-08T06:00:00+00:00",
"issue_type": "long_running",
"status": "running",
"duration_seconds": 2415.7,
"error_info": "",
"retry_count": 1
}
]
| Поле | Описание |
|---|---|
dag_id |
Идентификатор DAG |
dag_run_id |
Идентификатор запуска |
issue_type |
Тип проблемы: failed или long_running |
status |
Состояние в Airflow |
duration_seconds |
Длительность выполнения (секунды) |
error_info |
Информация об ошибке из task instance (для failed) |
retry_count |
Количество выполненных перезапусков |
При отсутствии проблем файл содержит [] — триггер автоматически снимается.
Файл status.json:
{
"timestamp": "2026-04-08T10:15:00+00:00",
"cycle_count": 42,
"dag_count": 15,
"issue_count": 0,
"cycle_time_seconds": 3.45
}
Логика работы
Цикл мониторинга
┌─────────────────────┐
│ Загрузка состояния │
└──────────┬──────────┘
v
┌─────────────────────────────┐
│ Получение списка DAG-ов │
│ GET /api/v1/dags │
└──────────────┬──────────────┘
v
┌─────────────────────────────┐
│ Для каждого DAG: │
│ GET /api/v1/dags/{id}/runs │
│ (фильтр: running, failed) │
└──────────────┬──────────────┘
v
┌───────────────────────┐
│ Анализ: есть проблемы?│
└───────┬───────┬───────┘
нет │ │ да
v v
┌────┐ ┌──────────────────┐
│Done│ │ Перезапуск (1 раз)│
└────┘ └────────┬─────────┘
v
┌─────────────────────┐
│ Ожидание 2 мин │
└──────────┬──────────┘
v
┌─────────────────────┐
│ Повторная проверка │
└───────┬──────┬──────┘
ОК │ │ Проблема
v v
┌────┐ ┌──────────────────┐
│Done│ │ Алерт в Zabbix │
└────┘ └──────────────────┘
│
v
┌─────────────────────┐
│ Heartbeat + cleanup │
│ Сохранение состояния │
└─────────────────────┘
Обработка ошибок
| Ситуация | Поведение |
|---|---|
| Airflow API недоступен | Пропуск цикла, повтор через cycle_interval |
| Таймаут запроса к API | Пропуск конкретного DAG, продолжение с остальными |
| Ошибка авторизации (401/403) | Логирование, продолжение цикла |
zabbix_sender не найден |
Отключение Zabbix, продолжение мониторинга |
zabbix_sender вернул ошибку |
Повтор отправки в следующем цикле |
| Повреждённый файл состояния | Пересоздание с пустым состоянием |
| Необработанное исключение в цикле | Логирование traceback, переход к следующему циклу |
Защита от дублирования
Персистентное состояние хранит для каждого DAG-запуска:
- Счётчик перезапусков — исключает повторный retry
- Флаг
alerted— исключает повторную отправку в Zabbix - Ключ
{dag_id}::{dag_run_id}— уникальная идентификация - Автоочистка записей старше 24 часов
Логирование и диагностика
Файлы логов
| Путь | Описание |
|---|---|
/var/log/airflow-monitor/monitor.log |
Лог приложения (с ротацией) |
journalctl -u airflow-monitor |
Лог через systemd journal |
Уровни логирования
- DEBUG — детали API-запросов, содержимое state файла
- INFO — начало/конец цикла, количество DAG-ов, выполненные действия
- WARNING — проблемные DAG-и, ошибки отдельных API-вызовов
- ERROR — ошибки Zabbix, невозможность сохранить состояние
- CRITICAL — невозможность подключиться к Airflow API
Проверка работоспособности
# Статус сервиса
systemctl status airflow-monitor
# Последние логи
journalctl -u airflow-monitor --since "1 hour ago"
# Содержимое state файла
cat /var/lib/airflow-monitor/state.json | python3 -m json.tool
# Тест отправки в Zabbix
zabbix_sender -z zabbix.example.com -s airflow-server -k airflow.dag.problems -o '[]'
Безопасность
Учётные данные
Файл config.yaml содержит пароль Airflow в открытом виде. Рекомендации:
# Ограничить доступ к конфигурации
chmod 600 config.yaml
chown root:root config.yaml
Systemd hardening
Сервис запускается с ограничениями:
| Директива | Значение | Назначение |
|---|---|---|
NoNewPrivileges |
true |
Запрет эскалации привилегий |
ProtectSystem |
strict |
Файловая система только для чтения |
ReadWritePaths |
явный список | Доступ на запись только в рабочие каталоги |
PrivateTmp |
true |
Изолированный /tmp |
ProtectHome |
read-only |
Домашняя директория только для чтения |
Lock file
Файл блокировки /var/run/airflow-monitor.lock предотвращает одновременный запуск
нескольких экземпляров. Блокировка снимается автоматически при завершении процесса.
Структура проекта
air-flow-monitor/
├── airflow_monitor/ # Основной Python-пакет
│ ├── __init__.py # Версия пакета
│ ├── __main__.py # Точка входа, CLI, сигналы, lock file
│ ├── config.py # Загрузка YAML-конфигурации в dataclasses
│ ├── discovery.py # Auto-discovery Airflow Docker инфраструктуры
│ ├── client.py # HTTP-клиент Airflow REST API
│ ├── analyzer.py # Анализ DAG-запусков, классификация проблем
│ ├── state.py # Персистентное состояние (JSON, атомарная запись)
│ ├── actions.py # Перезапуск DAG-ов, отправка в Zabbix
│ └── monitor.py # Оркестрация цикла мониторинга
├── config.yaml # Конфигурация (Airflow, Zabbix, пороги, логи)
├── requirements.txt # Python-зависимости
├── setup.sh # Скрипт установки (с интернетом)
├── build.sh # Сборка автономного ZIP-архива
├── airflow-monitor.service # Systemd unit файл
├── zabbix/
│ └── airflow-monitor.conf # UserParameter конфиг для Zabbix Agent
├── dist/ # Собранные архивы (после ./build.sh)
└── README.md
Зависимости
| Пакет | Версия | Назначение |
|---|---|---|
requests |
>=2.31, <3 | HTTP-клиент для Airflow API |
PyYAML |
>=6.0, <7 | Парсинг конфигурации |
Все остальные модули (json, logging, subprocess, signal, threading, fcntl,
pathlib, dataclasses, datetime) входят в стандартную библиотеку Python.
Лицензия
Внутренний проект. Все права защищены.