AI Infra 训练营
总览
  • 总览
  • 完整安装
  • 核心 K8s
  • Cilium 网络
  • Longhorn 存储
  • 监控日志
  • CI / GitOps
  • 安全准入
  • CI/CD 实战(MySQL+Go+Vue)
  • HPA/Ingress/Hubble 实战
  • 面试速查 + 真实踩坑
  • Day 0 · 新手接管 Runbook
  • Day 1 · 集群起步 + CNI
  • Day 2 · 控制面 + etcd
  • Day 3 · CRD + Operator + Webhook
  • Day 4 · 存储深度
  • Day 5 · 卷扩容 + 安全
  • Day 6 · 调度 + 可观测
  • Day 7 · Harbor + ArgoCD + Mesh
  • Day 8 · AI Infra
  • Day 9 · Triton + GPU
  • Day 10 · MIG + HPA + 量化
  • Day 11 · AI Agent 端到端
  • Day 12 · 灾备
  • Day 13 · Operator + 联邦 + Mesh + RAG
  • Day 14 · CKA / CKS + 总结
  • LLM 训练手册
  • RAG + Agent 手册
  • 推理优化手册
  • 上下文工程手册
  • Agent 开发手册
  • 面试深度复盘
  • 训练 v2 深度手册
  • 心智模型
  • 看懂命令输出
  • 容器网络底层
  • K8s 网络深入
  • DNS 全套
  • 故障排查方法论
  • 心智模型
  • 容器挂载完整指南
  • K8s Volumes 大全
  • PV/PVC/CSI 深入
  • NFS 深入
  • 分布式存储概览
  • 故障排查 runbook
命令手册
HiHuo 主站
GitHub
总览
  • 总览
  • 完整安装
  • 核心 K8s
  • Cilium 网络
  • Longhorn 存储
  • 监控日志
  • CI / GitOps
  • 安全准入
  • CI/CD 实战(MySQL+Go+Vue)
  • HPA/Ingress/Hubble 实战
  • 面试速查 + 真实踩坑
  • Day 0 · 新手接管 Runbook
  • Day 1 · 集群起步 + CNI
  • Day 2 · 控制面 + etcd
  • Day 3 · CRD + Operator + Webhook
  • Day 4 · 存储深度
  • Day 5 · 卷扩容 + 安全
  • Day 6 · 调度 + 可观测
  • Day 7 · Harbor + ArgoCD + Mesh
  • Day 8 · AI Infra
  • Day 9 · Triton + GPU
  • Day 10 · MIG + HPA + 量化
  • Day 11 · AI Agent 端到端
  • Day 12 · 灾备
  • Day 13 · Operator + 联邦 + Mesh + RAG
  • Day 14 · CKA / CKS + 总结
  • LLM 训练手册
  • RAG + Agent 手册
  • 推理优化手册
  • 上下文工程手册
  • Agent 开发手册
  • 面试深度复盘
  • 训练 v2 深度手册
  • 心智模型
  • 看懂命令输出
  • 容器网络底层
  • K8s 网络深入
  • DNS 全套
  • 故障排查方法论
  • 心智模型
  • 容器挂载完整指南
  • K8s Volumes 大全
  • PV/PVC/CSI 深入
  • NFS 深入
  • 分布式存储概览
  • 故障排查 runbook
命令手册
HiHuo 主站
GitHub
  • Day 0 · 环境与硬件

    • Day 0 新手现场接管 Runbook:先看懂,再动手
    • Day 0:5 节点裸 Ubuntu → K8s 装机基线
  • Week 1:K8s 内核 + 周边基础设施

    • Day 1:3 CP HA 集群 + CNI 选型 + DNS 调优
    • Day 2:控制面 deep dive + etcd 内核 + chaos drill
    • Day 3:CRD + Operator (kubebuilder 从 0 写)
    • Day 4:Longhorn 存储 + Cilium 二探(Hubble / NetworkPolicy / L7)
    • Day 5:PVC 在线扩容 + K8s 安全基线(RBAC / PSA / Secret 加密 / Kyverno)
    • Day 6:调度策略 + Prometheus / Loki 观测栈
    • Day 7:Harbor 私有镜像 + ArgoCD GitOps + Cilium WireGuard
  • Week 2:制品 + GitOps + AI Infra + 综合

    • Day 8:k3s 单节点 + NVIDIA Device Plugin + vLLM 跑 Qwen2.5-3B
    • Day 8(attempt 1):跨 WAN GPU 加入主集群(失败复盘)
    • Day 8:AlertManager 真接入 + PrometheusRule 实战
    • Day 8:集群内 CI 闭环 — Gitea + Jenkins + Kaniko
    • Day 9:Triton 多框架推理 + DCGM 跨集群可观测 + vLLM 实测
    • Day 10:MIG 硬切片 + AWQ 量化 + HPA Custom Metrics
    • Day 11:AI 业务端到端 —— chainlit + GitOps + 跨 WAN vLLM
    • Day 12:灾难恢复 + 生产事故注入
    • Day 13:LLM Operator + 多集群联邦 + Ambient Mesh + RAG
    • Day 14:CKA / CKS 真题演练 + 14 天技术栈横向汇总

