背景#

Homelab 跑了一套 LGTM 可观测性栈(Loki + Grafana + Tempo + Prometheus),Prometheus Alertmanager 已经在收集各种告警,但一直没有配出通知渠道——告警只是静静地积在队列里,没有任何推送。

Gotify 是一个轻量的自托管推送通知服务,已经在集群里跑着了,主要用于接收 Redpanda Connect 推送的消息。顺手把 Alertmanager 的告警也接进来,省得再引入第三方服务。

架构#

Alertmanager 的 webhook receiver 发送的是自己定义的 JSON 格式;Gotify 的 POST /message API 期望的是另一套字段(title / message / priority)。两者不能直接对接,需要一个中间层做格式转换。

DRuggeri/alertmanager_gotify_bridge 是一个专门做这件事的小服务。它的 README 说明默认监听 /gotify_webhook,并把收到的 Alertmanager webhook 转换后转发到 Gotify。

flowchart LR P[Prometheus] --> A[Alertmanager] A -->|webhook| B[alertmanager-gotify-bridge] B -->|POST /message| G[Gotify]

部署拓扑:

组件 Namespace 说明
Gotify personal-services 消息服务端,已有
alertmanager-gotify-bridge monitoring 格式转换 bridge
Alertmanager monitoring kube-prometheus-stack 的一部分

Token 管理走 Vault + External Secrets Operator:在 Gotify 创建一个专用 Application,把 token 存入 Vault,ESO 自动同步到 bridge 所在的 monitoring namespace。

