Relay Gunicorn для алертов через Telegram: настройка с нуля и подключение Zabbix, Grafana, CrowdSec, The Dude

Настройка relay на Gunicorn для отправки алертов в Telegram. Подключение Alertmanager, Zabbix, Grafana, CrowdSec, The Dude. Решение проблемы с блокировками РКН. Защита через fail2ban.
Быстрый ответ
Relay на Gunicorn — это Python Flask-приложение, которое принимает webhook-запросы от систем мониторинга и пересылает их в Telegram через Bot API. Настройка занимает 20-30 минут.

Шаги: создать Telegram-бота через @BotFather, получить токен и chat_id, развернуть Flask-приложение, запустить через Gunicorn + systemd, настроить fail2ban для защиты эндпоинта.

Подключается к: Alertmanager (Prometheus), Zabbix, Grafana, CrowdSec, The Dude (MikroTik). Все системы шлют POST/GET на один эндпоинт.

Зачем это нужно, если есть встроенная интеграция

Начало 2025 года. Ты поднимаешь мониторинг, настраиваешь Alertmanager, добавляешь telegram_config. Идёшь спать спокойно. В 3 ночи падает сервис. Алерт не приходит. Потому что РКН снова «частично ограничил» работу Telegram в России.

С августа 2025 года Роскомнадзор официально ввёл ограничения на звонки в Telegram и WhatsApp. До этого были замедления в феврале 2026-го — задержки в доставке сообщений достигали нескольких минут. Прямые запросы к api.telegram.org с российских серверов периодически не проходят или идут с огромными задержками.

Это прямой удар по инфраструктуре мониторинга. Alertmanager не умеет ретраить через прокси. Zabbix пишет «Couldn’t connect to server» и молчит. Grafana просто не отправляет уведомление.

Relay на Gunicorn решает три проблемы сразу:

  • Централизованная точка отправки — все системы мониторинга шлют алерты в одно место, а relay уже разбирается с доставкой
  • Прокси через VPS за рубежом — relay разворачивается на сервере вне зоны ограничений, и доступ к Telegram Bot API всегда работает
  • Единый формат сообщений — нормализуешь JSON от Alertmanager, Zabbix, CrowdSec в читаемые Telegram-сообщения в одном месте, не в пяти разных конфигах

Ещё один сценарий: у тебя homelab или корпоративная сеть, и несколько систем мониторинга работают в изолированном сегменте без прямого доступа в интернет. Relay стоит в DMZ или на пограничном узле и принимает запросы изнутри, а наружу ходит только он.

Что ты получишь из этой статьи: рабочий Python-relay на Flask + Gunicorn, настроенный как systemd-сервис, с защитой через fail2ban, и примеры подключения для Alertmanager, Zabbix, Grafana, CrowdSec и The Dude. Всё с реальными конфигами, без абстракций.

Системные требования

Компонент Минимум Рекомендовано
ОС Ubuntu 22.04 LTS Ubuntu 24.04 LTS
Python 3.10 3.12
Gunicorn 21.x 23.x
Flask 3.0 3.1
RAM 256 MB 512 MB
Диск 1 GB 5 GB (логи)
fail2ban 1.0.x 1.1.x

На момент публикации актуальна версия Gunicorn 23.0 и Flask 3.1. Перед установкой проверь свежие релизы на PyPI.

Архитектура решения