Day 8:AlertManager 真接入 + PrometheusRule 实战

kube-prometheus-stack 装上之后,最容易停在「Prometheus 在采、Grafana 在画」的中间态 —— rule 是装好的 35 条默认规则,alert 触发了也只在 Alertmanager UI 里堆着,没有一条真的推到通知渠道。这一天把这个链路打通:mock webhook receiver、AlertmanagerConfig CRD、5 条自定义 PrometheusRule,再用集群当下真实存在的 28 个 firing alert 做端到端验证。

整篇按 A → E 5 个阶段走,每阶段先说做什么,再讲一个真坑。


集群当前状态

接 Day 7 装好的 kube-prometheus-stack(helm release kps):35 条内置 rule(kps chart 自带:KubeAPIDown / KubePodCrashLooping / etcd 系列)+ 0 条自定义 + Alertmanager alertmanagerConfigSelector: {}(不接受任何 CR)+ 实时 firing 28 个(多数是几天前 audit policy 改完 cp-2/cp-3 etcd 重启遗留 + Grafana CrashLoop 次生告警)。目标:让这 28 个端到端送达 webhook,JSON 完整。


A. mock webhook receiver:先把「能收」做出来

接生产渠道前先要一个能看到 Alertmanager 真实发什么 JSON 的 receiver。直接接钉钉只能看客户端渲染后的 markdown,看不到原始 payload。用 mendhak/http-https-echo —— 把 POST body + headers 全 echo 到 stdout:

# alert-receiver.yaml
apiVersion: v1
kind: Namespace
metadata: {name: alert-receiver}
---
apiVersion: apps/v1
kind: Deployment
metadata: {name: webhook-mock, namespace: alert-receiver}
spec:
  replicas: 1
  selector: {matchLabels: {app: webhook-mock}}
  template:
    metadata: {labels: {app: webhook-mock}}
    spec:
      containers:
      - {name: c, image: mendhak/http-https-echo:latest, ports: [{containerPort: 8080}], env: [{name: HTTP_PORT, value: '8080'}]}
---
apiVersion: v1
kind: Service
metadata: {name: webhook-mock, namespace: alert-receiver}
spec:
  selector: {app: webhook-mock}
  ports: [{port: 80, targetPort: 8080}]
kubectl apply -f alert-receiver.yaml
# svc/webhook-mock   ClusterIP 10.103.39.189:80

独立 namespace:AlertmanagerConfig 会被 Operator 自动注入 namespace=<configns> matcher(见 §B),receiver 跟业务混在一起后期绕。不直连钉钉:demo 没凭证 + mock 能看完整 JSON,接生产只换 URL + 加 template。


B. AlertmanagerConfig CRD:Operator-pattern 接 receiver

非 Operator 时代改 Alertmanager 要 helm upgrade 全 chart values。AlertmanagerConfig CRD 把 receiver / route 切成独立 K8s 资源,GitOps 友好。

# alertmanager-config.yaml
apiVersion: monitoring.coreos.com/v1alpha1
kind: AlertmanagerConfig
metadata:
  name: bootcamp-routes
  namespace: monitoring
  labels: {alertmanagerConfig: bootcamp}   # ← AM Pod 的 selector 抓这个
spec:
  route:
    receiver: webhook-mock
    groupBy: [alertname, severity]
    groupWait: 10s
    groupInterval: 30s
    repeatInterval: 4h                     # 不要 1h,见 §D 表
    routes:
    - matchers: [{name: severity, value: critical}]
      receiver: webhook-mock
      groupWait: 0s                        # crit 不等 group
    - matchers: [{name: alertname, value: Watchdog}]
      receiver: webhook-mock               # kps 心跳必须接
  receivers:
  - name: webhook-mock
    webhookConfigs:
    - url: http://webhook-mock.alert-receiver.svc.cluster.local/alerts
      sendResolved: true
kubectl apply -f alertmanager-config.yaml

