PLG: 部署与飞书告警实践
PLG:
部署与飞书告警实践
本篇记录如何把 PLG 监控体系以 git +
helm + CI
的方式部署进集群,并把告警送达飞书群,所有做法都是生产环境的通用玩法
监控栈的 CI 部署
整体形态
监控栈是一个
umbrella chart:聚合kube-prometheus-stack(含Prometheus/Alertmanager)+loki+promtail+grafana依赖,环境差异全部放在values-*.yaml中部署不走手工命令,而是
GitHub Actions在push到main时按路径触发,路径过滤保证只有监控配置变更才触发部署:1
2
3
4
5
6
7
8on:
push:
branches:
- main
paths:
- charts/monitoring/**
- charts/istio/**
- charts/ingress/**多集群、多产品通过
matrix展开,每个条目指定chart、命名空间、values文件与kubeconfig上下文:1
2
3
4
5
6
7
8
9
10strategy:
matrix:
product:
- name: monitoring
namespace: monitoring
values: values-example-prod.yaml
cluster:
- name: example-prod
runner: example-runner # 自托管 runner,持有集群访问权
context: example-prod
密钥注入
密钥永远不进
git:values文件里写占位符,CI用envsubst在发布时替换1
2
3
4# values 文件中的占位
grafana:
env:
BOT_GRAFANA_WEBHOOK_TOKEN: $BOT_GRAFANA_WEBHOOK_TOKEN1
2envsubst '$FEISHU_APP_ID $FEISHU_APP_SECRET $BOT_GRAFANA_WEBHOOK_TOKEN' \
< charts/monitoring/values-example-prod.yaml > $values_file简单值直接用
--set注入(如CloudflareToken),不必经过values文件
发布与自愈
统一使用
helm upgrade --install --create-namespace,并对”上一个操作未完成”的常见冲突做自动回滚 + 重试:1
2
3
4
5
6
7
8
9
10
11
12for retry in {1..3}; do
res=$(helm -n monitoring upgrade --install --create-namespace monitoring \
./charts/monitoring -f "$values_file" \
--set cloudflare.apiToken=$CF_TOKEN 2>&1)
[[ $? -eq 0 ]] && exit 0
if [[ "$res" = *"another operation (install/upgrade/rollback) is in progress"* ]]; then
helm -n monitoring rollback monitoring # 卡住的发布先回滚再重试
continue
fi
sleep 10
done
exit 1runner通过kubeconfigsecret切换目标集群上下文,一个runner可以发布多个集群1
2
3
4
5- uses: azure/k8s-set-context@v4
with:
method: kubeconfig
kubeconfig: ${{ secrets.KUBECONFIG }}
context: ${{ matrix.cluster.context }}
告警即代码
Grafana 统一告警
选择
Grafana统一告警(Unified Alerting)而非Alertmanager作为告警出口:规则、联系点、通知策略都住在Grafana里,与仪表盘共享数据源与标签体系,Alertmanager仅作为kube-prometheus-stack的兜底保留告警规则不进
Grafana界面手点,而是写成ConfigMap,打上约定标签,由Grafana sidecar全命名空间监听并同步进Grafana,改动走git评审:1
2
3
4
5
6
7
8
9
10kind: ConfigMap
metadata:
name: pod-status-alert
namespace: monitoring
labels:
grafana_alert: "1" # sidecar 按该标签发现告警资源
data:
pod-status-alert.yaml: |
apiVersion: 1
...1
2
3
4
5
6# values 中开启 sidecar 的告警同步,搜索全命名空间
grafana:
sidecar:
alerts:
enabled: true
searchNamespace: ALL同一文件描述四类资源:
groups(规则)、contactPoints(联系点)、policies(通知策略)、templates(通知模板)
告警规则的结构
规则按
group组织,folder决定在Grafana中的目录归属,interval是评估周期:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34apiVersion: 1
groups:
- orgId: 1
name: Kubernetes
folder: Infra
interval: 1m # 每分钟评估一次
rules:
- uid: a9a96a82-f6f5-4bfe-a18e-6f1ac6c9e72f
title: 服务健康状态
condition: 不健康服务增多 # 指向起决定作用的表达式 refId
data:
- refId: 不健康服务
datasourceUid: prometheus # 查询 Prometheus
model:
expr: |
sum by (namespace, pod, phase) (
kube_pod_status_phase{namespace !~ 'staging|dev-.*', phase=~"Pending|Unknown|Failed"} == 1
and on (namespace, pod) ((time() - kube_pod_created) > 180)
)
- refId: 不健康服务增多
datasourceUid: __expr__ # 表达式阶段做阈值判断
model:
type: threshold
expression: 不健康服务
conditions:
- evaluator:
params: [0]
type: gt # 结果 > 0 即触发
for: 5m # 持续满足 5 分钟才真正触发,抑制抖动
noDataState: OK # 无数据视为正常
annotations:
summary: 命名空间 {{ $labels.namespace }} 下 Pod {{ $labels.pod }} 处于 {{ $labels.phase }} 状态
labels:
product: infra # 供通知模板与策略路由使用注意三个工程细节:
PromQL里先做过滤与降噪:排除staging等低价值命名空间、只看存活超过3分钟的Podfor: 5m+noDataState: OK是防告警风暴的两道闸labels.product是整条告警链路的路由键,从values按产品开关(alerts.<product>.enabled)决定渲染哪些规则
飞书告警链路
联系点:webhook +
自建机器人
Grafana原生不支持飞书,与其等官方支持,不如加一层自建转发服务:联系点配置为普通webhook,指向自建的机器人服务,由它把Grafana的告警 payload 转成飞书群消息好处是格式完全自主、鉴权自己掌控、以后接任何新
IM都只改这一个服务:1
2
3
4
5
6
7
8
9
10
11
12apiVersion: 1
contactPoints:
- orgId: 1
name: 服务端告警群
receivers:
- uid: 80192ad5-0865-4075-a69a-eeccccbf82d8
type: webhook
settings:
url: https://bot.example.com/grafana/webhook # 自建机器人
httpMethod: POST
authorization_scheme: Bearer # 出口鉴权,防止伪造告警
authorization_credentials: $BOT_GRAFANA_WEBHOOK_TOKENBearer Token通过环境变量注入Grafana(grafana.env),再由envsubst在CI阶段填充,全程不落git
通知策略
策略决定”谁收到什么、多久提醒一次”,
repeat_interval是防轰炸的关键参数:1
2
3
4
5
6
7
8
9apiVersion: 1
policies:
- orgId: 1
receiver: grafana-default-email # 兜底接收器
group_by: ['...']
repeat_interval: 5m # 未恢复告警每 5 分钟重复提醒
routes:
- receiver: 服务端告警群 # 全部告警转发到飞书群
continue: true # 继续匹配后续路由,便于后续按产品分流
通知模板
模板用
Grafana的Go template语法,自定义标题与正文,把关键信息(product/application标签、当前数值、图表与静默链接)组织成飞书消息卡片的字段:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25apiVersion: 1
templates:
- orgId: 1
name: "example.title"
template: |
{{- if len .Alerts.Firing -}}
🚨🚨🚨 有 {{ len .Alerts.Firing }} 条告警规则正在触发
{{- end }}
{{- if and (len .Alerts.Firing) (len .Alerts.Resolved) -}}; {{ end -}}
{{- if len .Alerts.Resolved -}}
✅✅✅ 有 {{ len .Alerts.Resolved }} 条告警规则已经恢复
{{- end }}
- orgId: 1
name: "example.message"
template: |
- 产品名称: {{ .Labels.product }}
- 产品应用: {{ or (or .Labels.application .Labels.pod) "没有数据" }}
- 告警规则: {{ .Labels.alertname }}
- 当前数值: {{ range $k, $v := .Values }}{{ $k }}={{ $v }} {{ end }}
{{- if .DashboardURL }}
- 查看图表: [点击查看]({{ .DashboardURL }})
{{- end }}
{{- if .SilenceURL }}
- 抑制告警: [点击抑制]({{ .SilenceURL }})
{{- end }}.Labels.application正是Prometheus抓取配置里relabel出来的服务名标签——采集端打的标签决定通知端的信息量,这是整条链路设计的一体性所在
飞书登录(OAuth)
Grafana的登录同样接入飞书,用generic_oauth对接飞书开放平台的OAuth端点,账号体系与公司IM统一:1
2
3
4
5
6
7
8
9
10
11grafana.ini:
auth.generic_oauth:
enabled: true
name: 飞书
client_id: $FEISHU_APP_ID # CI 注入
client_secret: $FEISHU_APP_SECRET
auth_url: https://passport.feishu.cn/suite/passport/oauth/authorize
token_url: https://passport.feishu.cn/suite/passport/oauth/token
api_url: https://passport.feishu.cn/suite/passport/oauth/userinfo
login_attribute_path: name
role_attribute_path: name == 'someone' && 'Admin' || 'Viewer' # 按用户名映射角色role_attribute_path实现”白名单管理员、其余只读”,避免给全员开编辑权限
链路总结
1
2
3
4
5
6
7
8
9
10Pod/Envoy 指标 ──> Prometheus(注解发现/relabel 打标) ──┐
容器日志 ──> Promtail(CRI 解析) ──> Loki(S3/分级保留) ──┤
v
Grafana(数据源/仪表盘/告警,全部 ConfigMap + sidecar 供给)
|
告警规则(interval/for/labels.product)
v
通知策略(repeat_interval) ─> 联系点(webhook + Bearer)
v
自建机器人 ──> 飞书群卡片(title/message 模板)