Маршрутизация Alertmanager в Telegram и Mattermost

Published: 2026-03-04

Alertmanager отправляет алерты кластера в два места: в Telegram — чтобы видеть их с телефона, в Mattermost — для командных тредов. Оба канала используют один кастомный Go-шаблон, встроенный прямо в values HelmRelease kube-prom-stack. Ключевое архитектурное решение: Alertmanager не обращается к Telegram или Mattermost напрямую — он отправляет всё в abot, небольшой fan-out-прокси.


Шаблон для Telegram

Шаблон живёт в alertmanager.templateFiles внутри values HelmRelease:

{{- define "telegram.default.message" -}}
{{- $alert := index .Alerts 0 -}}
{{- $desc := reReplaceAll ",?\\s*timestamp:.*$" "" $alert.Annotations.description -}}
{{- if eq .Status "resolved" }}✅
{{- else }}
  {{- with .CommonLabels.severity }}
    {{- if eq . "critical" }}🔴
    {{- else if eq . "warning" }}🟡
    {{- else if eq . "info" }}🔵
    {{- else }}🟠{{ end }}
  {{- else }}🔴{{ end }}
{{- end }} · {{ .CommonLabels.severity | toUpper }} · <a href="{{ .CommonLabels.grafana_url }}">dashboard</a>

🖥 {{ .CommonLabels.cluster }}
📦 {{ .CommonLabels.namespace }}{{ if .CommonLabels.pod }} · {{ .CommonLabels.pod }}{{ end }}
💬 {{ $desc }}
⏰ {{ $alert.StartsAt.Local.Format "2006-01-02 15:04:05" }}
{{- end -}}

Ключевые детали:

  • $desc убирает суффиксы timestamp: ... из описаний — Alertmanager иногда добавляет их из лейблов.
  • Severity отображается цветным эмодзи: 🔴 critical, 🟡 warning, 🔵 info, 🟠 остальное.
  • Лейбл cluster добавляется через externalLabels в spec Prometheus — каждый алерт знает, откуда пришёл.
  • Resolved-алерты всегда получают ✅ независимо от severity.

Шаблон для Mattermost

Идентичная логика, но Markdown вместо HTML:

{{- define "mattermost.default.message" -}}
{{- $alert := index .Alerts 0 -}}
{{- $desc := reReplaceAll ",?\\s*timestamp:.*$" "" $alert.Annotations.description -}}
{{- if eq .Status "resolved" }}✅{{ else }}
  {{- with .CommonLabels.severity }}
    {{- if eq . "critical" }}🔴{{ else if eq . "warning" }}🟡{{ else }}🔵{{ end }}
  {{- end }}
{{- end }} **{{ .CommonLabels.severity | toUpper }}** · [dashboard]({{ .CommonLabels.grafana_url }})
🖥 `{{ .CommonLabels.cluster }}`
📦 `{{ .CommonLabels.namespace }}`
💬 {{ $desc }}
{{- end -}}

Настройка receiver

yamlalertmanager:
  enabled: true
  config:
    global:
      resolve_timeout: 5m

    route:
      group_by: [cluster, namespace, alertname]
      group_wait: 30s
      group_interval: 5m
      repeat_interval: 4h
      receiver: telegram-mattermost
      routes:
        - matchers:
            - severity = "critical"
          repeat_interval: 1h
          receiver: telegram-mattermost
        - matchers:
            - alertname = "Watchdog"
          receiver: "null"   # подавить heartbeat-алерт

    receivers:
      - name: "null"
      - name: telegram-mattermost
        webhook_configs:
          - url: "http://abot.observability.svc.cluster.local:9444/alert"
            send_resolved: true

Почему abot, а не нативные вебхуки

У нативного Telegram-ресивера Alertmanager есть ограничения, а Mattermost требует специфического формата. Что даёт abot:

  1. Единую точку входа для Alertmanager — не нужно N ресиверов на N бэкендов.
  2. Fan-out в Telegram, Mattermost и куда угодно ещё.
  3. Полный контроль над форматом сообщений через Go-шаблоны.
  4. Повторные отправки с экспоненциальной задержкой.
  5. Работу через HTTP-прокси — on-prem-кластеры не могут напрямую достучаться до api.telegram.org.

Прокси для Telegram

On-prem-кластеры отправляют запросы к Telegram через внутренний HTTP-прокси:

yamlconfig:
  telegram:
    botToken: "..."
    chatId: "-100..."
    proxy: "http://10.16.91.109:3128"
  mattermost:
    webhookUrl: "https://mattermost.internal/hooks/..."

Прокси — это внутренний tinyproxy, который пробрасывает запросы во внешний интернет.


group_by и repeat_interval

group_by: [cluster, namespace, alertname] гарантирует, что алерты из разных кластеров образуют отдельные группы. Без cluster в group_by флаппинг алерта на dev может подавить тот же алерт на prod.

Настройка Значение Смысл
group_wait 30s Собрать связанные алерты перед отправкой
group_interval 5m Минимальный интервал между обновлениями группы
repeat_interval (warning) 4h Не спамить ночью
repeat_interval (critical) 1h Напоминать каждый час, пока не решено

Тестирование маршрутизации

bash# Отправить тестовый алерт
curl -X POST http://alertmanager.observability.svc.cluster.local:9093/api/v2/alerts \
  -H 'Content-Type: application/json' \
  -d '[{
    "labels": {
      "alertname": "TestAlert",
      "severity": "warning",
      "cluster": "dev",
      "namespace": "app"
    },
    "annotations": {
      "description": "Тестовый алерт"
    }
  }]'

# Логи abot
kubectl logs -n observability deployment/abot --context=dev-k8s -f

# Активные алерты
kubectl port-forward -n observability svc/alertmanager 9093:9093
curl -s http://localhost:9093/api/v2/alerts | jq '[.[] | {name: .labels.alertname, status: .status.state}]'

Типичные проблемы

Алерты не приходят:

  • group_wait — первый алерт ждёт 30 секунд перед отправкой.
  • repeat_interval — если алерт уже отправлялся, он молчит до истечения интервала.
  • Inhibition rules — critical может подавлять warning от того же компонента.

Resolved не приходят:

  • В webhook_config должен стоять send_resolved: true.

Ошибки шаблона:

  • Проверяйте конфигурацию командой alertmanager --check-config.