%%{init: {
  'theme': 'base',
  'themeVariables': {
    'primaryColor': '#ffffff',
    'primaryTextColor': '#1e293b',
    'primaryBorderColor': '#94a3b8',
    'lineColor': '#64748b',
    'fontSize': '15px',
    'fontFamily': 'ui-sans-serif, system-ui, sans-serif'
  },
  'flowchart': {'curve': 'linear', 'nodeSpacing': 50, 'rankSpacing': 50}
}}%%
flowchart TD
    A["Alertmanager"] --> R
    B["Zabbix"] --> R
    C["Grafana"] --> R
    D["CrowdSec"] --> R
    E["The Dude"] --> R
    R["Gunicorn Relay :8000"] --> F["fail2ban защита"]
    F --> G["Flask /alert endpoint"]
    G --> H["Telegram Bot API"]
    H --> I["Ваш чат / группа"]
    style A fill:#f8fafc,stroke:#3b82f6,stroke-width:2px,color:#1e40af
    style B fill:#f8fafc,stroke:#3b82f6,stroke-width:2px,color:#1e40af
    style C fill:#f8fafc,stroke:#3b82f6,stroke-width:2px,color:#1e40af
    style D fill:#f8fafc,stroke:#3b82f6,stroke-width:2px,color:#1e40af
    style E fill:#f8fafc,stroke:#3b82f6,stroke-width:2px,color:#1e40af
    style R fill:#f8fafc,stroke:#f97316,stroke-width:2px,color:#c2410c
    style F fill:#f8fafc,stroke:#ef4444,stroke-width:2px,color:#dc2626
    style G fill:#f8fafc,stroke:#f97316,stroke-width:2px,color:#c2410c
    style H fill:#f8fafc,stroke:#22c55e,stroke-width:2px,color:#15803d
    style I fill:#f8fafc,stroke:#22c55e,stroke-width:2px,color:#15803d

Шаг 1. Создаём Telegram-бота

Открой Telegram. Найди @BotFather. Отправь команду /newbot и следуй инструкциям — задай имя бота и username (должен заканчиваться на bot).

BotFather выдаст токен вида 1234567890:AAHbcdefGHIJKLMNOPQRSTUVWXYZ. Сохрани его — он понадобится в конфиге.

Теперь узнай chat_id. Добавь бота в нужный чат или группу, затем выполни запрос:


curl -s "https://api.telegram.org/bot<ВАШ_ТОКЕН>/getUpdates" | python3 -m json.tool

Найди в ответе поле chat.id. Для личного чата это положительное число, для группы — отрицательное (например, -1001234567890).

Проверь доступность Bot API
Если запрос выше зависает или возвращает ошибку — ты на российском сервере и api.telegram.org недоступен. Устанавливай relay на зарубежный VPS, а не на продакшн-сервер внутри РФ.

# Быстрая проверка доступности Bot API
curl -m 5 -s "https://api.telegram.org/bot<ТОКЕН>/getMe" && echo "OK" || echo "BLOCKED"

Шаг 2. Готовим окружение

Создаём отдельного пользователя для relay. Это обязательно — не запускай сервисы от root.


useradd -m -s /bin/bash tgrelay
mkdir -p /opt/tgrelay
chown tgrelay:tgrelay /opt/tgrelay

Ставим зависимости и создаём виртуальное окружение:


apt update && apt install -y python3.12 python3.12-venv python3-pip fail2ban
su - tgrelay
cd /opt/tgrelay
python3.12 -m venv venv
source venv/bin/activate
pip install flask gunicorn requests

Шаг 3. Пишем relay-приложение

Создай файл /opt/tgrelay/app.py. Это сердце всей конструкции — Flask-приложение принимает запросы, форматирует сообщения и отправляет их в Telegram.


import os
import logging
import requests
from flask import Flask, request, jsonify

app = Flask(__name__)

# --- Конфигурация ---
TG_TOKEN = os.environ.get("TG_TOKEN", "")
TG_CHAT_ID = os.environ.get("TG_CHAT_ID", "")
SECRET_TOKEN = os.environ.get("SECRET_TOKEN", "changeme")

# --- Логирование ---
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(message)s",
    handlers=[
        logging.FileHandler("/opt/tgrelay/relay.log"),
        logging.StreamHandler()
    ]
)
log = logging.getLogger(__name__)


def send_telegram(text: str) -> bool:
    url = f"https://api.telegram.org/bot{TG_TOKEN}/sendMessage"
    payload = {
        "chat_id": TG_CHAT_ID,
        "text": text,
        "parse_mode": "HTML"
    }
    try:
        r = requests.post(url, json=payload, timeout=10)
        r.raise_for_status()
        return True
    except Exception as e:
        log.error(f"Telegram send error: {e}")
        return False


