Files
MessageGateway/app/api/v1/endpoints/monitoring.py
Sergey Antropov b90def35ed Initial commit: Message Gateway project
- FastAPI приложение для отправки мониторинговых алертов в мессенджеры
- Поддержка Telegram и MAX/VK
- Интеграция с Grafana, Zabbix, AlertManager
- Автоматическое создание тикетов в Jira
- Управление группами мессенджеров через API
- Декораторы для авторизации и скрытия эндпоинтов
- Подробная документация в папке docs/

Автор: Сергей Антропов
Сайт: https://devops.org.ru
2025-11-12 20:25:11 +03:00

404 lines
17 KiB
Python
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.

"""
Эндпоинты для обработки 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)}")