- FastAPI приложение для отправки мониторинговых алертов в мессенджеры - Поддержка Telegram и MAX/VK - Интеграция с Grafana, Zabbix, AlertManager - Автоматическое создание тикетов в Jira - Управление группами мессенджеров через API - Декораторы для авторизации и скрытия эндпоинтов - Подробная документация в папке docs/ Автор: Сергей Антропов Сайт: https://devops.org.ru
404 lines
17 KiB
Python
404 lines
17 KiB
Python
"""
|
||
Эндпоинты для обработки webhooks из систем мониторинга (Grafana, Zabbix, AlertManager).
|
||
|
||
Автор: Сергей Антропов
|
||
Сайт: https://devops.org.ru
|
||
"""
|
||
import logging
|
||
from fastapi import APIRouter, HTTPException, Path, Body, Query
|
||
from typing import Dict, Optional
|
||
|
||
from app.models.grafana import GrafanaAlert
|
||
from app.models.zabbix import ZabbixAlert
|
||
from app.models.alertmanager import PrometheusAlert
|
||
from app.modules.grafana import send as grafana_send
|
||
from app.modules.zabbix import send as zabbix_send
|
||
from app.modules.alertmanager import send as alertmanager_send
|
||
from app.core.metrics import metrics
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
router = APIRouter(tags=["monitoring"])
|
||
|
||
|
||
@router.post(
|
||
"/grafana/{group_name}/{thread_id}",
|
||
name="Отправка вебхуков из Grafana",
|
||
response_model=Dict[str, str],
|
||
summary="Отправить алерт из Grafana",
|
||
description="Эндпоинт для обработки webhooks из Grafana. **Не требует авторизации.**",
|
||
responses={
|
||
200: {
|
||
"description": "Сообщение успешно отправлено",
|
||
"content": {
|
||
"application/json": {
|
||
"example": {"status": "ok", "message": "Сообщение отправлено"}
|
||
}
|
||
}
|
||
},
|
||
400: {
|
||
"description": "Некорректные данные запроса",
|
||
"content": {
|
||
"application/json": {
|
||
"example": {"detail": "Неверный формат данных для Grafana алерта"}
|
||
}
|
||
}
|
||
},
|
||
404: {
|
||
"description": "Группа не найдена",
|
||
"content": {
|
||
"application/json": {
|
||
"example": {"detail": "Группа 'monitoring' не найдена в конфигурации"}
|
||
}
|
||
}
|
||
},
|
||
500: {
|
||
"description": "Ошибка сервера",
|
||
"content": {
|
||
"application/json": {
|
||
"example": {"detail": "Ошибка отправки сообщения"}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
)
|
||
async def send_grafana_alert(
|
||
group_name: str = Path(
|
||
...,
|
||
description="Имя группы из конфигурации (config/groups.json)",
|
||
examples=["monitoring", "alerts", "devops"]
|
||
),
|
||
thread_id: int = Path(
|
||
...,
|
||
description="ID треда в группе (0 для отправки в основную группу без треда, поддерживается только для Telegram)",
|
||
examples=[0, 123, 456]
|
||
),
|
||
messenger: Optional[str] = Query(
|
||
None,
|
||
description="Тип мессенджера (telegram, max). Если не указан, используется из конфигурации группы",
|
||
examples=["telegram", "max"]
|
||
),
|
||
alert: GrafanaAlert = Body(
|
||
...,
|
||
description="Данные алерта из Grafana",
|
||
examples=[
|
||
{
|
||
"title": "[Alerting] High CPU Usage",
|
||
"ruleId": 674180201771804383,
|
||
"ruleName": "High CPU Usage Alert",
|
||
"state": "alerting",
|
||
"evalMatches": [
|
||
{
|
||
"value": 95.5,
|
||
"metric": "cpu_usage_percent",
|
||
"tags": {"host": "server01", "instance": "production"}
|
||
}
|
||
],
|
||
"orgId": 1,
|
||
"dashboardId": 123,
|
||
"panelId": 456,
|
||
"tags": {"severity": "critical", "environment": "production"},
|
||
"ruleUrl": "http://grafana.cism-ms.ru/alerting/list",
|
||
"message": "CPU usage is above 90% threshold for more than 5 minutes"
|
||
},
|
||
{
|
||
"title": "[OK] High CPU Usage",
|
||
"ruleId": 674180201771804383,
|
||
"ruleName": "High CPU Usage Alert",
|
||
"state": "ok",
|
||
"evalMatches": [
|
||
{
|
||
"value": 45.2,
|
||
"metric": "cpu_usage_percent",
|
||
"tags": {"host": "server01", "instance": "production"}
|
||
}
|
||
],
|
||
"orgId": 1,
|
||
"dashboardId": 123,
|
||
"panelId": 456,
|
||
"tags": {"severity": "critical", "environment": "production"},
|
||
"ruleUrl": "http://grafana.cism-ms.ru/alerting/list",
|
||
"message": "CPU usage has returned to normal levels"
|
||
}
|
||
]
|
||
)
|
||
) -> Dict[str, str]:
|
||
"""
|
||
Отправить алерт из Grafana в мессенджер.
|
||
|
||
Принимает webhook от Grafana и отправляет сообщение в указанную группу мессенджера.
|
||
Не требует авторизации (API ключ не нужен).
|
||
|
||
Подробная документация: см. docs/monitoring/grafana.md
|
||
"""
|
||
metrics.increment_api_endpoint("grafana")
|
||
logger.info(f"Получен алерт Grafana для группы {group_name}, тред {thread_id}, мессенджер {messenger}")
|
||
|
||
try:
|
||
await grafana_send(group_name, thread_id, alert, messenger)
|
||
return {
|
||
"status": "ok",
|
||
"message": "Сообщение отправлено"
|
||
}
|
||
except ValueError as e:
|
||
logger.error(f"Ошибка валидации: {e}")
|
||
raise HTTPException(status_code=404, detail=str(e))
|
||
except Exception as e:
|
||
logger.error(f"Ошибка отправки алерта Grafana: {e}")
|
||
raise HTTPException(status_code=500, detail=f"Ошибка отправки сообщения: {str(e)}")
|
||
|
||
|
||
@router.post(
|
||
"/zabbix/{group_name}/{thread_id}",
|
||
name="Отправка вебхуков из Zabbix",
|
||
response_model=Dict[str, str],
|
||
summary="Отправить алерт из Zabbix",
|
||
description="Эндпоинт для обработки webhooks из Zabbix. **Не требует авторизации.**",
|
||
responses={
|
||
200: {
|
||
"description": "Сообщение успешно отправлено",
|
||
"content": {
|
||
"application/json": {
|
||
"example": {"status": "ok", "message": "Сообщение отправлено"}
|
||
}
|
||
}
|
||
},
|
||
400: {
|
||
"description": "Некорректные данные запроса",
|
||
"content": {
|
||
"application/json": {
|
||
"example": {"detail": "Неверный формат данных для Zabbix алерта"}
|
||
}
|
||
}
|
||
},
|
||
404: {
|
||
"description": "Группа не найдена",
|
||
"content": {
|
||
"application/json": {
|
||
"example": {"detail": "Группа 'monitoring' не найдена в конфигурации"}
|
||
}
|
||
}
|
||
},
|
||
500: {
|
||
"description": "Ошибка сервера",
|
||
"content": {
|
||
"application/json": {
|
||
"example": {"detail": "Ошибка отправки сообщения"}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
)
|
||
async def send_zabbix_alert(
|
||
group_name: str = Path(
|
||
...,
|
||
description="Имя группы из конфигурации (config/groups.json)",
|
||
examples=["monitoring", "alerts", "devops"]
|
||
),
|
||
thread_id: int = Path(
|
||
...,
|
||
description="ID треда в группе (0 для отправки в основную группу без треда, поддерживается только для Telegram)",
|
||
examples=[0, 123, 456]
|
||
),
|
||
messenger: Optional[str] = Query(
|
||
None,
|
||
description="Тип мессенджера (telegram, max). Если не указан, используется из конфигурации группы",
|
||
examples=["telegram", "max"]
|
||
),
|
||
alert: ZabbixAlert = Body(
|
||
...,
|
||
description="Данные алерта из Zabbix",
|
||
examples=[
|
||
{
|
||
"link": "https://zabbix.example.com/tr_events.php?triggerid=42667&eventid=8819711",
|
||
"status": "PROBLEM",
|
||
"action-id": "7",
|
||
"alert-subject": "Problem: High CPU utilization (over 90% for 5m)",
|
||
"alert-message": "Problem started at 16:48:44 on 2024.02.08\r\nProblem name: High CPU utilization (over 90% for 5m)\r\nHost: pnode28\r\nSeverity: Warning\r\nCurrent utilization: 95.2 %\r\n",
|
||
"event-id": "8819711",
|
||
"event-name": "High CPU utilization (over 90% for 5m)",
|
||
"event-nseverity": "2",
|
||
"event-opdata": "Current utilization: 95.2 %",
|
||
"event-severity": "Warning",
|
||
"host-name": "pnode28",
|
||
"host-ip": "10.14.253.38",
|
||
"host-port": "10050"
|
||
},
|
||
{
|
||
"link": "https://zabbix.example.com/tr_events.php?triggerid=42667&eventid=8819711",
|
||
"status": "OK",
|
||
"action-id": "7",
|
||
"alert-subject": "Resolved in 1m 0s: High CPU utilization (over 90% for 5m)",
|
||
"alert-message": "Problem has been resolved at 16:49:44 on 2024.02.08\r\nProblem name: High CPU utilization (over 90% for 5m)\r\nProblem duration: 1m 0s\r\nHost: pnode28\r\nSeverity: Warning\r\nOriginal problem ID: 8819711\r\n",
|
||
"event-id": "8819711",
|
||
"event-name": "High CPU utilization (over 90% for 5m)",
|
||
"event-nseverity": "2",
|
||
"event-opdata": "Current utilization: 70.9 %",
|
||
"event-recovery-date": "2024.02.08",
|
||
"event-recovery-time": "16:49:44",
|
||
"event-duration": "1m 0s",
|
||
"event-recovery-name": "High CPU utilization (over 90% for 5m)",
|
||
"event-recovery-status": "RESOLVED",
|
||
"event-recovery-tags": "Application:CPU",
|
||
"event-severity": "Warning",
|
||
"host-name": "pnode28",
|
||
"host-ip": "10.14.253.38",
|
||
"host-port": "10050"
|
||
}
|
||
]
|
||
)
|
||
) -> Dict[str, str]:
|
||
"""
|
||
Отправить алерт из Zabbix в мессенджер.
|
||
|
||
Принимает webhook от Zabbix и отправляет сообщение в указанную группу мессенджера.
|
||
Не требует авторизации (API ключ не нужен).
|
||
|
||
Подробная документация: см. docs/monitoring/zabbix.md
|
||
"""
|
||
metrics.increment_api_endpoint("zabbix")
|
||
logger.info(f"Получен алерт Zabbix для группы {group_name}, тред {thread_id}, мессенджер {messenger}")
|
||
|
||
try:
|
||
await zabbix_send(group_name, thread_id, alert, messenger)
|
||
return {
|
||
"status": "ok",
|
||
"message": "Сообщение отправлено"
|
||
}
|
||
except ValueError as e:
|
||
logger.error(f"Ошибка валидации: {e}")
|
||
raise HTTPException(status_code=404, detail=str(e))
|
||
except Exception as e:
|
||
logger.error(f"Ошибка отправки алерта Zabbix: {e}")
|
||
raise HTTPException(status_code=500, detail=f"Ошибка отправки сообщения: {str(e)}")
|
||
|
||
|
||
@router.post(
|
||
"/alertmanager/{k8s_cluster}/{group_name}/{thread_id}",
|
||
name="Отправка вебхуков из AlertManager",
|
||
response_model=Dict[str, str],
|
||
summary="Отправить алерт из AlertManager",
|
||
description="Эндпоинт для обработки webhooks из AlertManager. **Не требует авторизации.**",
|
||
responses={
|
||
200: {
|
||
"description": "Сообщение успешно отправлено",
|
||
"content": {
|
||
"application/json": {
|
||
"example": {"status": "ok", "message": "Сообщение отправлено"}
|
||
}
|
||
}
|
||
},
|
||
400: {
|
||
"description": "Некорректные данные запроса",
|
||
"content": {
|
||
"application/json": {
|
||
"example": {"detail": "Неверный формат данных для AlertManager алерта"}
|
||
}
|
||
}
|
||
},
|
||
404: {
|
||
"description": "Группа не найдена",
|
||
"content": {
|
||
"application/json": {
|
||
"example": {"detail": "Группа 'monitoring' не найдена в конфигурации"}
|
||
}
|
||
}
|
||
},
|
||
500: {
|
||
"description": "Ошибка сервера",
|
||
"content": {
|
||
"application/json": {
|
||
"example": {"detail": "Ошибка отправки сообщения"}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
)
|
||
async def send_alertmanager_alert(
|
||
k8s_cluster: str = Path(
|
||
...,
|
||
description="Имя Kubernetes кластера (используется для формирования URL к Grafana/Prometheus)",
|
||
examples=["production", "staging", "development"]
|
||
),
|
||
group_name: str = Path(
|
||
...,
|
||
description="Имя группы из конфигурации (config/groups.json)",
|
||
examples=["monitoring", "alerts", "devops"]
|
||
),
|
||
thread_id: int = Path(
|
||
...,
|
||
description="ID треда в группе (0 для отправки в основную группу без треда, поддерживается только для Telegram)",
|
||
examples=[0, 123, 456]
|
||
),
|
||
messenger: Optional[str] = Query(
|
||
None,
|
||
description="Тип мессенджера (telegram, max). Если не указан, используется из конфигурации группы",
|
||
examples=["telegram", "max"]
|
||
),
|
||
alert: PrometheusAlert = Body(
|
||
...,
|
||
description="Данные алерта из AlertManager",
|
||
examples=[
|
||
{
|
||
"status": "firing",
|
||
"externalURL": "http://alertmanager.example.com",
|
||
"commonLabels": {
|
||
"alertname": "HighCPUUsage",
|
||
"severity": "critical",
|
||
"namespace": "production",
|
||
"pod": "app-deployment-7d8f9b4c5-abc123",
|
||
"container": "app-container"
|
||
},
|
||
"commonAnnotations": {
|
||
"summary": "High CPU usage detected in production namespace",
|
||
"description": "CPU usage is above 90% for 5 minutes on pod app-deployment-7d8f9b4c5-abc123",
|
||
"runbook_url": "https://wiki.example.com/runbooks/high-cpu-usage"
|
||
}
|
||
},
|
||
{
|
||
"status": "resolved",
|
||
"externalURL": "http://alertmanager.example.com",
|
||
"commonLabels": {
|
||
"alertname": "HighCPUUsage",
|
||
"severity": "critical",
|
||
"namespace": "production",
|
||
"pod": "app-deployment-7d8f9b4c5-abc123",
|
||
"container": "app-container"
|
||
},
|
||
"commonAnnotations": {
|
||
"summary": "High CPU usage resolved in production namespace",
|
||
"description": "CPU usage has returned to normal levels on pod app-deployment-7d8f9b4c5-abc123"
|
||
}
|
||
}
|
||
]
|
||
)
|
||
) -> Dict[str, str]:
|
||
"""
|
||
Отправить алерт из AlertManager в мессенджер.
|
||
|
||
Эндпоинт для обработки webhooks из AlertManager.
|
||
Не требует авторизации (API ключ не нужен).
|
||
|
||
Подробная документация: см. docs/monitoring/alertmanager.md
|
||
"""
|
||
metrics.increment_api_endpoint("alertmanager")
|
||
logger.info(f"Получен алерт AlertManager для кластера {k8s_cluster}, группы {group_name}, тред {thread_id}, мессенджер {messenger}")
|
||
|
||
try:
|
||
if not isinstance(alert, PrometheusAlert):
|
||
raise HTTPException(status_code=400, detail="Неверный формат данных для AlertManager алерта")
|
||
await alertmanager_send(k8s_cluster, group_name, thread_id, alert, messenger)
|
||
return {
|
||
"status": "ok",
|
||
"message": "Сообщение отправлено"
|
||
}
|
||
except HTTPException:
|
||
raise
|
||
except ValueError as e:
|
||
logger.error(f"Ошибка валидации: {e}")
|
||
raise HTTPException(status_code=404, detail=str(e))
|
||
except Exception as e:
|
||
logger.error(f"Ошибка отправки алерта AlertManager: {e}")
|
||
raise HTTPException(status_code=500, detail=f"Ошибка отправки сообщения: {str(e)}")
|
||
|