def format_alertmanager(data: dict) -> str:
    lines = []
    for alert in data.get("alerts", []):
        status = alert.get("status", "unknown").upper()
        name = alert.get("labels", {}).get("alertname", "Unknown")
        severity = alert.get("labels", {}).get("severity", "")
        instance = alert.get("labels", {}).get("instance", "")
        summary = alert.get("annotations", {}).get("summary", "")
        icon = "🔴" if status == "FIRING" else "✅"
        lines.append(
            f"{icon} [{status}] {name}\n"
            f"Severity: {severity}\n"
            f"Instance: {instance}\n"
            f"Summary: {summary}"
        )
    return "\n\n".join(lines) if lines else "Empty alert payload"


@app.route("/alert", methods=["POST"])
def alert():
    token = request.headers.get("X-Token", "")
    if token != SECRET_TOKEN:
        log.warning(f"Unauthorized request from {request.remote_addr}")
        return jsonify({"error": "unauthorized"}), 403

    data = request.get_json(silent=True) or {}
    source = request.args.get("source", "generic")

    if source == "alertmanager":
        text = format_alertmanager(data)
    elif source == "zabbix":
        subject = data.get("subject", "Zabbix Alert")
        message = data.get("message", "")
        text = f"Zabbix: {subject}\n{message}"
    elif source == "crowdsec":
        ip = data.get("ip", "unknown")
        reason = data.get("reason", "")
        text = f"CrowdSec ban: {ip}\nReason: {reason}" else: # Универсальный fallback: передаём любой текст text = data.get("text", str(data)) if not text: return jsonify({"error": "empty message"}), 400 ok = send_telegram(text) if ok: log.info(f"Alert sent from {request.remote_addr}, source={source}") return jsonify({"status": "sent"}), 200 else: return jsonify({"error": "telegram delivery failed"}), 502 @app.route("/health", methods=["GET"]) def health(): return jsonify({"status": "ok"}), 200 if __name__ == "__main__": app.run(host="127.0.0.1", port=8000) 

Создай файл конфигурации /opt/tgrelay/config.env:


TG_TOKEN=1234567890:AAHbcdefGHIJKLMNOPQRSTUVWXYZ
TG_CHAT_ID=-1001234567890
SECRET_TOKEN=supersecrettoken123

chmod 600 /opt/tgrelay/config.env
chown tgrelay:tgrelay /opt/tgrelay/config.env

Шаг 4. Настраиваем Gunicorn

Создай файл конфигурации Gunicorn /opt/tgrelay/gunicorn.conf.py:


bind = "127.0.0.1:8000"
workers = 2
worker_class = "sync"
timeout = 30
keepalive = 5
accesslog = "/opt/tgrelay/access.log"
errorlog = "/opt/tgrelay/error.log"
loglevel = "info"
proc_name = "tgrelay"
pidfile = "/opt/tgrelay/gunicorn.pid"
user = "tgrelay"
group = "tgrelay"

Два воркера достаточно — relay не CPU-hungry, основная задача это I/O ожидание ответа от Telegram. Если у тебя больше 10 источников алертов и высокая частота — подними до 4.

Шаг 5. Systemd-сервис

Создай файл /etc/systemd/system/tgrelay.service:


[Unit]
Description=Telegram Alert Relay (Gunicorn)
After=network.target
Wants=network-online.target

[Service]
Type=notify
User=tgrelay
Group=tgrelay
WorkingDirectory=/opt/tgrelay
EnvironmentFile=/opt/tgrelay/config.env
ExecStart=/opt/tgrelay/venv/bin/gunicorn \
    --config /opt/tgrelay/gunicorn.conf.py \
    app:app
ExecReload=/bin/kill -s HUP $MAINPID
Restart=on-failure
RestartSec=5
StandardError=journal

[Install]
WantedBy=multi-user.target

systemctl daemon-reload
systemctl enable tgrelay
systemctl start tgrelay
systemctl status tgrelay

