Подробный гайд: Отправка алертов из Alertmanager через webhook в JSON

Полный гайд по интеграции Alertmanager и webhook. Узнайте, как настроить отправку JSON-алертов, разобрать payload и написать обработчик на Python, Go или Node.js

2026.10.11                  


Подробный гайд: отправка алертов из Alertmanager через webhook в JSONПодробный гайд: отправка алертов из Alertmanager через webhook в JSON

1. Как это работает

Alertmanager получает алерты из Prometheus, группирует их и отправляет в настроенные приёмники (receivers). Один из встроенных типов приёмников — webhook_configs. Он отправляет HTTP POST-запрос с телом в формате JSON на указанный вами эндпоинт.

Prometheus → Alertmanager → HTTP POST (JSON) → ваш сервис

Формат полезной нагрузки (payload) фиксированный — вы не можете кастомизировать тело запроса через шаблоны напрямую (в отличие от, например, telegram_configs или email_configs). Если нужен кастомный формат — ставят промежуточный шлюз.




2. Настройка alertmanager.yml

Минимальная конфигурация

global:
  resolve_timeout: 5m

route:
  receiver: 'default-webhook'
  group_by: ['alertname', 'job']
  group_wait: 30s          # сколько ждать перед первой отправкой группы
  group_interval: 5m       # как часто отправлять обновления по группе
  repeat_interval: 4h      # как часто повторять активные алерты

receivers:
  - name: 'default-webhook'
    webhook_configs:
      - url: 'http://my-service:8080/alertmanager/webhook'
        send_resolved: true

Полные параметры webhook_configs

Параметр Описание По умолчанию
url URL эндпоинта обязателен
send_resolved Отправлять ли статус resolved false
max_alerts Макс. алертов в одном запросе (0 = без лимита) 0
http_config Настройки HTTP-клиента —

Пример с аутентификацией и TLS

receivers:
  - name: 'secure-webhook'
    webhook_configs:
      - url: 'https://hooks.example.com/alertmanager'
        send_resolved: true
        max_alerts: 10
        http_config:
          basic_auth:
            username: 'alertmanager'
            password: 'secret123'
          # или Bearer-токен:
          # authorization:
          #   type: 'Bearer'
          #   credentials: 'my-token-here'
          tls_config:
            insecure_skip_verify: false
            ca_file: '/etc/ssl/certs/ca.crt'

3. Формат JSON-payload (версия 4)

Каждый запрос — это один объект со следующей структурой:

{
  "version": "4",
  "groupKey": "{}:{alertname=\"HighCPUUsage\"}",
  "truncatedAlerts": 0,
  "status": "firing",
  "receiver": "default-webhook",
  "groupLabels": {
    "alertname": "HighCPUUsage"
  },
  "commonLabels": {
    "alertname": "HighCPUUsage",
    "severity": "warning",
    "job": "node-exporter"
  },
  "commonAnnotations": {
    "summary": "Высокая загрузка CPU",
    "description": "CPU выше 90% на {{ $labels.instance }}"
  },
  "externalURL": "http://alertmanager:9093",
  "alerts": [
    {
      "status": "firing",
      "labels": {
        "alertname": "HighCPUUsage",
        "severity": "warning",
        "job": "node-exporter",
        "instance": "server-01:9100"
      },
      "annotations": {
        "summary": "Высокая загрузка CPU на server-01",
        "description": "CPU выше 90% на сервере server-01"
      },
      "startsAt": "2026-10-11T14:23:05.123Z",
      "endsAt": "0001-01-01T00:00:00Z",
      "generatorURL": "http://prometheus:9090/graph?g0.expr=...",
      "fingerprint": "a1b2c3d4e5f67890"
    }
  ]
}

Разбор полей

Корневой уровень

Поле Тип Описание
version string Версия формата. Актуальная — "4"
groupKey string Уникальный ключ группы алертов
truncatedAlerts int Сколько алертов было отброшено из-за max_alerts
status string Общий статус группы: "firing" или "resolved"
receiver string Имя приёмника из конфига
groupLabels object Метки, по которым сгруппированы алерты
commonLabels object Метки, общие для всех алертов в группе
commonAnnotations object Аннотации, общие для всех алертов в группе
externalURL string URL Alertmanager (для ссылок в уведомлениях)

Массив alerts[]

