Шаги: создать 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.
Архитектура решения
Шаг 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
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
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
Проверка работы
Проверяем 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 ускорят диагностику в разы.
Оставайтесь на связи
Рецепты от IT-боли. Без воды, без рекламы, без маркетинговой шелухи.
Подписаться на IT-Аптеку →



Поднял relay, Zabbix уведомления приходят в Telegram. Как добавить несколько получателей?
Создай несколько endpoint в конфиге — один для обычных алертов, другой для критических. Проще всего: добавь обоих администраторов в одну Telegram группу и шли туда. Для разного приоритета — разные webhook URL в Zabbix с разными chat_id.
Grafana алерты через этот же relay можно настроить или нужна отдельная интеграция?
Через тот же relay — да. В Grafana настрой Contact Point типа Webhook, укажи URL relay. Формат от Grafana отличается от Zabbix, поэтому нужен отдельный обработчик для Grafana формата. В статье есть примеры для основных источников.
CrowdSec уведомления настроил — приходят когда кто-то атакует. Но слишком много, несколько в минуту. Как фильтровать?
Варианты: 1) дедупликация в relay — одинаковые события за N секунд группировать в одно, 2) в CrowdSec настрой минимальный порог, 3) digest режим — собирай события за час и присылай сводку. Для активного сервера несколько алертов в минуту норма, но для читаемости лучше группировать.