Maksim Totmin bc437cbf8c 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
2026-04-08 17:23:03 +07:00

323 lines
11 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# =============================================================================
# Airflow DAG Monitor - Offline Build Script
#
# Собирает автономный ZIP-архив со всеми зависимостями (wheel-пакеты),
# исходным кодом и скриптами установки. Архив можно перенести в закрытый
# контур без доступа в интернет и развернуть через install.sh.
#
# Использование:
# ./build.sh # сборка для текущей платформы
# ./build.sh --platform manylinux2014_x86_64 # явное указание платформы
# =============================================================================
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
VERSION=$(python3 -c "
import re, pathlib
text = pathlib.Path('$SCRIPT_DIR/airflow_monitor/__init__.py').read_text()
m = re.search(r'__version__\s*=\s*[\\x22\\x27]([^\\x22\\x27]+)', text)
print(m.group(1) if m else '0.0.0')
")
BUILD_DIR="$SCRIPT_DIR/build"
DIST_DIR="$SCRIPT_DIR/dist"
WHEELS_DIR="$BUILD_DIR/wheels"
STAGE_DIR="$BUILD_DIR/airflow-monitor-${VERSION}"
PLATFORM=""
# --- Parse arguments ---
while [[ $# -gt 0 ]]; do
case "$1" in
--platform)
PLATFORM="$2"
shift 2
;;
-h|--help)
echo "Usage: $0 [--platform PLATFORM]"
echo ""
echo "Options:"
echo " --platform Target platform for wheels (e.g., manylinux2014_x86_64)"
echo " Default: auto-detect from current system"
echo ""
echo "Output: dist/airflow-monitor-VERSION.zip"
exit 0
;;
*)
echo "Unknown option: $1" >&2
exit 1
;;
esac
done
echo "=== Airflow DAG Monitor — Offline Build ==="
echo "Version: ${VERSION}"
echo "Build dir: ${BUILD_DIR}"
echo ""
# --- Cleanup previous build ---
rm -rf "$BUILD_DIR"
mkdir -p "$WHEELS_DIR" "$STAGE_DIR" "$DIST_DIR"
# --- Step 1: Download wheel packages ---
echo "[1/4] Downloading dependencies as wheel packages..."
PIP_ARGS=(
download
-r "$SCRIPT_DIR/requirements.txt"
--dest "$WHEELS_DIR"
--quiet
)
if [ -n "$PLATFORM" ]; then
PIP_ARGS+=(--platform "$PLATFORM" --only-binary=:all:)
echo " Target platform: $PLATFORM"
else
echo " Target platform: current ($(python3 -c 'import platform; print(platform.machine())'))"
fi
python3 -m pip "${PIP_ARGS[@]}"
WHEEL_COUNT=$(find "$WHEELS_DIR" -type f | wc -l)
echo " Downloaded $WHEEL_COUNT packages:"
find "$WHEELS_DIR" -type f -printf " %f\n" | sort
# --- Step 2: Stage project files ---
echo "[2/4] Staging project files..."
# Application code
cp -r "$SCRIPT_DIR/airflow_monitor" "$STAGE_DIR/airflow_monitor"
# Remove __pycache__
find "$STAGE_DIR/airflow_monitor" -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true
# Configuration and docs
cp "$SCRIPT_DIR/config.yaml" "$STAGE_DIR/"
cp "$SCRIPT_DIR/requirements.txt" "$STAGE_DIR/"
cp "$SCRIPT_DIR/airflow-monitor.service" "$STAGE_DIR/"
cp "$SCRIPT_DIR/README.md" "$STAGE_DIR/"
# Zabbix agent config
cp -r "$SCRIPT_DIR/zabbix" "$STAGE_DIR/zabbix"
# Wheels directory
cp -r "$WHEELS_DIR" "$STAGE_DIR/wheels"
echo " Staged files:"
find "$STAGE_DIR" -type f -printf " %P\n" | head -30
# --- Step 3: Generate offline install script ---
echo "[3/4] Generating install.sh..."
cat > "$STAGE_DIR/install.sh" << 'INSTALL_SCRIPT'
#!/usr/bin/env bash
# =============================================================================
# Airflow DAG Monitor - Offline Installer
#
# Устанавливает сервис из автономного архива без доступа в интернет.
# Все зависимости включены в каталог wheels/.
#
# Использование:
# ./install.sh # установка в /opt/airflow-monitor
# ./install.sh --prefix /srv/monitor # установка в указанный каталог
# =============================================================================
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
INSTALL_DIR="/opt/airflow-monitor"
SERVICE_USER="$(whoami)"
SERVICE_NAME="airflow-monitor"
# --- Parse arguments ---
while [[ $# -gt 0 ]]; do
case "$1" in
--prefix)
INSTALL_DIR="$2"
shift 2
;;
--user)
SERVICE_USER="$2"
shift 2
;;
-h|--help)
echo "Usage: $0 [--prefix INSTALL_DIR] [--user SERVICE_USER]"
echo ""
echo "Options:"
echo " --prefix Installation directory (default: /opt/airflow-monitor)"
echo " --user User to run the service as (default: current user)"
exit 0
;;
*)
echo "Unknown option: $1" >&2
exit 1
;;
esac
done
VENV_DIR="$INSTALL_DIR/venv"
echo "=== Airflow DAG Monitor — Offline Install ==="
echo "Source: $SCRIPT_DIR"
echo "Install dir: $INSTALL_DIR"
echo "Service user: $SERVICE_USER"
echo ""
# --- Step 1: Copy files if installing to a different directory ---
if [ "$INSTALL_DIR" != "$SCRIPT_DIR" ]; then
echo "[1/5] Copying files to $INSTALL_DIR..."
mkdir -p "$INSTALL_DIR"
cp -r "$SCRIPT_DIR/airflow_monitor" "$INSTALL_DIR/"
cp -r "$SCRIPT_DIR/wheels" "$INSTALL_DIR/"
cp -r "$SCRIPT_DIR/zabbix" "$INSTALL_DIR/"
cp "$SCRIPT_DIR/config.yaml" "$INSTALL_DIR/"
cp "$SCRIPT_DIR/requirements.txt" "$INSTALL_DIR/"
cp "$SCRIPT_DIR/airflow-monitor.service" "$INSTALL_DIR/"
cp "$SCRIPT_DIR/README.md" "$INSTALL_DIR/"
echo " Done"
else
echo "[1/5] Installing in place, skipping copy"
fi
# --- Step 2: Create virtual environment ---
echo "[2/5] Creating Python virtual environment..."
if [ -d "$VENV_DIR" ]; then
rm -rf "$VENV_DIR"
fi
python3 -m venv "$VENV_DIR"
echo " Created: $VENV_DIR"
# --- Step 3: Install dependencies from local wheels ---
echo "[3/5] Installing dependencies from local wheels (offline)..."
"$VENV_DIR/bin/pip" install --upgrade pip --quiet 2>/dev/null || true
"$VENV_DIR/bin/pip" install \
--no-index \
--find-links "$INSTALL_DIR/wheels" \
-r "$INSTALL_DIR/requirements.txt" \
--quiet
echo " Installed packages:"
"$VENV_DIR/bin/pip" list --format=columns 2>/dev/null | grep -vE '^(Package|---)'
echo ""
# --- Step 4: Create required directories ---
echo "[4/5] Creating directories..."
STATE_DIR="/var/lib/airflow-monitor"
LOG_DIR="/var/log/airflow-monitor"
for dir in "$STATE_DIR" "$LOG_DIR"; do
if [ ! -d "$dir" ]; then
sudo mkdir -p "$dir"
sudo chown "${SERVICE_USER}:$(id -gn "$SERVICE_USER")" "$dir"
echo " Created: $dir"
else
echo " Exists: $dir"
fi
done
# --- Step 5: Install systemd service ---
echo "[5/5] Installing systemd service..."
# Patch service file with actual paths and user
SERVICE_FILE="$INSTALL_DIR/airflow-monitor.service"
if [ -f "$SERVICE_FILE" ]; then
TEMP_SERVICE=$(mktemp)
sed \
-e "s|User=.*|User=${SERVICE_USER}|" \
-e "s|Group=.*|Group=$(id -gn "$SERVICE_USER")|" \
-e "s|WorkingDirectory=.*|WorkingDirectory=${INSTALL_DIR}|" \
-e "s|ExecStart=.*|ExecStart=${VENV_DIR}/bin/python -m airflow_monitor --config ${INSTALL_DIR}/config.yaml|" \
"$SERVICE_FILE" > "$TEMP_SERVICE"
sudo cp "$TEMP_SERVICE" /etc/systemd/system/${SERVICE_NAME}.service
rm -f "$TEMP_SERVICE"
sudo systemctl daemon-reload
echo " Installed: /etc/systemd/system/${SERVICE_NAME}.service"
else
echo " WARNING: service file not found, skipping"
fi
# --- Step 6: Install Zabbix agent config ---
echo "[6/6] Installing Zabbix agent config..."
ZABBIX_CONF_SRC="$INSTALL_DIR/zabbix/airflow-monitor.conf"
ZABBIX_CONF_DST="/etc/zabbix/zabbix_agentd.conf.d/airflow-monitor.conf"
if [ -f "$ZABBIX_CONF_SRC" ]; then
if [ -d "/etc/zabbix/zabbix_agentd.conf.d" ]; then
sudo cp "$ZABBIX_CONF_SRC" "$ZABBIX_CONF_DST"
sudo chmod 644 "$ZABBIX_CONF_DST"
echo " Installed: $ZABBIX_CONF_DST"
if systemctl is-active zabbix-agent >/dev/null 2>&1; then
sudo systemctl restart zabbix-agent
echo " Restarted: zabbix-agent"
elif systemctl is-active zabbix-agent2 >/dev/null 2>&1; then
sudo systemctl restart zabbix-agent2
echo " Restarted: zabbix-agent2"
else
echo " WARNING: zabbix-agent not running, restart manually"
fi
else
echo " WARNING: /etc/zabbix/zabbix_agentd.conf.d/ not found"
echo " Copy manually: sudo cp $ZABBIX_CONF_SRC $ZABBIX_CONF_DST"
fi
else
echo " WARNING: zabbix config not found"
fi
# Ensure data dir is readable by zabbix agent
sudo chmod 755 /var/lib/airflow-monitor 2>/dev/null || true
echo ""
echo "=== Install Complete ==="
echo ""
echo "Next steps:"
echo " 1. Edit config.yaml:"
echo " vim $INSTALL_DIR/config.yaml"
echo ""
echo " 2. Test manually:"
echo " $VENV_DIR/bin/python -m airflow_monitor --discover -c $INSTALL_DIR/config.yaml"
echo ""
echo " 3. Enable and start:"
echo " sudo systemctl enable --now $SERVICE_NAME"
echo ""
echo " 4. Check logs:"
echo " journalctl -u $SERVICE_NAME -f"
echo ""
echo " 5. Verify Zabbix agent reads data:"
echo " zabbix_agentd -t airflow.monitor.heartbeat"
echo " zabbix_agentd -t airflow.dag.problems.count"
INSTALL_SCRIPT
chmod +x "$STAGE_DIR/install.sh"
echo " Generated: install.sh"
# --- Step 4: Create ZIP archive ---
echo "[4/4] Creating ZIP archive..."
ARCHIVE_NAME="airflow-monitor-${VERSION}.zip"
ARCHIVE_PATH="$DIST_DIR/$ARCHIVE_NAME"
(cd "$BUILD_DIR" && zip -r "$ARCHIVE_PATH" "airflow-monitor-${VERSION}" -q)
ARCHIVE_SIZE=$(du -h "$ARCHIVE_PATH" | cut -f1)
FILE_COUNT=$(unzip -l "$ARCHIVE_PATH" | tail -1 | awk '{print $2}')
echo " Archive: $ARCHIVE_PATH"
echo " Size: $ARCHIVE_SIZE"
echo " Files: $FILE_COUNT"
# --- Cleanup build directory ---
rm -rf "$BUILD_DIR"
echo ""
echo "=== Build Complete ==="
echo ""
echo "Архив для переноса в закрытый контур:"
echo " $ARCHIVE_PATH"
echo ""
echo "Развёртывание на целевом сервере:"
echo " 1. Скопировать: scp $ARCHIVE_PATH user@target:/tmp/"
echo " 2. Распаковать: unzip /tmp/$ARCHIVE_NAME -d /tmp/"
echo " 3. Установить: cd /tmp/airflow-monitor-${VERSION} && ./install.sh"
echo ""
echo "По умолчанию устанавливается в /opt/airflow-monitor."
echo "Для смены каталога или пользователя:"
echo " ./install.sh --prefix /srv/airflow-monitor --user airflow"