feat: добавил систему мониторинга Airflow DAG-ов с интеграцией в Zabbix

- автообнаружение 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
This commit is contained in:
Maksim Totmin
2026-04-08 17:23:03 +07:00
commit bc437cbf8c
19 changed files with 3723 additions and 0 deletions
+32
View File
@@ -0,0 +1,32 @@
# =============================================================================
# Zabbix Agent UserParameter — Airflow DAG Monitor
#
# Устанавливается в /etc/zabbix/zabbix_agentd.conf.d/airflow-monitor.conf
# Zabbix agent читает файлы, записанные сервисом airflow-monitor,
# и передаёт данные на Zabbix Server/Proxy через стандартный канал агента.
#
# Файлы данных:
# /var/lib/airflow-monitor/problems.json — JSON массив проблемных DAG-ов
# /var/lib/airflow-monitor/heartbeat — epoch timestamp последнего цикла
# /var/lib/airflow-monitor/status.json — статус монитора (JSON)
# =============================================================================
# Список проблемных DAG-ов (JSON text)
# Zabbix item: type=Zabbix agent, key=airflow.dag.problems, type of info=Text
UserParameter=airflow.dag.problems,cat /var/lib/airflow-monitor/problems.json 2>/dev/null || echo '[]'
# Количество проблемных DAG-ов (для простых триггеров)
# Zabbix item: type=Zabbix agent, key=airflow.dag.problems.count, type of info=Numeric (unsigned)
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 последнего успешного цикла
# Zabbix item: type=Zabbix agent, key=airflow.monitor.heartbeat, type of info=Numeric (unsigned)
UserParameter=airflow.monitor.heartbeat,cat /var/lib/airflow-monitor/heartbeat 2>/dev/null || echo 0
# Статус монитора (JSON: cycle_count, dag_count, issue_count, cycle_time)
# Zabbix item: type=Zabbix agent, key=airflow.monitor.status, type of info=Text
UserParameter=airflow.monitor.status,cat /var/lib/airflow-monitor/status.json 2>/dev/null || echo '{}'
# Проверка что сервис запущен (1 = работает, 0 = остановлен)
# Zabbix item: type=Zabbix agent, key=airflow.monitor.alive, type of info=Numeric (unsigned)
UserParameter=airflow.monitor.alive,systemctl is-active airflow-monitor >/dev/null 2>&1 && echo 1 || echo 0
+288
View File
@@ -0,0 +1,288 @@
zabbix_export:
version: '6.4'
template_groups:
- uuid: 846977d1dfed4968bc5f8bdb363285bc
name: 'Templates/Applications'
templates:
- uuid: a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6
template: 'Airflow DAG Monitor'
name: 'Airflow DAG Monitor'
description: |
Мониторинг Apache Airflow DAG-ов через сервис airflow-monitor.
Сервис пишет данные в файлы, Zabbix Agent читает через UserParameter.
Требования:
- Установлен airflow-monitor (/opt/airflow-monitor)
- Установлен конфиг /etc/zabbix/zabbix_agentd.conf.d/airflow-monitor.conf
- Перезапущен zabbix-agent после установки конфига
Версия шаблона: 1.0.0
groups:
- name: 'Templates/Applications'
items:
# ================================================================
# Основные items
# ================================================================
- uuid: 11111111111111111111111111111001
name: 'Airflow: Problematic DAGs (JSON)'
key: airflow.dag.problems
type: ZABBIX_PASSIVE
value_type: TEXT
delay: 1m
history: 7d
description: 'JSON-массив проблемных DAG-запусков. [] = нет проблем.'
tags:
- tag: component
value: airflow
- tag: component
value: dag-monitor
- uuid: 11111111111111111111111111111002
name: 'Airflow: Problematic DAGs count'
key: airflow.dag.problems.count
type: ZABBIX_PASSIVE
value_type: UNSIGNED
delay: 1m
history: 90d
trends: 365d
description: 'Количество DAG-запусков с проблемами (failed или long_running).'
tags:
- tag: component
value: airflow
- uuid: 11111111111111111111111111111003
name: 'Airflow Monitor: Heartbeat'
key: airflow.monitor.heartbeat
type: ZABBIX_PASSIVE
value_type: UNSIGNED
delay: 1m
history: 7d
description: 'Epoch timestamp последнего успешного цикла мониторинга.'
tags:
- tag: component
value: dag-monitor
- uuid: 11111111111111111111111111111004
name: 'Airflow Monitor: Status (JSON)'
key: airflow.monitor.status
type: ZABBIX_PASSIVE
value_type: TEXT
delay: 5m
history: 7d
description: 'JSON со статусом монитора: cycle_count, dag_count, issue_count, cycle_time.'
tags:
- tag: component
value: dag-monitor
- uuid: 11111111111111111111111111111005
name: 'Airflow Monitor: Service alive'
key: airflow.monitor.alive
type: ZABBIX_PASSIVE
value_type: UNSIGNED
delay: 1m
history: 90d
trends: 365d
description: '1 = сервис airflow-monitor запущен, 0 = остановлен.'
tags:
- tag: component
value: dag-monitor
# ================================================================
# Вычисляемые items (из heartbeat)
# ================================================================
- uuid: 11111111111111111111111111111006
name: 'Airflow Monitor: Data age (seconds)'
key: airflow.monitor.data_age
type: CALCULATED
value_type: UNSIGNED
delay: 1m
history: 7d
params: 'now()-last(//airflow.monitor.heartbeat)'
description: 'Сколько секунд прошло с последнего обновления данных.'
tags:
- tag: component
value: dag-monitor
# ================================================================
# Dependent items (извлечение из status JSON)
# ================================================================
- uuid: 11111111111111111111111111111010
name: 'Airflow Monitor: DAG count'
key: airflow.monitor.dag_count
type: DEPENDENT
value_type: UNSIGNED
history: 90d
trends: 365d
description: 'Количество отслеживаемых DAG-ов (из status.json).'
master_item:
key: airflow.monitor.status
preprocessing:
- type: JSONPATH
parameters:
- '$.dag_count'
- type: DISCARD_UNCHANGED_HEARTBEAT
parameters:
- 1h
tags:
- tag: component
value: airflow
- uuid: 11111111111111111111111111111011
name: 'Airflow Monitor: Cycle time (seconds)'
key: airflow.monitor.cycle_time
type: DEPENDENT
value_type: FLOAT
history: 90d
trends: 365d
description: 'Время выполнения последнего цикла мониторинга (секунды).'
master_item:
key: airflow.monitor.status
preprocessing:
- type: JSONPATH
parameters:
- '$.cycle_time_seconds'
tags:
- tag: component
value: dag-monitor
- uuid: 11111111111111111111111111111012
name: 'Airflow Monitor: Cycle count'
key: airflow.monitor.cycle_count
type: DEPENDENT
value_type: UNSIGNED
history: 90d
trends: 365d
description: 'Номер текущего цикла мониторинга (с момента запуска сервиса).'
master_item:
key: airflow.monitor.status
preprocessing:
- type: JSONPATH
parameters:
- '$.cycle_count'
- type: DISCARD_UNCHANGED_HEARTBEAT
parameters:
- 1h
tags:
- tag: component
value: dag-monitor
# ==================================================================
# Triggers
# ==================================================================
triggers:
- uuid: 22222222222222222222222222222001
expression: 'last(/Airflow DAG Monitor/airflow.dag.problems.count)>0'
name: 'Airflow: {ITEM.LASTVALUE1} problematic DAG run(s) detected'
priority: HIGH
description: |
Обнаружены проблемные DAG-запуски (failed или long_running).
Подробности в item "Airflow: Problematic DAGs (JSON)".
Триггер снимается автоматически, когда все проблемы разрешены.
tags:
- tag: scope
value: availability
- tag: component
value: airflow
- uuid: 22222222222222222222222222222002
expression: 'last(/Airflow DAG Monitor/airflow.monitor.data_age)>600'
name: 'Airflow Monitor: Data is stale (no update for {ITEM.LASTVALUE1}s)'
priority: DISASTER
description: |
Данные мониторинга не обновлялись более 10 минут.
Возможные причины: сервис остановлен, Airflow API недоступен,
ошибка в цикле мониторинга.
dependencies:
- name: 'Airflow Monitor: Service is not running'
expression: 'last(/Airflow DAG Monitor/airflow.monitor.alive)=0'
tags:
- tag: scope
value: availability
- tag: component
value: dag-monitor
- uuid: 22222222222222222222222222222003
expression: 'last(/Airflow DAG Monitor/airflow.monitor.alive)=0'
name: 'Airflow Monitor: Service is not running'
priority: HIGH
description: |
Сервис airflow-monitor не запущен (systemctl is-active вернул не-active).
Запустить: sudo systemctl start airflow-monitor
tags:
- tag: scope
value: availability
- tag: component
value: dag-monitor
- uuid: 22222222222222222222222222222004
expression: 'last(/Airflow DAG Monitor/airflow.monitor.cycle_time)>60'
name: 'Airflow Monitor: Cycle time is high ({ITEM.LASTVALUE1}s)'
priority: WARNING
description: |
Цикл мониторинга выполняется дольше 60 секунд.
Возможные причины: большое количество DAG-ов, медленный API,
проблемы с сетью до Airflow.
tags:
- tag: scope
value: performance
- tag: component
value: dag-monitor
- uuid: 22222222222222222222222222222005
expression: 'last(/Airflow DAG Monitor/airflow.dag.problems.count)>0 and length(last(/Airflow DAG Monitor/airflow.dag.problems))>2'
recovery_mode: RECOVERY_EXPRESSION
recovery_expression: 'last(/Airflow DAG Monitor/airflow.dag.problems.count)=0'
name: 'Airflow: DAG failures require attention'
priority: AVERAGE
description: |
Есть проблемные DAG-запуски после попытки автоматического перезапуска.
Требуется ручное вмешательство.
tags:
- tag: scope
value: notice
- tag: component
value: airflow
# ==================================================================
# Graphs
# ==================================================================
graphs:
- uuid: 33333333333333333333333333333001
name: 'Airflow: Problem DAG count'
graph_items:
- color: FF0000
item:
host: 'Airflow DAG Monitor'
key: airflow.dag.problems.count
- color: 00AA00
yaxisside: RIGHT
item:
host: 'Airflow DAG Monitor'
key: airflow.monitor.dag_count
- uuid: 33333333333333333333333333333002
name: 'Airflow Monitor: Cycle time'
graph_items:
- color: 0000FF
item:
host: 'Airflow DAG Monitor'
key: airflow.monitor.cycle_time
- uuid: 33333333333333333333333333333003
name: 'Airflow Monitor: Data freshness'
graph_items:
- color: FF6600
item:
host: 'Airflow DAG Monitor'
key: airflow.monitor.data_age
# ==================================================================
# Macros (для тюнинга через host-level overrides)
# ==================================================================
macros:
- macro: '{$AIRFLOW.STALE_THRESHOLD}'
value: '600'
description: 'Порог устаревания данных (секунды). По умолчанию 10 минут.'
- macro: '{$AIRFLOW.CYCLE_TIME_WARN}'
value: '60'
description: 'Порог предупреждения о долгом цикле (секунды).'
+347
View File
@@ -0,0 +1,347 @@
<?xml version="1.0" encoding="UTF-8"?>
<zabbix_export>
<version>5.4</version>
<groups>
<group>
<name>Templates/Applications</name>
</group>
</groups>
<templates>
<template>
<template>Airflow DAG Monitor</template>
<name>Airflow DAG Monitor</name>
<description>Мониторинг Apache Airflow DAG-ов через сервис airflow-monitor.&#13;
Сервис пишет данные в файлы, Zabbix Agent читает через UserParameter.&#13;
&#13;
Требования:&#13;
- Установлен airflow-monitor (/opt/airflow-monitor)&#13;
- Установлен конфиг /etc/zabbix/zabbix_agentd.conf.d/airflow-monitor.conf&#13;
&#13;
Версия шаблона: 1.0.0 (Zabbix 5.x)</description>
<groups>
<group>
<name>Templates/Applications</name>
</group>
</groups>
<items>
<!-- Problematic DAGs JSON -->
<item>
<name>Airflow: Problematic DAGs (JSON)</name>
<key>airflow.dag.problems</key>
<delay>1m</delay>
<history>7d</history>
<value_type>TEXT</value_type>
<description>JSON-массив проблемных DAG-запусков. [] = нет проблем.</description>
<tags>
<tag>
<tag>component</tag>
<value>airflow</value>
</tag>
</tags>
</item>
<!-- Problematic DAGs count -->
<item>
<name>Airflow: Problematic DAGs count</name>
<key>airflow.dag.problems.count</key>
<delay>1m</delay>
<history>90d</history>
<trends>365d</trends>
<value_type>UNSIGNED</value_type>
<description>Количество DAG-запусков с проблемами.</description>
<tags>
<tag>
<tag>component</tag>
<value>airflow</value>
</tag>
</tags>
</item>
<!-- Heartbeat -->
<item>
<name>Airflow Monitor: Heartbeat</name>
<key>airflow.monitor.heartbeat</key>
<delay>1m</delay>
<history>7d</history>
<value_type>UNSIGNED</value_type>
<description>Epoch timestamp последнего успешного цикла мониторинга.</description>
<tags>
<tag>
<tag>component</tag>
<value>dag-monitor</value>
</tag>
</tags>
</item>
<!-- Status JSON -->
<item>
<name>Airflow Monitor: Status (JSON)</name>
<key>airflow.monitor.status</key>
<delay>5m</delay>
<history>7d</history>
<value_type>TEXT</value_type>
<description>JSON со статусом монитора.</description>
<tags>
<tag>
<tag>component</tag>
<value>dag-monitor</value>
</tag>
</tags>
</item>
<!-- Service alive -->
<item>
<name>Airflow Monitor: Service alive</name>
<key>airflow.monitor.alive</key>
<delay>1m</delay>
<history>90d</history>
<trends>365d</trends>
<value_type>UNSIGNED</value_type>
<description>1 = сервис запущен, 0 = остановлен.</description>
<valuemap>
<name>Service state</name>
</valuemap>
<tags>
<tag>
<tag>component</tag>
<value>dag-monitor</value>
</tag>
</tags>
</item>
<!-- DAG count (dependent from status) -->
<item>
<name>Airflow Monitor: DAG count</name>
<type>DEPENDENT</type>
<key>airflow.monitor.dag_count</key>
<history>90d</history>
<trends>365d</trends>
<value_type>UNSIGNED</value_type>
<description>Количество отслеживаемых DAG-ов.</description>
<master_item>
<key>airflow.monitor.status</key>
</master_item>
<preprocessing>
<step>
<type>JSONPATH</type>
<parameters>
<parameter>$.dag_count</parameter>
</parameters>
</step>
<step>
<type>DISCARD_UNCHANGED_HEARTBEAT</type>
<parameters>
<parameter>1h</parameter>
</parameters>
</step>
</preprocessing>
<tags>
<tag>
<tag>component</tag>
<value>airflow</value>
</tag>
</tags>
</item>
<!-- Cycle time (dependent from status) -->
<item>
<name>Airflow Monitor: Cycle time (seconds)</name>
<type>DEPENDENT</type>
<key>airflow.monitor.cycle_time</key>
<history>90d</history>
<trends>365d</trends>
<value_type>FLOAT</value_type>
<units>s</units>
<description>Время выполнения последнего цикла мониторинга.</description>
<master_item>
<key>airflow.monitor.status</key>
</master_item>
<preprocessing>
<step>
<type>JSONPATH</type>
<parameters>
<parameter>$.cycle_time_seconds</parameter>
</parameters>
</step>
</preprocessing>
<tags>
<tag>
<tag>component</tag>
<value>dag-monitor</value>
</tag>
</tags>
</item>
<!-- Cycle count (dependent from status) -->
<item>
<name>Airflow Monitor: Cycle count</name>
<type>DEPENDENT</type>
<key>airflow.monitor.cycle_count</key>
<history>90d</history>
<trends>365d</trends>
<value_type>UNSIGNED</value_type>
<description>Номер текущего цикла мониторинга.</description>
<master_item>
<key>airflow.monitor.status</key>
</master_item>
<preprocessing>
<step>
<type>JSONPATH</type>
<parameters>
<parameter>$.cycle_count</parameter>
</parameters>
</step>
<step>
<type>DISCARD_UNCHANGED_HEARTBEAT</type>
<parameters>
<parameter>1h</parameter>
</parameters>
</step>
</preprocessing>
<tags>
<tag>
<tag>component</tag>
<value>dag-monitor</value>
</tag>
</tags>
</item>
</items>
<triggers>
<!-- Problematic DAGs detected -->
<trigger>
<expression>{Airflow DAG Monitor:airflow.dag.problems.count.last()}&gt;0</expression>
<recovery_expression>{Airflow DAG Monitor:airflow.dag.problems.count.last()}=0</recovery_expression>
<name>Airflow: {ITEM.LASTVALUE1} problematic DAG run(s) detected</name>
<priority>HIGH</priority>
<description>Обнаружены проблемные DAG-запуски (failed или long_running).&#13;
Подробности в item "Airflow: Problematic DAGs (JSON)".&#13;
Триггер снимается автоматически при разрешении всех проблем.</description>
<tags>
<tag>
<tag>scope</tag>
<value>availability</value>
</tag>
</tags>
</trigger>
<!-- Heartbeat stale -->
<trigger>
<expression>{Airflow DAG Monitor:airflow.monitor.heartbeat.nodata(600)}=1 or (({Airflow DAG Monitor:airflow.monitor.heartbeat.last()}&gt;0) and ({Airflow DAG Monitor:airflow.monitor.heartbeat.fuzzytime(600)}=0))</expression>
<name>Airflow Monitor: Data is stale (no update for 10+ minutes)</name>
<priority>DISASTER</priority>
<description>Данные мониторинга не обновлялись более 10 минут.&#13;
Возможные причины: сервис остановлен, Airflow API недоступен.</description>
<dependencies>
<dependency>
<name>Airflow Monitor: Service is not running</name>
<expression>{Airflow DAG Monitor:airflow.monitor.alive.last()}=0</expression>
</dependency>
</dependencies>
<tags>
<tag>
<tag>scope</tag>
<value>availability</value>
</tag>
</tags>
</trigger>
<!-- Service down -->
<trigger>
<expression>{Airflow DAG Monitor:airflow.monitor.alive.last()}=0</expression>
<name>Airflow Monitor: Service is not running</name>
<priority>HIGH</priority>
<description>Сервис airflow-monitor не запущен.&#13;
Запустить: sudo systemctl start airflow-monitor</description>
<tags>
<tag>
<tag>scope</tag>
<value>availability</value>
</tag>
</tags>
</trigger>
<!-- Slow cycle -->
<trigger>
<expression>{Airflow DAG Monitor:airflow.monitor.cycle_time.last()}&gt;60</expression>
<name>Airflow Monitor: Cycle time is high ({ITEM.LASTVALUE1}s)</name>
<priority>WARNING</priority>
<description>Цикл мониторинга выполняется дольше 60 секунд.</description>
<tags>
<tag>
<tag>scope</tag>
<value>performance</value>
</tag>
</tags>
</trigger>
</triggers>
<graphs>
<!-- Problem count graph -->
<graph>
<name>Airflow: Problem DAG count</name>
<graph_items>
<graph_item>
<color>FF0000</color>
<item>
<host>Airflow DAG Monitor</host>
<key>airflow.dag.problems.count</key>
</item>
</graph_item>
<graph_item>
<color>00AA00</color>
<yaxisside>RIGHT</yaxisside>
<item>
<host>Airflow DAG Monitor</host>
<key>airflow.monitor.dag_count</key>
</item>
</graph_item>
</graph_items>
</graph>
<!-- Cycle time graph -->
<graph>
<name>Airflow Monitor: Cycle time</name>
<graph_items>
<graph_item>
<color>0000FF</color>
<item>
<host>Airflow DAG Monitor</host>
<key>airflow.monitor.cycle_time</key>
</item>
</graph_item>
</graph_items>
</graph>
</graphs>
<macros>
<macro>
<macro>{$AIRFLOW.STALE_THRESHOLD}</macro>
<value>600</value>
<description>Порог устаревания данных (секунды).</description>
</macro>
<macro>
<macro>{$AIRFLOW.CYCLE_TIME_WARN}</macro>
<value>60</value>
<description>Порог предупреждения о долгом цикле (секунды).</description>
</macro>
</macros>
</template>
</templates>
<value_maps>
<value_map>
<name>Service state</name>
<mappings>
<mapping>
<value>0</value>
<newvalue>Stopped</newvalue>
</mapping>
<mapping>
<value>1</value>
<newvalue>Running</newvalue>
</mapping>
</mappings>
</value_map>
</value_maps>
</zabbix_export>