apply 完什么都不会发生。Alertmanager Pod 默认 alertmanagerConfigSelector: {},不接受任何 CR(Operator 安全默认,避免误装 chart 偷偷改 routing)。patch 打开:

kubectl patch alertmanager kps-kube-prometheus-stack-alertmanager -n monitoring --type=merge \
  -p '{"spec":{"alertmanagerConfigSelector":{"matchLabels":{"alertmanagerConfig":"bootcamp"}}}}'

B 真坑:Operator 自动加 namespace matcher,跨 ns alert 全丢

Operator 合并 AlertmanagerConfig 时自动给 route 加 matcher namespace="monitoring"。也就是说写在 monitoring 下的 config 只接收 namespace=monitoring 的 alert,其他 ns(kube-system etcd、longhorn-system volume)走默认 receiver(null —— 直接丢)。

第一次接 webhook 5 分钟没收到 POST,UI 看所有 alert 标 Inhibited By: null receiver 就是这个原因。多租户合理(A/B 团队隔离),单租户需要显式覆盖:

kubectl patch alertmanager kps-kube-prometheus-stack-alertmanager -n monitoring \
  --type=merge -p '{"spec":{"alertmanagerConfigMatcherStrategy":{"type":"None"}}}'

生产多团队走另一条路:每个 ns 一个 AlertmanagerConfig + 配 alertmanagerConfigNamespaceSelector,隔离干净。

附带现象:Operator 合并时自动给 receiver name 加 namespace 前缀(webhook-mock → monitoring/bootcamp-routes/webhook-mock),webhook JSON 里看到这个全名是正常的。


C. 5 条自定义 PrometheusRule

35 条内置 rule 覆盖控制面 + 节点,业务相关告警必须自己写。挑 5 条覆盖典型来源:CPU / 内存 / Pod 重启 / 节点 / 存储。

# custom-rules.yaml
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: bootcamp-custom-alerts
  namespace: monitoring
  labels: {release: kps}         # ← 必须!见下文真坑