Поле Тип Описание
status string Статус конкретного алерта
labels object Все метки алерта
annotations object Все аннотации алерта
startsAt string (RFC 3339) Время начала
endsAt string (RFC 3339) Время окончания (0001-01-01T00:00:00Z если ещё активен)
generatorURL string Ссылка на выражение в Prometheus UI
fingerprint string Уникальный отпечаток алерта (для дедупликации)



Важно: в одном запросе может быть несколько алертов в массиве alerts — это зависит от настройки group_by в маршруте.


4. Пример правила алерта в Prometheus

groups:
  - name: node-alerts
    rules:
      - alert: HighCPUUsage
        expr: 100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 90
        for: 5m
        labels:
          severity: warning
          team: infrastructure
        annotations:
          summary: "Высокая загрузка CPU на {{ $labels.instance }}"
          description: "CPU выше 90% более 5 минут на сервере {{ $labels.instance }}"

      - alert: InstanceDown
        expr: up == 0
        for: 1m
        labels:
          severity: critical
          team: infrastructure
        annotations:
          summary: "Инстанс {{ $labels.instance }} недоступен"
          description: "{{ $labels.instance }} ({{ $labels.job }}) не отвечает более 1 минуты"

5. Примеры обработчиков

5.1. Простой сервер на Python (Flask)

from flask import Flask, request, jsonify
import logging
from datetime import datetime

app = Flask(__name__)
logging.basicConfig(level=logging.INFO, format='%(asctime)s %(message)s')
logger = logging.getLogger(__name__)

@app.route('/alertmanager/webhook', methods=['POST'])
def webhook():
    data = request.get_json(force=True)

    if not data:
        return jsonify({"error": "empty payload"}), 400

    version = data.get("version", "unknown")
    status = data.get("status")
    receiver = data.get("receiver")
    group_key = data.get("groupKey")
    alerts = data.get("alerts", [])

    logger.info(f"=== Alertmanager webhook ===")
    logger.info(f"Version: {version}, Status: {status}, Receiver: {receiver}")
    logger.info(f"Group Key: {group_key}")
    logger.info(f"Alerts count: {len(alerts)}")

    for i, alert in enumerate(alerts):
        alert_status = alert.get("status")
        labels = alert.get("labels", {})
        annotations = alert.get("annotations", {})
        starts_at = alert.get("startsAt")
        fingerprint = alert.get("fingerprint")

        logger.info(f"  --- Alert #{i+1} ---")
        logger.info(f"  Status:      {alert_status}")
        logger.info(f"  Fingerprint: {fingerprint}")
        logger.info(f"  Labels:      {labels}")
        logger.info(f"  Annotations: {annotations}")
        logger.info(f"  Started at:  {starts_at}")

        # Пример: извлечение конкретного поля
        alertname = labels.get("alertname", "unknown")
        severity = labels.get("severity", "unknown")
        instance = labels.get("instance", "unknown")
        summary = annotations.get("summary", "")

        logger.info(f"  → [{severity.upper()}] {alertname} on {instance}: {summary}")

    return jsonify({"status": "ok"}), 200

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8080)

5.2. Простой сервер на Go (net/http)

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"
)

type AlertmanagerPayload struct {
    Version           string            `json:"version"`
    GroupKey          string            `json:"groupKey"`
    TruncatedAlerts   int               `json:"truncatedAlerts"`
    Status            string            `json:"status"`
    Receiver          string            `json:"receiver"`
    GroupLabels       map[string]string `json:"groupLabels"`
    CommonLabels      map[string]string `json:"commonLabels"`
    CommonAnnotations map[string]string `json:"commonAnnotations"`
    ExternalURL       string            `json:"externalURL"`
    Alerts            []Alert           `json:"alerts"`
}

type Alert struct {
    Status       string            `json:"status"`
    Labels       map[string]string `json:"labels"`
    Annotations  map[string]string `json:"annotations"`
    StartsAt     string            `json:"startsAt"`
    EndsAt       string            `json:"endsAt"`
    GeneratorURL string            `json:"generatorURL"`
    Fingerprint  string            `json:"fingerprint"`
}

func webhookHandler(w http.ResponseWriter, r *http.Request) {
    if r.Method != http.MethodPost {
        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
        return
    }

    var payload AlertmanagerPayload
    if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
        log.Printf("Error decoding JSON: %v", err)
        http.Error(w, "Bad request", http.StatusBadRequest)
        return
    }

    log.Printf("=== Alertmanager Webhook ===")
    log.Printf("Status: %s | Receiver: %s | Alerts: %d",
        payload.Status, payload.Receiver, len(payload.Alerts))

    for _, alert := range payload.Alerts {
        log.Printf("  [%s] %s (instance: %s, fingerprint: %s)",
            alert.Status,
            alert.Labels["alertname"],
            alert.Labels["instance"],
            alert.Fingerprint,
        )
        log.Printf("    Summary: %s", alert.Annotations["summary"])
    }

    w.WriteHeader(http.StatusOK)
    fmt.Fprintln(w, "OK")
}

