课程目录(第 21 章 / 共 33 章)
可观测性:指标、日志与告警
把「出事了才登机器看」变成「提前发现、快速定位」:搭起指标、日志与告警三条链路,并知道哪些告警必须配。
学完这一章,你将能够
- ✓说得出可观测三支柱各自回答什么问题、在 K8s 里由谁提供
- ✓分清 metrics-server、cAdvisor、kube-state-metrics、node-exporter 的职责
- ✓用 Helm 装起一套指标加日志栈,并在 Grafana 里看到关键看板
- ✓写出节点未就绪、Pod 反复重启、PVC 将满等必备告警规则
可观测的三支柱与各自负责人
线上出问题时,你需要回答三类不同的问题:现在有多忙、刚才发生了什么、这个请求慢在哪一段。它们分别对应指标、日志和链路追踪,也就是常说的可观测三支柱。
| 支柱 | 回答的问题 | Kubernetes 里由谁提供 |
|---|---|---|
| Metrics 指标 | 现在有多忙、容量还剩多少 | kubelet/cAdvisor、metrics-server、node-exporter、kube-state-metrics、Prometheus |
| Logs 日志 | 刚才发生了什么、报错内容是什么 | 容器 stdout/stderr、kubectl logs、Fluent Bit 或 Vector、Loki 或 Elasticsearch |
| Traces 链路 | 一个请求慢在哪个服务、哪个函数 | 应用埋点 OpenTelemetry、Tempo 或 Jaeger |
三者的成本差别很大:指标最便宜,适合长期保留;日志量大且贵,通常只留几天到几周;链路追踪需要改代码,通常只在核心服务上开。
还有一个容易被忽略的第四类信号:事件(Events)。kubectl get events -A 里记录的是调度失败、拉镜像失败、探针失败这类「集群视角的日记」,排障时往往比日志更快命中原因。
指标链路:cAdvisor、metrics-server 与 Prometheus
Kubernetes 的指标有好几个来源,各管一段,别搞混。先看一张链路图,把「谁采集、谁存储、谁展示」三层对上号:
指标从哪来、到哪去(示意)
节点上 聚合与存储 使用方
────── ────────── ──────
┌───────────────────┐
│ kubelet / cAdvisor│──/metrics/cadvisor──┐
├───────────────────┤ ├──▶ metrics-server ──▶ kubectl top / HPA
│ node-exporter │──/metrics───────────┤ (只保留最近几分钟)
├───────────────────┤ │
│ kube-state-metrics│──/metrics───────────┼──▶ Prometheus ──┬──▶ Grafana 看板
├───────────────────┤ │ 或 VictoriaMetrics │
│ 应用 /metrics │──/metrics───────────┘ │ └──▶ Alertmanager ──▶ 值班 / 群
└───────────────────┘
日志:容器 stdout ──▶ /var/log/containers ──▶ Fluent Bit / Vector ──▶ Loki 或 VictoriaLogs ──▶ Grafana| 数据源 | 提供什么 | 部署形态 |
|---|---|---|
kubelet /metrics/cadvisor | 容器与节点的 CPU、内存、网络、文件系统用量 | 节点内建,无需单独部署 |
| node-exporter | 节点操作系统指标:CPU、内存、磁盘、网卡、文件系统 | DaemonSet |
| kube-state-metrics | Kubernetes 对象状态:期望副本数、Pod 阶段、PVC 状态 | Deployment |
| metrics-server | 聚合后的资源用量,供 kubectl top 与 HPA 使用 | Deployment |
应用 /metrics | 业务指标:QPS、延迟、错误率 | 随应用暴露 |
最关键的区分是:cAdvisor 管「用了多少资源」,kube-state-metrics 管「对象是什么状态」。想知道某个 Deployment 有几个副本没起来,靠 kube-state-metrics;想知道某个容器吃了多少内存,靠 cAdvisor。
长期存储与查询要靠 Prometheus 系:它按固定间隔去各个 /metrics 端点拉取数据(pull 模型),存成本地时序数据库再用 PromQL 查询,在 Kubernetes 里通常配合 Prometheus Operator,用 ServiceMonitor、PodMonitor 这类 CRD 声明「要抓谁」。
metrics-server 是最轻的一环,它把 kubelet 的数据聚合起来,通过 metrics.k8s.io 这个聚合接口专供 kubectl top 与 HPA 使用。它的安装、两处 TLS 证书校验以及 --kubelet-insecure-tls 的适用场景和风险,在探针与资源管理里有完整说明。这里只记住它的边界:不能替代 Prometheus——只保留最近几分钟的瞬时值,不能查询历史、不能做告警。
VictoriaMetrics:部署形态与保留期
VictoriaMetrics 是一套兼容 Prometheus 的时序数据库,查询语言 MetricsQL 是 PromQL 的超集。它的两个卖点很实际:存储效率更高(同样数据占用更小),以及单机版一个进程就能跑。
| 维度 | Prometheus | VictoriaMetrics |
|---|---|---|
| 查询语言 | PromQL | MetricsQL,兼容 PromQL 并扩展 |
| 存储效率 | 一般 | 明显更好,适合长期保留 |
| 部署复杂度 | 单机简单,高可用要额外组件 | 单机版极简,集群版组件多 |
| 适合 | 团队已熟悉 Prometheus 生态 | 指标量大、想省存储与运维成本 |
选哪种部署形态,判据是时间序列数量和查询并发,不是集群规模:victoria-metrics-single 适合千万级序列以内的集群,victoria-metrics-cluster 把写入(vminsert)、查询(vmselect)、存储(vmstorage)拆成三组可独立扩容的组件,适合序列上亿的场景,victoria-metrics-k8s-stack 则是一套装好采集、规则、看板与告警的全家桶。
单机版的数据落在 PVC 上,最该显式设置的是保留期和存储大小:
helm repo add vm https://victoriametrics.github.io/helm-charts/
helm repo update
helm install vmsingle vm/victoria-metrics-single --namespace monitoring --create-namespace \
--set server.retentionPeriod=1y \
--set server.persistentVolume.size=100Gi
kubectl -n monitoring get podsretentionPeriod 支持 30d、1y 这类写法,也可以按月数或磁盘用量设上限。生产上必须显式设置:不设就按默认保留期丢数据,容量规划也无从谈起。集群版还要给 vmstorage 设副本数,它决定能容忍多少存储副本同时挂掉:
helm install vmcluster vm/victoria-metrics-cluster --namespace monitoring \
--set vmstorage.replicaCount=3 --set vmstorage.persistentVolume.size=200Gi两套的 Grafana 看板基本通用,迁移成本主要在采集配置和少量 PromQL 差异上——vmagent 同时兼容 Prometheus 抓取配置与 ServiceMonitor 这类 CRD,规则不必重写。
参考:reference/k8s-in-action/o11y/vm/README.mdGrafana 与关键看板
Grafana 只做一件事:连上数据源,把查询画成图。它本身不存数据,所以换后端(Prometheus 或 VictoriaMetrics)对看板影响很小。
装好监控栈后,通常用端口转发先看一眼:
kubectl -n monitoring port-forward svc/kube-prometheus-stack-grafana 3000:80
kubectl -n monitoring get secret kube-prometheus-stack-grafana \
-o jsonpath='{.data.admin-password}' | base64 -d; echo浏览器打开 http://localhost:3000,用户名 admin,密码是上面命令的输出。如果用的是 VictoriaMetrics 那套,把服务名换成 vmstack-grafana 即可。建议至少固定看四类看板:
- 集群总览:节点数、CPU 与内存的分配率、Pod 总数、命名空间资源占用。
- 节点:CPU、内存、磁盘使用率与 inode、网络吞吐,用来发现「某台机器快满了」。
- 工作负载:Deployment 期望副本与就绪副本的差值、容器重启次数、OOMKilled 次数。
- API Server 与 etcd:请求延迟分位、5xx 比例、etcd 的 fsync 延迟与 leader 变更次数。
数据源只加两类:指标(Prometheus 或 VictoriaMetrics)和日志(Loki 或 VictoriaLogs),加完点 Save & test 确认连通。kube-prometheus-stack 与 victoria-metrics-k8s-stack 都自带一批常用看板,在 Dashboards 里按 Kubernetes / Compute Resources 分组找即可,不必从零画。
应用看板用 RED 方法起步
给自己的服务做看板时,先盯三个指标:Rate(QPS)、Errors(错误率)、Duration(延迟分位)。这三个够了之后再考虑更细的维度,不要一上来就堆几十个面板。
日志:stdout、采集与查询
容器里正确的做法是把日志写到标准输出,由运行时收集,而不是写进容器内的文件。kubectl logs 读的就是这些内容,但它有三个硬限制:只能看单个 Pod、默认只看当前容器实例(容器重启后要加 --previous)、Pod 删除后日志随之消失。
kubectl -n demo logs deploy/web --tail=50
kubectl -n demo logs deploy/web --previous
kubectl -n demo logs -l app=web --prefix --tail=20要在集群级别做搜索和留存,就需要采集器加存储。采集器通常以 DaemonSet 运行,读取节点上的容器日志文件,补上 namespace、pod、label 这些元数据,再发到后端。选采集器看两点:后端认不认它、资源开销大不大。Fluent Bit 最省资源,Promtail 专为 Loki 而生(VictoriaLogs 也实现了 Loki 的推送接口),Vector 的转换能力最强但更重。
| 环节 | 常见选择 | 生产落地要点 |
|---|---|---|
| 采集 | Fluent Bit、Promtail、Vector | DaemonSet 读 /var/log/containers/*.log,必须给采集端加限速,否则一个死循环日志能把后端写爆 |
| 后端:Loki | Promtail、Fluent Bit | 只有 single-binary 模式且副本数为 1 时才能用本地盘,simple-scalable、distributed 模式都必须配 S3 之类的对象存储 |
| 后端:VictoriaLogs | Fluent Bit、Promtail | 单机版一个进程即可,资源占用比 Loki 更小,写入与查询语法更灵活 |
VictoriaLogs 单机版最省事,装完直接用它的 Loki 兼容入口接收 Promtail 的推送:
helm repo add vm https://victoriametrics.github.io/helm-charts/
helm repo update
helm install vlogs vm/victoria-logs-single --namespace logging --create-namespace
kubectl -n logging get podsPromtail 的 clients 指向 http://vl-victoria-logs-single-server.logging.svc:9428/insert/loki/api/v1/push?_stream_fields=instance,job,host,app,就能把节点日志写进 VictoriaLogs;换回 Loki 只需把这个地址改成 http://loki-gateway/loki/api/v1/push。采集端配好后在 Grafana 里加数据源:Loki 填 http://loki-gateway.monitoring.svc,VictoriaLogs 填 http://vl-victoria-logs-single-server.logging.svc:9428(VictoriaLogs 要先装 victorialogs-datasource 插件,VictoriaMetrics 的 Helm chart 里用 GF_INSTALL_PLUGINS 声明即可)。之后就能按 namespace、pod 过滤,把「刚才发生了什么」和「现在有多忙」对起来看。
参考:reference/k8s-in-action/o11y/logs/readme.md、reference/k8s-in-action/o11y/logs/vl/readme.md、reference/k8s-in-action/o11y/logs/loki/readme.md
事件导出:别让集群的「黑匣子」过期
kubectl get events 的问题不是不好用,而是留不住:默认只保留 1 小时左右,Pod 删掉后相关事件很快消失,第二天复盘时现场已经没了。生产上要把事件当成一类日志来采集。
kubernetes-event-exporter 是一个 watch Event API 的 Deployment,按模板把事件字段映射成日志字段,再通过 webhook 写进 VictoriaLogs 或 Loki:
Event API ──watch──▶ kubernetes-event-exporter ──webhook──▶ VictoriaLogs / Loki
│ classify=k8s-event
│ type / reason / count
▼
LogsQL:classify:"k8s-event" AND type:"Warning"配置只有三段:route.routes 决定哪些事件发到哪个 receiver,receivers[].webhook.endpoint 指定后端地址,layout 决定事件字段怎么落到日志字段。`count` 字段最值得留——同一个事件被重复上报的次数越多,说明故障持续得越久,这比单看一条事件更能说明严重程度。大集群里还要把 kubeQPS、kubeBurst 调到与 API Server 承载能力匹配,否则 exporter 自己会变成 API Server 的压力源。
装好之后,按 classify:"k8s-event" 就能把所有集群事件捞出来,再按 namespace、type:"Warning"、component:"kubelet" 逐层过滤。完整的配置清单和查询示例见排障手册里「用指标与事件定位问题」一节。
参考:reference/k8s-in-action/o11y/logs/collectors/kubernetes-event-exporter/readme.md、reference/k8s-in-action/o11y/logs/readme.md
告警:Alertmanager 路由与必备规则
告警分两步:Prometheus 用规则表达式判断「是否异常」,Alertmanager 决定「发给谁、多久发一次」。Alertmanager 的核心是路由树,加上分组与抑制来对抗告警风暴。
| 配置 | 作用 |
|---|---|
route.group_by | 把同一类告警合并成一条通知,常用 namespace、alertname |
route.group_wait | 新告警先等一会,等同类告警凑齐再发 |
route.repeat_interval | 未恢复的告警多久提醒一次 |
routes + matchers | 按标签分流,比如 critical 走值班,warning 走群消息 |
inhibit_rules | 抑制次生告警,比如节点挂了就别再报上面的 Pod 全不可用 |
下面这份规则覆盖了集群里最该有的五类告警,用 kube-prometheus-stack 时直接 kubectl apply 即可:
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: kube101-critical
namespace: monitoring
labels:
release: kube-prometheus-stack
spec:
groups:
- name: kube101.rules
rules:
- alert: NodeNotReady
expr: kube_node_status_condition{condition="Ready",status="true"} == 0
for: 5m
labels:
severity: critical
annotations:
summary: "节点 {{ $labels.node }} 未就绪"
description: "节点 {{ $labels.node }} 已持续 5 分钟 NotReady,检查 kubelet 与节点网络。"
- alert: PodCrashLooping
expr: increase(kube_pod_container_status_restarts_total[15m]) > 3
for: 10m
labels:
severity: warning
annotations:
summary: "Pod {{ $labels.namespace }}/{{ $labels.pod }} 反复重启"
- alert: PVCWillFillIn
expr: |
kubelet_volume_stats_available_bytes / kubelet_volume_stats_capacity_bytes < 0.15
and
predict_linear(kubelet_volume_stats_available_bytes[6h], 4 * 3600) < 0
for: 30m
labels:
severity: warning
annotations:
summary: "PVC {{ $labels.persistentvolumeclaim }} 预计 4 小时内写满"
- alert: APIServerHighErrorRate
expr: |
sum(rate(apiserver_request_total{code=~"5.."}[5m]))
/
sum(rate(apiserver_request_total[5m])) > 0.01
for: 10m
labels:
severity: critical
annotations:
summary: "API Server 5xx 比例超过 1%"
- alert: CertificateExpiringSoon
expr: certmanager_certificate_expiration_timestamp_seconds - time() < 7 * 24 * 3600
for: 1h
labels:
severity: warning
annotations:
summary: "证书 {{ $labels.name }} 将在 7 天内过期"几点说明:kube_node_status_condition 与 kube_pod_container_status_restarts_total 来自 kube-state-metrics;kubelet_volume_stats_* 来自 kubelet,需要 Prometheus 抓取 kubelet;证书这条依赖 cert-manager 的指标,如果证书是手工签发的,就用 kube-prometheus-stack 自带的 KubeClientCertificateExpiration 规则覆盖 apiserver 客户端证书。
接收方配置写进 values:
alertmanager:
config:
route:
group_by: ["namespace", "alertname"]
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
receiver: default
routes:
- matchers:
- severity="critical"
receiver: oncall
repeat_interval: 1h
receivers:
- name: default
webhook_configs:
- url: http://alert-webhook.monitoring.svc:8080/
- name: oncall
webhook_configs:
- url: http://oncall-webhook.monitoring.svc:8080/配好之后可以临时把 for 改小、把阈值改低,观察告警是否真的发到接收端,验证完再改回来。
动手练习:装一套最小监控栈,在 Grafana 里看到一条真实指标
第一步,装 metrics-server。注意 kind 集群的 kubelet 用的是自签证书,不加 --kubelet-insecure-tls 会报 x509 错误:
helm repo add metrics-server https://kubernetes-sigs.github.io/metrics-server/
helm repo update
helm install metrics-server metrics-server/metrics-server --namespace kube-system \
--set 'args={--kubelet-insecure-tls}'
kubectl -n kube-system rollout status deploy/metrics-server --timeout=180s
kubectl top nodeskubectl top nodes 能打出 CPU 与内存两列数字就算这一步成功;如果报 metrics not available yet,等 30 秒再试一次。
第二步,装指标栈(这里用 kube-prometheus-stack,换成 VictoriaMetrics 那套命令也可以):
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack \
--namespace monitoring --create-namespace
kubectl -n monitoring get pods第三步,登录 Grafana 并查一条指标:
kubectl -n monitoring port-forward svc/kube-prometheus-stack-grafana 3000:80
kubectl -n monitoring get secret kube-prometheus-stack-grafana \
-o jsonpath='{.data.admin-password}' | base64 -d; echo浏览器打开 http://localhost:3000,用户名 admin,密码是上面命令的输出。进入 Explore,数据源选 Prometheus,依次执行:
up
sum(rate(container_cpu_usage_seconds_total{namespace="demo"}[5m])) by (pod)第一条会返回一串值等于 1 的时间序列(每个采集目标一条),说明「kubelet/cAdvisor → Prometheus → Grafana」这条链路已经通了;第二条要先把 demo 命名空间里的应用跑起来才有数据。能在图上看到一条随时间变化的曲线,这套最小栈就算搭好了。
监控栈本身也要被监控
监控挂了没人知道,是最尴尬的故障。至少给 monitoring 命名空间配一条 Pod 未就绪的告警,或者用外部探针访问 Grafana 的 /api/health。另外,Grafana 的 admin 密码要改掉,别用默认值暴露在公网上。
常见坑与排错
可观测性自己的故障最讽刺:你以为在看监控,其实监控早就瞎了。排查时先确认「采集有没有数据」,再怀疑「看板配错了」。
| 现象 | 原因 | 怎么确认 | 怎么办 |
|---|---|---|---|
kubectl top 报 metrics not available yet 或 x509 错误 | metrics-server 刚启动,或抓不到 kubelet 的自签证书 | kubectl -n kube-system logs deploy/metrics-server | kind 上补 --set 'args={--kubelet-insecure-tls}',等 30 秒重试 |
| Grafana 看板一片空白 | 数据源选错、时间范围不对、指标名不存在 | 在 Explore 里直接执行该表达式看有没有返回 | 切数据源、放宽时间范围、核对指标名与标签 |
| Prometheus 内存持续上涨甚至 OOM | 标签基数爆炸 | prometheus_tsdb_head_series 的数值、按指标名统计序列数 | 去掉 user_id 这类高基数标签,用 metric_relabel_configs 丢弃 |
| 节点一挂收到几十条告警 | 没有分组与抑制 | 看 Alertmanager 配置里的 group_by 与 inhibit_rules | 配分组与抑制,并给告警设置 for 持续时间 |
| 告警该响却不响 | 表达式永远为假,或 for 太长 | 在 Prometheus 的 Rules 页面直接执行表达式 | 单独跑一遍表达式确认返回值,再调 for |
| 日志后端磁盘被写满 | 采集端无限速、后端无保留策略 | df -h、Loki 的 limits_config.retention_period | 配保留期、给采集器加限速 |
可观测性里最常见的四类事故
- 指标基数爆炸:把
user_id、request_id、带 ID 的 URL 路径当标签,时间序列数量会指数增长,Prometheus 内存飙升甚至 OOM。标签只留低基数的维度。 - 告警风暴:节点一挂,上面几十个 Pod 的告警同时触发。用
group_by分组、用inhibit_rules抑制,再给关键告警设for持续时间。 - 日志采集把磁盘写满:采集器没有限额、后端用本地盘且没有保留策略,几天就能填满。给 Loki 或 Elasticsearch 配保留期,采集端加限速。
- 只看平均延迟:平均值会被大量快请求拉低,掩盖长尾。看 p95 与 p99,必要时看 p99.9。
自测题
自测:为什么装了 metrics-server,`kubectl top` 还是报错?(点击展开答案)
最常见的两个原因是证书和时间。metrics-server 要从每个节点的 kubelet 拉数据,而 kind 之类的本地集群里 kubelet 用的是自签证书,metrics-server 校验失败就会报 x509 错误,必须加 --kubelet-insecure-tls(生产环境应改为把集群 CA 挂给 metrics-server,而不是关掉校验)。另一个原因是它需要跑满一轮采集周期,刚装完的几十秒内会报 metrics not available yet。此外,如果 kubelet 的 read-only-port 或网络被限制,也会抓不到。判断方法很直接:看 metrics-server 自己的日志,错误类型会明确告诉你卡在哪一步。
自测:为什么 cAdvisor 和 kube-state-metrics 都要有,只留一个不行吗?(点击展开答案)
因为它们回答的是两个不同维度的问题。cAdvisor 由 kubelet 内建,提供的是资源消耗:某个容器用了多少 CPU、多少内存、发了多少网络包。kube-state-metrics 提供的是对象状态:某个 Deployment 期望几个副本、实际几个就绪,某个 PVC 是 Bound 还是 Pending。你无法从 cAdvisor 知道「副本数不够」,也无法从 kube-state-metrics 知道「某个容器吃了 3GB 内存」。告警规则同理:kube_pod_container_status_restarts_total 来自 kube-state-metrics,container_memory_working_set_bytes 来自 cAdvisor,两条都要有。
自测:为什么告警一定要配 `for` 和分组、抑制?(点击展开答案)
for 解决的是抖动:一个 Pod 因为一次 GC 或探针超时瞬间重启,for: 10m 能把它过滤掉,避免半夜被误报叫起来。分组和抑制解决的是风暴:节点宕机时,上面所有 Pod 的告警会在同一秒触发,如果不按 namespace 与 alertname 合并,值班人会收到几十条通知,反而看不到根因。用 inhibit_rules 让「节点 NotReady」抑制「该节点上的 Pod 不可用」,本质上是在告诉系统:根因告警已经发了,次生告警不必再喊。这三件配置决定了告警是「有用的信号」还是「被静音的噪音」。
小结
- 指标回答「有多忙」,日志回答「发生了什么」,链路回答「慢在哪一段」,三者的成本与用途不同。
- cAdvisor 给资源用量,kube-state-metrics 给对象状态,node-exporter 给节点 OS 指标,metrics-server 只服务
kubectl top与 HPA。 - Prometheus 与 VictoriaMetrics 是两条可互换的指标后端,后者更省存储;VM 单机版按序列量选型,
retentionPeriod必须显式设置。 - 日志采集用 DaemonSet 上的 Fluent Bit、Promtail 或 Vector,后端选 Loki、VictoriaLogs 或 Elasticsearch;Loki 只有单副本单进程模式能用本地盘。
- 事件默认只留 1 小时左右,用
kubernetes-event-exporter导出到日志后端,才能事后复盘集群级异常。 - 告警靠 Prometheus 规则加 Alertmanager 路由,节点 NotReady、Pod 反复重启、PVC 将满、API Server 5xx、证书过期是必备的五条。
- 监控栈自己要配告警,否则故障会静默。
练习
- 装好 metrics-server 后执行
kubectl top pods -A,再对比 Grafana 里同一个容器的内存曲线,说说两者数据为什么可能略有差异。 - 把
NodeNotReady的for改成1m,然后故意在一个 kind 节点上停掉 kubelet,观察告警从 Pending 到 Firing 的过程。 - 给
web应用加一个/metrics端点(可以用 nginx-prometheus-exporter),接入采集并在 Grafana 里画出 QPS 与延迟分位。
本章的告警规则里用到了节点、Pod、PVC 的状态,这些资源的入门用法在第 10 章 存储卷与 PV/PVC与第 11 章 探针与资源管理;etcd 那几条指标怎么读,回看第 18 章 生产集群部署的磁盘验收部分。指标看到异常之后该从哪里下手,排障手册给了一套固定顺序。
到这里,进阶模块的四章就结束了。回头看看这条路线:集群怎么装、网络怎么通、数据怎么落、状态怎么看——这四件事撑起了「敢在生产上管」的底气。