Проверь что relay слушает:


ss -tlnp | grep 8000
curl -s http://127.0.0.1:8000/health

Должен вернуть {"status": "ok"}. Если видишь это — сервис живой, двигаемся дальше.

Шаг 6. Nginx как reverse proxy (опционально, но правильно)

Не выставляй Gunicorn напрямую в интернет. Поставь перед ним Nginx — он отдаёт rate limiting, TLS-терминацию и скрывает детали реализации.


server {
    listen 443 ssl;
    server_name relay.example.com;

    ssl_certificate /etc/letsencrypt/live/relay.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/relay.example.com/privkey.pem;

    # Rate limiting для защиты от спама
    limit_req_zone $binary_remote_addr zone=relay:10m rate=10r/m;

    location /alert {
        limit_req zone=relay burst=5 nodelay;
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_read_timeout 30s;
    }

    location /health {
        proxy_pass http://127.0.0.1:8000;
    }
}

nginx -t && systemctl reload nginx

Шаг 7. Защита через fail2ban

Relay открыт к интернету — значит, его будут сканировать. Fail2ban блокирует адреса, которые долбятся с неверным токеном или слишком часто. Настройка минимальная, но работает.

Создай фильтр /etc/fail2ban/filter.d/tgrelay.conf:


[Definition]
failregex = .*Unauthorized request from .*
            .*"(GET|POST|PUT|DELETE) /alert.* (401|403).*
ignoreregex =

Создай jail /etc/fail2ban/jail.d/tgrelay.local:


[tgrelay]
enabled = true
port = http,https,8000
filter = tgrelay
logpath = /opt/tgrelay/relay.log
          /var/log/nginx/access.log
maxretry = 5
findtime = 300
bantime = 3600
action = iptables-multiport[name=tgrelay, port="80,443,8000", protocol=tcp]
         %(action_mwl)s

Теперь добавь отправку уведомления в Telegram при каждом бане. Создай action-файл /etc/fail2ban/action.d/telegram-notify.conf:


[Definition]
actionban = curl -s -X POST \
    "https://api.telegram.org/bot%(tg_token)s/sendMessage" \
    -d "chat_id=%(tg_chat_id)s" \
    -d "text=🚫 fail2ban ban: %0AJail: %(name)s%0ARetries: %(failures)s"

actionunban = curl -s -X POST \
    "https://api.telegram.org/bot%(tg_token)s/sendMessage" \
    -d "chat_id=%(tg_chat_id)s" \
    -d "text=✅ fail2ban unban: %0AJail: %(name)s"

[Init]
tg_token = 1234567890:AAHbcdefGHIJKLMNOPQRSTUVWXYZ
tg_chat_id = -1001234567890

Добавь этот action к jail (в тот же tgrelay.local, секция action):


action = iptables-multiport[name=tgrelay, port="80,443,8000", protocol=tcp]
         telegram-notify[name=%(__name__)s]
Внимание: fail2ban и российские блокировки
Если relay стоит на российском сервере, curl в action_ban тоже может не достучаться до api.telegram.org. Используй для action только relay на зарубежном VPS или MTProto-прокси. Лучший вариант — отправлять fail2ban алерты через сам relay с localhost.

# Перезапускаем fail2ban
systemctl restart fail2ban

# Проверяем статус
fail2ban-client status tgrelay

# Тестируем бан вручную
fail2ban-client set tgrelay banip 1.2.3.4
fail2ban-client status tgrelay
fail2ban-client set tgrelay unbanip 1.2.3.4

Таблица портов

Порт Протокол Назначение Доступен снаружи?
8000 TCP Gunicorn (внутренний) Нет (только 127.0.0.1)
443 TCP Nginx HTTPS -> relay Да (разрешённые IP)
80 TCP HTTP -> редирект на HTTPS Да
9001 TCP Gunicorn stats (опционально) Нет

Подключаем Alertmanager (Prometheus)

С версии Alertmanager 0.24 есть встроенная интеграция с Telegram через telegram_configs. Но она не работает через прокси и не форматирует сообщения гибко. Relay решает обе проблемы.