前置条件#

  • Gotify 已部署并可访问(本文中地址为 https://notify.meirong.dev
  • Prometheus + Alertmanager 已通过 kube-prometheus-stack 部署
  • HashiCorp Vault + External Secrets Operator 已就绪
  • ClusterSecretStore 已配置(名称 vault-backend

步骤一:在 Gotify 创建 Application#

登录 Gotify Web UI,进入 AppsCreate Application

  • Name:Alertmanager(用于标识来源)
  • Description:Prometheus Alertmanager alerts

创建后复制显示的 Token,下一步存入 Vault。这里要用的是 Application token,不是客户端登录 token;Gotify 的 API 文档push message 文档 都按 application token 授权。

步骤二:将 Token 存入 Vault#

如果 Gotify 的 Vault path 已存在(secret/homelab/gotify),用 kv patch 追加字段,不影响已有的其他 key:

# 获取 root token
TOKEN=$(kubectl get secret vault-backup-keys -n vault --context k3s-homelab \
  -o jsonpath='{.data.vault-keys\.json}' | base64 -d | python3 -c \
  "import sys,json; print(json.load(sys.stdin)['root_token'])")

# 写入 alertmanager app token
kubectl exec -n vault vault-0 -- \
  env VAULT_TOKEN="$TOKEN" \
  vault kv patch secret/homelab/gotify alertmanager_app_token="<你的 token>"

确认写入成功:

kubectl exec -n vault vault-0 -- \
  env VAULT_TOKEN="$TOKEN" \
  vault kv get secret/homelab/gotify

步骤三:部署 bridge#

gotify.yaml(或任意 ArgoCD 管理的 manifest 文件)末尾追加以下三个资源。注意 namespace 显式写为 monitoring,ArgoCD 会按 manifest 中的 namespace 部署,不受 Application 的 destination.namespace 影响。

---
# ExternalSecret:从 Vault 同步 alertmanager app token 到 monitoring namespace
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
  name: alertmanager-gotify-bridge
  namespace: monitoring
spec:
  refreshInterval: "1h"
  secretStoreRef:
    name: vault-backend
    kind: ClusterSecretStore
  target:
    name: alertmanager-gotify-bridge
    creationPolicy: Owner
    deletionPolicy: Retain
  data:
    - secretKey: token
      remoteRef:
        key: homelab/gotify
        property: alertmanager_app_token
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: alertmanager-gotify-bridge
  namespace: monitoring
  labels:
    app: alertmanager-gotify-bridge
spec:
  replicas: 1
  selector:
    matchLabels:
      app: alertmanager-gotify-bridge
  template:
    metadata:
      labels:
        app: alertmanager-gotify-bridge
    spec:
      containers:
        - name: bridge
          image: ghcr.io/druggeri/alertmanager_gotify_bridge:latest
          ports:
            - containerPort: 8080
              name: http
          env:
            - name: GOTIFY_ENDPOINT
              value: "https://notify.meirong.dev/message"   # Gotify /message 完整路径
            - name: GOTIFY_TOKEN
              valueFrom:
                secretKeyRef:
                  name: alertmanager-gotify-bridge
                  key: token
          resources:
            requests:
              cpu: 10m
              memory: 16Mi
            limits:
              cpu: 100m
              memory: 64Mi
---
apiVersion: v1
kind: Service
metadata:
  name: alertmanager-gotify-bridge
  namespace: monitoring
spec:
  type: ClusterIP
  selector:
    app: alertmanager-gotify-bridge
  ports:
    - port: 8080
      targetPort: 8080
      name: http

几个要点(都能在 bridge README 的 usage 段落 对上):

  • GOTIFY_ENDPOINT 最好写成完整的 /message 路径,不要只写域名。
  • bridge 默认监听 0.0.0.0:8080,webhook 接收路径按 README 的说明是 /gotify_webhook
  • 镜像来自 GitHub Container Registry,不需要 Docker Hub 账号。

步骤四:配置 Alertmanager#

在 kube-prometheus-stack 的 Helm values 文件(values/kube-prometheus-stack.yaml)中,在 alertmanager: 下添加 config: 块:

alertmanager:
  enabled: true
  config:
    global:
      resolve_timeout: 5m
    inhibit_rules:
      - equal: [namespace, alertname]
        source_matchers: [severity = critical]
        target_matchers: [severity =~ "warning|info"]
      - equal: [namespace, alertname]
        source_matchers: [severity = warning]
        target_matchers: [severity = info]
      - equal: [namespace]
        source_matchers: [alertname = InfoInhibitor]
        target_matchers: [severity = info]
      - target_matchers: [alertname = InfoInhibitor]
    route:
      group_by: [namespace]
      group_wait: 30s
      group_interval: 5m
      repeat_interval: 12h
      receiver: "null"
      routes:
        - matchers: [alertname = "Watchdog"]
          receiver: "null"
        - matchers: [severity =~ "critical|warning"]
          receiver: gotify
    receivers:
      - name: "null"
      - name: gotify
        webhook_configs:
          - url: "http://alertmanager-gotify-bridge.monitoring.svc:8080/gotify_webhook"
            send_resolved: true
    templates:
      - /etc/alertmanager/config/*.tmpl
  alertmanagerSpec:
    # ... 其余 spec 配置不变

路由逻辑:

  • Watchdog(心跳告警)→ null,不发通知。
  • severity=criticalseverity=warninggotify,推送到 Gotify。
  • 其他告警保留默认行为(也走 null,可按需扩展)。
  • send_resolved: true:告警恢复时也发通知。

重新部署 Helm release 使配置生效:

helm upgrade --install kube-prometheus-stack prometheus-community/kube-prometheus-stack \
    --namespace monitoring \
    --kube-context k3s-homelab \
    --values values/kube-prometheus-stack.yaml \
    --timeout 15m

步骤五:验证#

1. 确认 bridge 运行正常

kubectl get pods -n monitoring --context k3s-homelab | grep bridge
# 期望输出:alertmanager-gotify-bridge-xxxx   1/1   Running

bridge 启动日志:

Starting server on http://0.0.0.0:8080/gotify_webhook translating to https://notify.meirong.dev/message ...

2. 确认 Alertmanager 加载了新配置

kubectl exec -n monitoring alertmanager-kube-prometheus-stack-alertmanager-0 \
  -c alertmanager -- \
  wget -qO- http://localhost:9093/api/v2/status | \
  python3 -c "import sys,json; d=json.load(sys.stdin); print(d['config']['original'])" | \
  grep -A5 "gotify"

3. 发送测试告警

直接 POST 一条符合 Alertmanager webhook 格式的请求到 bridge,验证全链路:

kubectl run test-alert --image=curlimages/curl --restart=Never \
  --context k3s-homelab -n monitoring \
  --command -- curl -s -X POST \
    http://alertmanager-gotify-bridge.monitoring.svc:8080/gotify_webhook \
    -H 'Content-Type: application/json' \
    -d '{
      "version": "4",
      "status": "firing",
      "receiver": "gotify",
      "groupLabels": {"severity": "warning"},
      "commonLabels": {
        "alertname": "TestAlert",
        "severity": "warning",
        "namespace": "monitoring"
      },
      "commonAnnotations": {
        "summary": "Test notification",
        "description": "Verifying Alertmanager → Gotify bridge"
      },
      "alerts": [{
        "status": "firing",
        "labels": {"alertname": "TestAlert", "severity": "warning"},
        "annotations": {"summary": "Test notification"},
        "startsAt": "2026-04-25T10:00:00Z"
      }]
    }'

# 查看结果
kubectl logs test-alert -n monitoring --context k3s-homelab
# 期望输出:Message 0 dispatched

# 清理
kubectl delete pod test-alert -n monitoring --context k3s-homelab

输出 Message 0 dispatched 表示消息已成功发到 Gotify,此时 Gotify 客户端或 Web UI 应该能收到通知。

4. 通过 Alertmanager API 发测试告警

也可以通过 Alertmanager 本身的 API 触发,验证完整的路由链路(会在 group_wait: 30s 后由 Alertmanager 转发给 bridge):

kubectl exec -n monitoring alertmanager-kube-prometheus-stack-alertmanager-0 \
  -c alertmanager -- \
  wget -qO- \
    --post-data='[{"labels":{"alertname":"TestAlert","severity":"warning","namespace":"monitoring"},"annotations":{"summary":"Test","description":"Full chain test"}}]' \
    --header='Content-Type: application/json' \
    http://localhost:9093/api/v2/alerts

常见问题#

bridge 启动报 lstat ./templates: no such file or directory

这是 bridge 在寻找可选的自定义消息模板目录,不影响功能,使用内置默认模板。

Alertmanager 日志出现 404 page not found

检查 webhook_configs.url 是否包含了 /gotify_webhook 路径。bridge 的 webhook 不在根路径 /,这里更稳妥的写法是 http://alertmanager-gotify-bridge.monitoring.svc:8080/gotify_webhook

Alertmanager 日志出现 operation not permitted

通常是网络策略(NetworkPolicy)拦截了 monitoring namespace 内部的 pod 间通信,或者 bridge pod 还没 Ready。确认 bridge pod 状态为 Running 后重试。

ESO ExternalSecret 没有及时同步新 token

修改 Vault 中的 token 后,ESO 默认按 refreshInterval 周期同步(本文设置为 1h)。如果想更快生效,可以删除对应的 K8s Secret,让 ESO 重新同步:

kubectl delete secret alertmanager-gotify-bridge -n monitoring --context k3s-homelab

小结#

整体配置不复杂,核心是三个部分:

  1. Token 管理:Gotify 创建专用 Application → token 存 Vault → ESO 同步到 K8s Secret。
  2. Bridgeghcr.io/druggeri/alertmanager_gotify_bridge 做格式转换,GOTIFY_ENDPOINT 最好写完整的 /message 路径,webhook 接收路径按当前 README 是 /gotify_webhook
  3. Alertmanager 路由:在 route.routes 中用 severity matcher 分流,Watchdog 告警单独路由到 null receiver 避免噪音。

这套方案全部基于已有组件(Vault、ESO、kube-prometheus-stack),没有额外引入新的基础设施依赖。