spec:
  groups:
  - name: bootcamp.cluster
    rules:
    - alert: NodeCPUHigh
      expr: 100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 80
      for: 2m
      labels: {severity: warning}
      annotations: {summary: 'Node CPU > 80% on {{ $labels.instance }}'}

    - alert: NodeMemoryHigh
      expr: (1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100 > 85
      for: 5m
      labels: {severity: warning}
      annotations: {summary: 'Node memory > 85% on {{ $labels.instance }}'}

    - alert: KubePodCrashLoopingCustom
      expr: rate(kube_pod_container_status_restarts_total[5m]) * 60 * 5 > 3
      for: 5m
      labels: {severity: warning}
      annotations: {summary: 'Pod {{ $labels.namespace }}/{{ $labels.pod }} restarted 3+ in 5m'}

    - alert: NodeNotReady
      expr: kube_node_status_condition{condition="Ready",status="true"} == 0
      for: 3m
      labels: {severity: critical}
      annotations: {summary: 'Node {{ $labels.node }} not Ready 3m+'}

    - alert: PVCFillingUp
      expr: (kubelet_volume_stats_used_bytes / kubelet_volume_stats_capacity_bytes) > 0.85
      for: 5m
      labels: {severity: critical}
      annotations: {summary: 'PVC {{ $labels.namespace }}/{{ $labels.persistentvolumeclaim }} > 85% full'}

三个关键字段:expr PromQL(结果非空即触发)、for 连续满足多久才 pending → firing(防抖,见面试题)、labels.severity 路由依据。

C 真坑:少了 release: kps label,rule 装了但 Prometheus 不加载

kubectl get prometheusrule 能看到资源,但 Prometheus UI 的 Rules 页找不到这 5 条。

根因:kps 给 Prometheus 资源配 ruleSelector: {matchLabels: {release: kps}},Operator 只翻译带这个 label 的 PrometheusRule,少了直接忽略。

# 验 selector
kubectl get prometheus -n monitoring kps-kube-prometheus-stack-prometheus \
  -o jsonpath='{.spec.ruleSelector}'
# {"matchLabels":{"release":"kps"}}

# 修
kubectl label prometheusrule bootcamp-custom-alerts -n monitoring release=kps --overwrite

30 秒 reconcile,UI 出现 bootcamp.cluster + 5 条 rule。同样适用 ServiceMonitor / PodMonitor —— Operator 资源 apply 完看不到效果第一反应查 selector。


D. 端到端验证:28 firing alert 真打到 webhook

kubectl port-forward -n monitoring svc/kps-kube-prometheus-stack-alertmanager 9093:9093 &
curl -s http://localhost:9093/api/v2/alerts | jq '[.[] | select(.status.state=="active")] | length'
# 28

跑着的 28 个(节选):etcdMembersDown / etcdInsufficientMembers (cp-2/cp-3 etcd 重启遗留) / KubeControllerManagerInstanceUnreachable x3 / TargetDown x3 / KubePodCrashLooping (Grafana) / Watchdog (kps 永远 firing 的心跳) / 自定义的 NodeCPUHigh 等。

kubectl logs -n alert-receiver -l app=webhook-mock -f 看到的 JSON:

{
  "receiver": "monitoring/bootcamp-routes/webhook-mock",
  "status": "firing",
  "alerts": [{
    "labels": {"alertname": "KubePodCrashLooping", "namespace": "monitoring",
               "pod": "kps-grafana-...", "severity": "warning"},
    "annotations": {"runbook_url": "https://runbooks.prometheus-operator.dev/...",
                    "summary": "Pod is crash looping."},
    "startsAt": "2026-05-26T08:37:44.174Z",
    "fingerprint": "7eced4013f5f4ede"
  }],
  "groupLabels": {"alertname": "KubePodCrashLooping", "severity": "warning"}
}

完整字段含 startsAt / endsAt / fingerprint / runbook_url / generatorURL / labels / annotations,接钉钉/企微只写 template 映射到 markdown 即可。按 groupBy: [alertname, severity] 分组后 28 alert 合并成 4 个 group,每 group 一次 HTTP 200 POST。

生产参数速查

参数推荐为什么
groupBy: [alertname, namespace]—10 pod 同时挂发 1 条不是 10 条
groupWait10s 普通 / 0s criticalwarn 等几秒等同类,critical 不等
groupInterval30s太短刷屏,太长滞后
repeatInterval4h不要 1h,24h 24 次直接被静音;4h = 6 次刚好
inhibitRulescrit 静默同 alertname 的 warn减噪
sendResolvedtrue否则不知道何时自动恢复

E. inhibit_rule:critical 静默同 alertname 的 warn

source_matchers 匹配的 alert firing 时,跟它共享 equal 里所有 label 的 target_matchers alert 被静默。典型用法:同一个 alertname 有 warn / crit 两个阈值,crit 触发后压住 warn:

spec:
  inhibitRules:
  - sourceMatch: [{name: severity, value: critical}]
    targetMatch: [{name: severity, value: warning}]
    equal: [alertname, namespace]

跨 alertname 的关联(etcdInsufficientMembers 抑制 etcdMembersDown)用 inhibit 不优雅,更适合在 PromQL 里合并表达。

silence / inhibit / group 三者对比:group 是 yaml 静态合并通知(默认就有),inhibit 是 yaml 配的减噪规则(另一条 firing 时压住这条),silence 是人在 UI/API 临时屏蔽(维护窗口专用,不进 git)。生产都需要。


接生产 webhook:换 receiver 就好

mock 验通后,接生产渠道只换 receiver 段。Alertmanager 原生支持 slackConfigs / wechatConfigs / pagerdutyConfigs,钉钉需配套 prometheus-webhook-dingtalk 做 JSON → markdown。webhook URL 必须 Secret 引用,不明文写 CRD:

- name: slack
  slackConfigs:
  - apiURL: {name: slack-webhook-secret, key: url}   # secretRef
    channel: '#alerts'
    sendResolved: true
    title: '{{ .GroupLabels.alertname }}'
    text: '{{ range .Alerts }}{{ .Annotations.summary }}{{ end }}'
kubectl -n monitoring create secret generic slack-webhook-secret \
  --from-literal=url=https://hooks.slack.com/services/XXX

CRD apply 到 git 时 webhook URL 不泄露。企微 / 钉钉 / PagerDuty 同理。


典型坑速查

现象根因修复
AlertmanagerConfig apply 完 receiver 不生效默认 alertmanagerConfigSelector: {}patch Alertmanager 开 selector
webhook 不收,UI 看 alert 都是 null receiverOperator 自动加 namespace=<configns> matcher设 alertmanagerConfigMatcherStrategy=None
PrometheusRule apply 完 UI 看不到少 release: kps label加 label
Watchdog 一直 firingkps 心跳告警,就是要永远 firing路由到 Dead Man's Snitch
webhook 间歇收不到groupWait 太长 group 还在凑crit 路由设 groupWait: 0s
repeat_interval: 1h 值班炸了一天 24 次推送改 4h 起步

面试常见题

Q1:PrometheusRule 和 AlertmanagerConfig 是什么关系?

两个 CRD 管告警链路两端:

  • PrometheusRule 在 Prometheus 侧,定义「什么 metric 满足什么条件算告警」。Operator 翻译成 rule 文件,Prometheus 每 30s eval 一次 + 把 firing 推 Alertmanager。
  • AlertmanagerConfig 在 Alertmanager 侧,定义「收到 alert 怎么路由 / 分组 / 静默 / 发给谁」。Operator 合并进 alertmanager.yaml。

链路:PrometheusRule → Prometheus eval → Alertmanager API → AlertmanagerConfig route → receiver。

实战易错:两个 CRD 都需要对应 Operator 资源(Prometheus / Alertmanager)的 selector 显式匹配,默认 selector 是 {} 或带特定 label,少了 label 等于没装。

Q2:alert 为什么有 pending → firing 过渡?for 字段干什么的?

防抖。NodeCPUHigh: expr > 80 没有 for:CPU 抖到 81% 立刻 firing,回 79% 立刻 resolved,1 分钟内可能切换 10 次刷屏。for: 2m 让 expr 满足后进入 pending,连续满足 2 分钟才转 firing,中途任一次不满足重新计时。

生产 alert 几乎都不能省 for(除了 KubeAPIDown 这种 1 秒就该报的)。经验值:CPU/内存 2-5m、可用性类 1-3m、趋势类(PVCFillingUp)10-30m。

Q3:Alertmanager 的 group / inhibit / silence 三个减噪机制区别?

  • group:同类 alert 合并通知(10 pod 同时挂发 1 条不是 10 条)。alert 本身还在,只是通知合并。
  • inhibit:另一条 alert firing 时压住这条不发。典型:crit 触发后压住同事件 warn。alert 还在 UI,不推 receiver。
  • silence:人在 UI / API 手动加,按 matcher 在指定时间段屏蔽通知。维护窗口专用。

层次:group 默认就有,inhibit 是 yaml 固定规则,silence 是人工临时操作。生产都需要。

Q4:receiver 配多个,alert 怎么路由?

Route 是树形结构:进 root → 按顺序检查子 routes,第一个 matchers 命中的子路由接收。子路由设 continue: true 命中后继续匹配后续子路由(一个 alert 发多个 receiver)。任何子路由都不命中 → 落到当前路由 receiver。

route:
  receiver: default-slack            # fallback,必须有
  routes:
  - matchers: [{name: severity, value: critical}]
    receiver: pagerduty
    continue: true                   # 同时也发下面的 oncall-slack
  - matchers: [{name: severity, value: critical}]
    receiver: oncall-slack
  - matchers: [{name: team, value: data}]
    receiver: data-team-slack

要点:root 必须有 fallback(否则未分类 alert 直接丢),continue 用于多渠道通知关键 alert。

Q5:生产告警分级和值班轮转怎么设计?

按 severity 分级:critical → PagerDuty / 钉钉电话 + Slack(15 分钟 SLA)、warning → Slack 频道(工作时间 1 小时)、info → 邮件归档。

值班:PagerDuty / Grafana OnCall 配 schedule 按周轮主备双人;escalation 主班 15 分钟没 ack 升级到备班,30 分钟升级到经理;Dead Man's Snitch 把 Alertmanager 永远发的 Watchdog alert 接到外部心跳监控服务,几分钟没收到反向报警 —— 防 Alertmanager 自己挂导致以为没事其实全挂。

经验:repeat_interval ≥ 4h、runbook_url 必填 annotation(半夜被叫起来直接点链接)、Grafana dashboard 链接也放 annotation、工作日/周末不同 schedule。


下一步

Day 8 结束集群有完整告警闭环:5 条自定义 + 35 条 kps 内置 PrometheusRule → Prometheus eval → Alertmanager 分组路由 → webhook 接收,28 firing 端到端验通。下一步把 mock 换成钉钉 / Slack,把这 28 个历史 firing 各自的根因(etcd / Grafana / TargetDown)逐个修掉 —— 是真问题,不是 alert 配置问题。

Day 9 接 GPU 节点:k3s 单节点 + NVIDIA GPU Operator + vLLM 跑 Qwen2.5,主 K8s 跨集群 curl GPU NodePort,Hubble 看 L7 流量。

在 GitHub 上编辑此页
Prev
Day 8(attempt 1):跨 WAN GPU 加入主集群(失败复盘)
Next
Day 8:集群内 CI 闭环 — Gitea + Jenkins + Kaniko