func main() {
    http.HandleFunc("/alertmanager/webhook", webhookHandler)
    log.Println("Listening on :8080...")
    log.Fatal(http.ListenAndServe(":8080", nil))
}



5.3. Пример на Node.js (Express)

const express = require('express');
const app = express();
app.use(express.json());

app.post('/alertmanager/webhook', (req, res) => {
  const { version, status, receiver, alerts = [] } = req.body;

  console.log(`[${new Date().toISOString()}] Alertmanager webhook`);
  console.log(`  Version: ${version}, Status: ${status}, Receiver: ${receiver}`);

  alerts.forEach((alert, i) => {
    const { alertname, severity, instance } = alert.labels;
    const { summary } = alert.annotations;
    console.log(`  Alert #${i + 1}: [${alert.status}] ${alertname} (${severity}) on ${instance}`);
    console.log(`    Summary: ${summary}`);
    console.log(`    Started: ${alert.startsAt}`);
  });

  res.status(200).json({ status: 'ok' });
});

app.listen(8080, () => console.log('Webhook listener on :8080'));

6. Группировка и жизненный цикл

Как работает group_by

route:
  group_by: ['alertname', 'cluster']
  • Все алерты с одинаковыми значениями alertname и cluster попадают в один запрос в массив alerts.
  • Если сработали 5 алертов InstanceDown на разных серверах и group_by: ['alertname'] — придёт один запрос с массивом из 5 алертов.

Тайминги отправки

Параметр Что делает Типичное значение
group_wait Задержка перед первой отправкой новой группы (накапливает алерты) 30s
group_interval Интервал между повторными отправками при изменении группы 5m
repeat_interval Интервал повторной отправки неизменных активных алертов 4h

Жизненный цикл алерта

Алерт сработал (firing)
  → Alertmanager ждёт group_wait
  → POST с status="firing", alerts[].status="firing"
  → Если алерт повторяется — повторная отправка через repeat_interval
  → Алерт затух (resolved)
  → POST с status="resolved", alerts[].status="resolved"
  (только если send_resolved: true)



7. Продвинутые сценарии

7.1. Маршрутизация разных алертов на разные вебхуки

route:
  receiver: 'default'
  routes:
    - match:
        severity: critical
      receiver: 'critical-webhook'
      group_wait: 10s
      repeat_interval: 1h

    - match:
        severity: warning
      receiver: 'warning-webhook'
      repeat_interval: 12h

receivers:
  - name: 'critical-webhook'
    webhook_configs:
      - url: 'http://oncall-service:9090/critical'
        send_resolved: true

  - name: 'warning-webhook'
    webhook_configs:
      - url: 'http://dashboard-service:9090/warnings'
        send_resolved: false

7.2. Ограничение количества алертов

webhook_configs:
  - url: 'http://my-service/webhook'
    max_alerts: 20   # не более 20 алертов в одном запросе

Если алертов больше — лишние будут отброшены, а поле truncatedAlerts покажет сколько.


7.3. Дедупликация на стороне обработчика

Используйте поле fingerprint для определения уникального алерта:

seen_alerts = {}

def handle_alert(alert):
    fp = alert["fingerprint"]
    status = alert["status"]

    if status == "firing":
        if fp not in seen_alerts:
            seen_alerts[fp] = alert
            # Отправить уведомление
    elif status == "resolved":
        if fp in seen_alerts:
            del seen_alerts[fp]
            # Отправить уведомление о восстановлении

7.4. Кастомный формат через прокси-шлюз

Поскольку Alertmanager отправляет фиксированный формат, для кастомизации ставят промежуточный сервис:

Alertmanager → JSON → Прокси-шлюз → Кастомный формат → Конечный сервис

Пример прокси на Python, который преобразует алерт в произвольный JSON:

