Homelab 消息通知:Alertmanager 通过 Gotify 推送告警
目录
背景#
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。
部署拓扑:
| 组件 | 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,进入 Apps → Create 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=critical或severity=warning→gotify,推送到 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
小结#
整体配置不复杂,核心是三个部分:
- Token 管理:Gotify 创建专用 Application → token 存 Vault → ESO 同步到 K8s Secret。
- Bridge:
ghcr.io/druggeri/alertmanager_gotify_bridge做格式转换,GOTIFY_ENDPOINT最好写完整的/message路径,webhook 接收路径按当前 README 是/gotify_webhook。 - Alertmanager 路由:在
route.routes中用 severity matcher 分流,Watchdog 告警单独路由到 null receiver 避免噪音。
这套方案全部基于已有组件(Vault、ESO、kube-prometheus-stack),没有额外引入新的基础设施依赖。