В alertmanager.yml добавь receiver:


receivers:
  - name: "telegram-relay"
    webhook_configs:
      - url: "https://relay.example.com/alert?source=alertmanager"
        send_resolved: true
        http_config:
          headers:
            X-Token: "supersecrettoken123"

route:
  receiver: "telegram-relay"
  group_by: ['alertname', 'severity']
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 4h

# Проверяем конфиг Alertmanager
amtool check-config /etc/alertmanager/alertmanager.yml

# Перезапускаем
systemctl restart alertmanager

# Тестовый алерт
curl -XPOST http://localhost:9093/api/v1/alerts \
  -H "Content-Type: application/json" \
  -d '[{"labels":{"alertname":"TestAlert","severity":"warning"},"annotations":{"summary":"Test from curl"}}]'

Подключаем Zabbix

Начиная с Zabbix 5.0 есть встроенный webhook для Telegram — он работает через скрипт на JavaScript прямо в Zabbix Server. Из России не достучится. Используем curl-скрипт через наш relay.

Создай скрипт /usr/lib/zabbix/alertscripts/tgrelay.sh:


#!/bin/bash
# Zabbix -> Telegram Relay
# $1 = to (игнорируем, chat_id в relay)
# $2 = subject
# $3 = message

RELAY_URL="https://relay.example.com/alert?source=zabbix"
SECRET="supersecrettoken123"