@app.route('/alertmanager/webhook', methods=['POST'])
def transform_and_forward():
    data = request.get_json()

    for alert in data.get("alerts", []):
        custom_payload = {
            "alert_name": alert["labels"].get("alertname"),
            "severity": alert["labels"].get("severity", "unknown"),
            "message": alert["annotations"].get("summary", ""),
            "server": alert["labels"].get("instance", ""),
            "state": alert["status"],
            "timestamp": alert["startsAt"],
            "link": alert.get("generatorURL", ""),
        }

        # Отправка в нужный сервис в своём формате
        requests.post(
            "https://internal-api.company.com/incidents",
            json=custom_payload,
            headers={"Authorization": "Bearer xxx"},
        )

    return jsonify({"status": "ok"}), 200

8. Отладка и проверка

8.1. Быстрая проверка через curl

Отправьте тестовый алерт вручную, чтобы проверить ваш обработчик без Alertmanager:

curl -X POST http://localhost:8080/alertmanager/webhook \
  -H "Content-Type: application/json" \
  -d '{
    "version": "4",
    "groupKey": "{}:{alertname=\"TestAlert\"}",
    "truncatedAlerts": 0,
    "status": "firing",
    "receiver": "test",
    "groupLabels": {"alertname": "TestAlert"},
    "commonLabels": {"alertname": "TestAlert", "severity": "info"},
    "commonAnnotations": {"summary": "Тестовый алерт"},
    "externalURL": "http://alertmanager:9093",
    "alerts": [
      {
        "status": "firing",
        "labels": {
          "alertname": "TestAlert",
          "severity": "info",
          "instance": "test-server:9100"
        },
        "annotations": {
          "summary": "Это тестовый алерт",
          "description": "Проверка webhook-обработчика"
        },
        "startsAt": "2026-10-11T12:00:00.000Z",
        "endsAt": "0001-01-01T00:00:00Z",
        "generatorURL": "http://prometheus:9090/graph?g0.expr=up",
        "fingerprint": "abc123def456"
      }
    ]
  }'



8.2. Использование amtool для тестов

# Отправить тестовый алерт в Alertmanager
amtool alert add TestAlert \
  alertname="TestAlert" severity="info" instance="test:9100" \
  --annotation=summary="Тестовый алерт" \
  --alertmanager.url=http://localhost:9093

8.3. Проверка конфига

# Валидация синтаксиса
amtool check-config /etc/alertmanager/alertmanager.yml

# Или через Docker
docker run --rm -v $(pwd)/alertmanager.yml:/etc/alertmanager.yml \
  prom/alertmanager amtool check-config /etc/alertmanager.yml

8.4. Логи и метрики

В логах Alertmanager ищите:

level=info msg="Notify success" receiver="default-webhook"
level=error msg="Notify failed" receiver="default-webhook"

Alertmanager метрики для мониторинга:

alertmanager_notifications_total{receiver="default-webhook"}
alertmanager_notifications_failed_total{receiver="default-webhook"}

9. Типичные ошибки и решения

Проблема Причина Решение
Обработчик не получает алерты Неверный url или сетевые ограничения Проверить доступность: curl -X POST http://...
Нет resolved уведомлений send_resolved: false Установить send_resolved: true
Дубликаты алертов Обработчик не учитывает repeat_interval Использовать fingerprint для дедупликации
Пустой alerts[] Неверный group_by или match Проверить маршруты и группировку
Обработчик падает при большом количестве алертов Слишком большая группа Настроить max_alerts или уменьшить group_by
Content-Type не парсится Обработчик ожидает application/x-www-form-urlencoded Alertmanager шлёт application/json — убедиться, что обработчик парсит JSON

10. Чек-лист настройки

  • [ ] Определены правила алертов в Prometheus (rules.yml)
  • [ ] В alertmanager.yml настроен route с group_by, таймингами
  • [ ] Создан receiver с webhook_configs
  • [ ] Указан корректный url
  • [ ] При необходимости настроена аутентификация (basic_auth / authorization)
  • [ ] Установлен send_resolved: true если нужны уведомления о восстановлении
  • [ ] Обработчик принимает POST и парсит application/json
  • [ ] Обработчик возвращает 200 OK (иначе Alertmanager будет повторять)
  • [ ] Проверена валидация конфига: amtool check-config
  • [ ] Проведён тестовый прогон через curl или amtool
  • [ ] Настроены метрики/логи для мониторинга доставки

Мы делимся этой технической информацией, чтобы помочь вам в решении задач — используйте её с пониманием. Статья носит рекомендательный характер, поэтому, пожалуйста, применяйте описанные методы осмотрительно.


Статью подготовил: Аверко Денис Сергеевич @Nymexis г. Омск (специалист по ЗИ)

Комментарии

Загрузка...
Если комментарии не загружаются, можете попробовать отключить блокировщик рекламы для этого сайта