PAYLOAD=$(cat <

chmod +x /usr/lib/zabbix/alertscripts/tgrelay.sh
chown zabbix:zabbix /usr/lib/zabbix/alertscripts/tgrelay.sh

В веб-интерфейсе Zabbix: Alerts -> Media Types -> Create media type. Тип: Script. Имя скрипта: tgrelay.sh. Параметры: {ALERT.SENDTO}, {ALERT.SUBJECT}, {ALERT.MESSAGE}.

Назначь media type пользователю в Users -> Media -> Add. В поле «Send to» можно поставить любое значение — relay его игнорирует.

Подключаем Grafana

В Grafana Alerting есть нативный Telegram contact point. Он снова идёт напрямую к api.telegram.org. Обходим это через webhook на наш relay.

В Grafana: Alerting -> Contact points -> Add contact point. Выбираем тип Webhook. URL:


https://relay.example.com/alert?source=grafana

HTTP Headers добавь: X-Token: supersecrettoken123.

Grafana шлёт POST с JSON в своём формате. Добавь обработчик в app.py в функцию alert():


elif source == "grafana":
    state = data.get("state", "unknown")
    title = data.get("title", "Grafana Alert")
    message = data.get("message", "")
    rule_url = data.get("ruleUrl", "")
    icon = "🔴" if state == "alerting" else "✅"
    text = (
        f"{icon} Grafana [{state.upper()}]\n"
        f"{title}\n"
        f"{message}\n"
        f'Открыть в Grafana'
    )

# После изменения app.py - перезапускаем relay
systemctl restart tgrelay

# Тест из Grafana: кнопка "Test" в contact point

Подключаем CrowdSec

CrowdSec использует HTTP notification plugin для отправки алертов. Именно через него подключается relay.

Создай /etc/crowdsec/notifications/http_tgrelay.yaml:


type: http
name: http_tgrelay

log_level: info
group_wait: 10s
group_threshold: 5
max_retry: 3

url: https://relay.example.com/alert?source=crowdsec
method: POST
headers:
  Content-Type: application/json
  X-Token: supersecrettoken123

format: |
  {
    "ip": "{{range . }}{{.Source.Value}}{{end}}",
    "reason": "{{range . }}{{.Scenario}}{{end}}",
    "decisions": {{len .}}
  }

В /etc/crowdsec/profiles.yaml добавь notification в секцию decisions:


name: default_ip_remediation
filters:
  - Alert.Remediation == true && Alert.GetScope() == "Ip"
decisions:
  - type: ban
    duration: 4h
notifications:
  - http_tgrelay
on_success: break

systemctl restart crowdsec
cscli notifications test http_tgrelay

Подключаем The Dude (MikroTik)

The Dude работает на RouterOS и не умеет делать HTTP POST. Зато умеет HTTP GET через /tool fetch. Relay поддерживает GET-запросы с текстом в параметре.

Добавь в app.py новый endpoint для GET:


@app.route("/dude", methods=["GET"])
def dude():
    token = request.args.get("token", "")
    if token != SECRET_TOKEN:
        log.warning(f"Dude: unauthorized from {request.remote_addr}")
        return "403 Forbidden", 403

    text = request.args.get("text", "")
    device = request.args.get("device", "unknown")
    event = request.args.get("event", "")

    if not text and device:
        text = f"The Dude: {device}\n{event}"

    if text:
        send_telegram(text)
        return "OK", 200
    return "400 Bad Request", 400

systemctl restart tgrelay

В The Dude создай новый Notification с типом «Execute on server». Используй RouterOS-скрипт:


/tool fetch url="https://relay.example.com/dude?token=supersecrettoken123\
&device=[DeviceName]&event=[EventType]&text=[DeviceName] - [EventType]" \
keep-result=no
The Dude и HTTPS
RouterOS 6.x иногда не принимает Let’s Encrypt сертификаты. Если /tool fetch падает с SSL error — добавь сертификат в Trust List через /certificate import или используй HTTP (порт 80) с redirect на HTTPS только для MikroTik-сегмента.

Проверка работы

Проверяем relay комплексно — статус сервиса, логи, тестовый запрос:


# Статус сервиса
systemctl status tgrelay

# Логи в реальном времени
journalctl -fu tgrelay

# Health check
curl -s http://127.0.0.1:8000/health

# Тестовый алерт от Alertmanager
curl -s -X POST "http://127.0.0.1:8000/alert?source=alertmanager" \
  -H "Content-Type: application/json" \
  -H "X-Token: supersecrettoken123" \
  -d '{"alerts":[{"status":"firing","labels":{"alertname":"TestAlert","severity":"critical","instance":"server1:9100"},"annotations":{"summary":"Test message from curl"}}]}'

# Тест Zabbix-формата
curl -s -X POST "http://127.0.0.1:8000/alert?source=zabbix" \
  -H "Content-Type: application/json" \
  -H "X-Token: supersecrettoken123" \
  -d '{"subject":"PROBLEM: High CPU on db01","message":"CPU load is 95% for 5 minutes"}'

Смотри логи relay:


tail -f /opt/tgrelay/relay.log
tail -f /opt/tgrelay/access.log

Мониторинг самого relay

Relay — это SPOF. Если он упадёт — ты ничего не узнаешь, потому что уведомления будут идти через него же. Нужен независимый watchdog.

Самый простой вариант — cron-скрипт с прямым вызовом Bot API (для зарубежного сервера это работает напрямую):


cat > /opt/tgrelay/watchdog.sh << 'EOF'
#!/bin/bash
HEALTH=$(curl -s -m 5 http://127.0.0.1:8000/health)
if echo "$HEALTH" | grep -q '"ok"'; then
    exit 0
fi
# relay не ответил - шлём напрямую через Bot API
curl -s -X POST "https://api.telegram.org/bot${TG_TOKEN}/sendMessage" \
  -d "chat_id=${TG_CHAT_ID}" \
  -d "text=CRITICAL: tgrelay is DOWN on $(hostname)"
EOF
chmod +x /opt/tgrelay/watchdog.sh

# Добавляем в cron от root
crontab -e
# Добавить строку:
# */5 * * * * TG_TOKEN="ВАШ_ТОКЕН" TG_CHAT_ID="-1001234567890" /opt/tgrelay/watchdog.sh

Резервное копирование

Бэкапить нужно конфиги и код, не логи. Логи ротируй через logrotate.


# Создай /etc/logrotate.d/tgrelay
cat > /etc/logrotate.d/tgrelay << 'EOF'
/opt/tgrelay/*.log {
    daily
    rotate 7
    compress
    delaycompress
    missingok
    notifempty
    postrotate
        systemctl kill -s USR1 tgrelay || true
    endscript
}
EOF

Бэкап конфигов (запускать перед обновлением):


tar czf /root/tgrelay-backup-$(date +%Y%m%d).tar.gz \
  /opt/tgrelay/app.py \
  /opt/tgrelay/gunicorn.conf.py \
  /opt/tgrelay/config.env \
  /etc/systemd/system/tgrelay.service \
  /etc/fail2ban/jail.d/tgrelay.local \
  /etc/fail2ban/filter.d/tgrelay.conf \
  /etc/nginx/sites-available/tgrelay

Обновление relay

Обновление проходит по схеме: бэкап — обновление зависимостей — тест — рестарт.


# Создаём бэкап перед обновлением
tar czf /root/tgrelay-before-update.tar.gz /opt/tgrelay/

# Обновляем зависимости
su - tgrelay -c "
  source /opt/tgrelay/venv/bin/activate
  pip install --upgrade flask gunicorn requests
"

# Проверяем синтаксис app.py
python3 -c "import py_compile; py_compile.compile('/opt/tgrelay/app.py')" && echo "OK"

# Graceful reload без даунтайма
systemctl reload tgrelay
# Если reload не поддерживается - restart
systemctl restart tgrelay

# Проверяем что всё встало
curl -s http://127.0.0.1:8000/health

Диагностика: типичные ошибки

Симптом Причина Решение
403 Forbidden на /alert Неверный X-Token в заголовке Проверить SECRET_TOKEN в config.env и в источнике алертов
Telegram delivery failed Bot API недоступен (блокировка РКН) Relay на зарубежном VPS, проверить curl к api.telegram.org
502 Bad Gateway от Nginx Gunicorn не запущен systemctl status tgrelay, проверить ss -tlnp | grep 8000
Сообщения приходят пустые JSON не парсится (неверный Content-Type) Добавить -H «Content-Type: application/json» в curl-запросы
fail2ban не банит Неверный regex в фильтре или путь к логу fail2ban-regex /opt/tgrelay/relay.log /etc/fail2ban/filter.d/tgrelay.conf
The Dude SSL error RouterOS не принимает сертификат Импортировать CA в /certificate или использовать HTTP на отдельном порту
Zabbix: script не выполняется Нет прав execute у скрипта chmod +x /usr/lib/zabbix/alertscripts/tgrelay.sh
CrowdSec не шлёт уведомления name в YAML не совпадает с profiles.yaml Имя в http_tgrelay.yaml должно совпадать с notifications: — http_tgrelay

# Отладка fail2ban regex
fail2ban-regex /opt/tgrelay/relay.log /etc/fail2ban/filter.d/tgrelay.conf

# Отладка CrowdSec нотификации
cscli notifications test http_tgrelay
journalctl -u crowdsec -n 50

# Проверка что app.py читает переменные окружения
systemctl show tgrelay --property=Environment

Альтернативы: когда relay не нужен

Relay — это дополнительный компонент инфраструктуры. Нужен он не всегда.

Прямая интеграция Alertmanager через telegram_configs работает если сервер находится вне России или ты используешь MTProto-прокси. Grafana с версии 9.0 нативно поддерживает Telegram contact point — настраивается за 2 минуты в UI. CrowdSec официально документирует прямую отправку в Telegram через http plugin без промежуточного relay.

Relay нужен когда: несколько систем мониторинга, сервер в России или за NAT, нужен единый формат сообщений, нужен audit log всех отправленных алертов, нужно добавить логику маршрутизации (critical -> дежурная группа, warning -> общий чат).

Профилактика

Проверяй работу relay раз в неделю — не вручную, а скриптом. Положи в cron тест с настоящим алертом и убедись, что сообщение пришло.


# Еженедельный тест (добавить в cron)
# 0 9 * * 1 /opt/tgrelay/test_weekly.sh

cat > /opt/tgrelay/test_weekly.sh << 'EOF' #!/bin/bash RESULT=$(curl -s -o /dev/null -w "%{http_code}" \ -X POST "http://127.0.0.1:8000/alert?source=zabbix" \ -H "Content-Type: application/json" \ -H "X-Token: supersecrettoken123" \ -d '{"subject":"Weekly relay test","message":"OK - relay is working"}') if [ "$RESULT" = "200" ]; then echo "$(date): weekly test OK" else echo "$(date): weekly test FAILED, HTTP $RESULT" >&2
fi
EOF
chmod +x /opt/tgrelay/test_weekly.sh

FAQ

Почему алерты через Telegram не приходят после настройки?

Первая причина — api.telegram.org недоступен с сервера. Проверь: curl -m 5 https://api.telegram.org. Если timeout — relay нужно ставить на зарубежный VPS или настраивать MTProto-прокси. Вторая причина — неверный токен в config.env. Третья — relay не запущен: systemctl status tgrelay.

Как проверить что relay работает правильно?

Выполни health check: curl -s http://127.0.0.1:8000/health — должен вернуть {"status": "ok"}. Потом отправь тестовый POST с корректным токеном и проверь что сообщение появилось в Telegram. Смотри лог: tail -f /opt/tgrelay/relay.log.

Что делать если fail2ban заблокировал легитимный источник алертов?

Разбань IP командой: fail2ban-client set tgrelay unbanip 1.2.3.4. Потом добавь этот IP в ignoreip в jail.local: ignoreip = 127.0.0.1/8 192.168.0.0/16 1.2.3.4. Перезапусти fail2ban. Также проверь правильность SECRET_TOKEN на стороне источника.

Чем relay на Gunicorn отличается от встроенной интеграции Alertmanager?

Встроенная интеграция работает только с одной системой мониторинга и идёт напрямую к api.telegram.org. Relay принимает запросы от любых источников, нормализует форматы, работает как прокси через безопасный узел, ведёт audit log. Минус — дополнительный компонент который нужно поддерживать.

Можно ли отправлять алерты в несколько Telegram-чатов?

Да. Добавь в app.py словарь CHAT_ROUTING с маппингом source -> chat_id. Например, critical алерты от Alertmanager идут в дежурный чат, Zabbix — в общий чат команды. Передавай нужный чат через query parameter ?chat=oncall.

Что в итоге

Ты развернул relay на Gunicorn с Flask, который принимает алерты от пяти систем мониторинга и отправляет их в Telegram. Fail2ban блокирует попытки подбора токена и отправляет уведомление о каждом бане. Systemd держит сервис живым после перезагрузки. Nginx стоит спереди и ограничивает частоту запросов.

Проблема с блокировками РКН решается установкой relay на зарубежный VPS — все внутренние системы мониторинга шлют алерты на внутренний endpoint, а наружу ходит только relay через стабильный канал.

Если что-то не заработало — пиши в комментарии, разберёмся. Конкретный симптом, версии компонентов и вывод journalctl -fu tgrelay ускорят диагностику в разы.

Андрей Анатольевич
Author: Андрей Анатольевич

Руководитель ИТ / Кризис-менеджер 25 лет в IT: от инженера в МегаФоне до руководителя отдела. Знаю, как выглядит бардак: нестабильные сети, устаревшая инфраструктура, конфликты в команде, раздутые сроки. Помогаю бизнесу выходить из кризиса: навожу порядок в легаси, стабилизирую то, что разваливается, выстраиваю прогнозируемые процессы. Не раз возвращал к жизни ИТ-структуры — знаю цену хаосу. 📍 Ищу проект для полной реорганизации / стабилизации. 📬 Telegram: @over_dude ✉️ mail@it-apteka.com

Оставайтесь на связи

Рецепты от IT-боли. Без воды, без рекламы, без маркетинговой шелухи.

Подписаться на IT-Аптеку →

Мы ВКонтакте

IT-Аптека — советы, новости и помощь рядом.

Вступить в группу ВКонтакте →
Поделитесь:

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Прокрутить вверх