Kubernetes 基础体系 · 第 66/83 篇。示例基于 Kubernetes 当前稳定 API;弃用、版本偏差、云厂商差异和生产风险会明确说明。

Kubernetes 指标与监控:Metrics Server、Prometheus、告警和 SLO

在 Kubernetes 中,“指标”不是单一数据源。kubectl top 能看到的 CPU 和内存、Prometheus 中保存的业务请求时延、控制器暴露的副本数,以及基于这些数据发送的告警,分别属于不同层次。

可以先建立如下关系:

flowchart LR
    Kubelet[Kubelet / 容器运行时] -->|资源指标| MS[Metrics Server]
    MS --> MA[Metrics API]
    MA --> KT[kubectl top]
    MA --> HPA[Horizontal Pod Autoscaler]

    App[应用 / Exporter] -->|Prometheus exposition| P[Prometheus]
    KSM[kube-state-metrics] -->|对象状态指标| P
    Node[Node Exporter] -->|节点指标| P
    Kubelet2[Kubelet / cAdvisor 指标] --> P
    P -->|PromQL| Dash[Grafana / 查询客户端]
    P -->|告警规则| AM[Alertmanager]
    AM --> Notify[邮件 / PagerDuty / 飞书等]

    P --> SLO[SLO 计算与错误预算]

这张图的关键点是:Metrics Server 和 Prometheus 解决的问题不同,Metrics Server 通常不替代 Prometheus,Prometheus 也不自动提供 Kubernetes 的资源指标 API。


一、先区分 Kubernetes 中的三类指标

1. 资源指标:回答“当前用了多少资源”

资源指标主要包括:

  • Pod 或容器当前使用的 CPU;
  • Pod 或容器当前使用的内存;
  • 节点当前使用的 CPU 和内存。

它们通过 Kubernetes 的资源指标 API 提供,例如:

metrics.k8s.io/v1beta1

这里的 v1beta1 是 Kubernetes 资源指标 API 的 API group 版本,不代表 Metrics Server 本身的版本。该 API 主要面向:

  • kubectl top
  • Horizontal Pod Autoscaler(HPA);
  • 其他需要短期资源使用数据的控制器或工具。

资源指标通常是短期、近实时数据,不是完整的历史监控数据库。


2. 对象状态指标:回答“Kubernetes 对象处于什么状态”

Kubernetes API 中的 Deployment、Pod、Job、Node 等对象包含期望状态和当前状态。例如:

  • Deployment 期望副本数是 5,当前可用副本数是 3;
  • Pod 是否处于 Ready;
  • Job 是否失败;
  • Node 是否因为磁盘压力被标记为异常;
  • 某个 Pod 是否频繁重启。

这些状态可以由 kube-state-metrics 转换为 Prometheus 指标。它不是 Kubernetes 内置控制器,也不是 Metrics Server 的一部分,而是一个独立组件,读取 Kubernetes API 对象并暴露状态指标。

例如,常见的 Deployment 指标可能表示:

kube_deployment_spec_replicas
kube_deployment_status_replicas_available

二者的含义不同:

  • spec_replicas 是期望值;
  • status_replicas_available 是当前可用值。

Prometheus 可以据此发现“期望副本数和可用副本数长期不一致”,而 kubectl top 不能回答这个问题。


3. 应用和系统观测指标:回答“服务运行得怎么样”

Prometheus 通常采集以下数据:

  • 应用请求数、错误数、延迟;
  • JVM、Go、Python 等运行时指标;
  • 节点 CPU、内存、磁盘、网络;
  • Kubernetes API Server、Scheduler、Controller Manager、kubelet 指标;
  • 数据库、消息队列、Ingress、负载均衡器等组件指标。

这些指标一般通过 HTTP endpoint 暴露,常见格式是 Prometheus exposition format:

http_requests_total{method="GET",route="/users",code="200"} 12345

这类指标的核心价值是保留时间序列,使工程师能够回答:

  • 过去一小时错误率是否上升;
  • 发布前后的延迟是否变化;
  • 某个节点的磁盘空间是否持续下降;
  • 某类请求在不同区域的成功率是否不同。

二、Metrics Server 的组件、数据流和边界

2.1 Metrics Server 做什么

Metrics Server 是 Kubernetes 资源指标 API 的实现之一。它通常执行以下流程:

  1. 通过 Kubernetes API 发现节点和 Pod;
  2. 向各节点上的 kubelet 请求资源使用数据;
  3. 对采集结果进行聚合和短期缓存;
  4. 通过扩展 API Server 暴露 metrics.k8s.io
  5. kubectl top 或 HPA 读取。
sequenceDiagram
    participant K as kubectl / HPA
    participant A as kube-apiserver
    participant M as Metrics Server
    participant L as kubelet

    K->>A: GET /apis/metrics.k8s.io/v1beta1/...
    A->>M: API Aggregation 转发请求
    M->>A: 查询节点和 Pod 信息
    M->>L: 请求 kubelet resource metrics
    L-->>M: CPU / memory 使用数据
    M-->>A: 返回 Metrics API 响应
    A-->>K: 返回资源指标

API Aggregation 是这里的关键机制。Metrics Server 并不是把 API Server 的二进制代码改掉,而是注册一个 APIService,由 kube-apiserver 将特定 API group 的请求转发给 Metrics Server。

可以检查相关对象:

kubectl get apiservice v1beta1.metrics.k8s.io
kubectl get --raw "/apis/metrics.k8s.io/v1beta1/nodes" | jq .
kubectl top nodes
kubectl top pods -A

正常情况下:

  • APIServiceAVAILABLE 应为 True
  • raw API 请求应返回 JSON;
  • kubectl top 应显示节点或 Pod 资源使用量。

如果 kubectl top 失败,不能直接得出“应用没有指标”的结论。它只说明资源指标 API 链路存在问题,或者当前没有可用的资源数据。


2.2 安装与验证

Metrics Server 的发布版本需要与目标 Kubernetes 版本、网络和 TLS 配置兼容。测试环境可以使用项目发布页提供的清单,例如:

kubectl apply -f \
  https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml

生产环境不应长期依赖 latest URL,因为它会随上游发布变化。应选择经过验证的具体版本,将清单纳入版本控制,并在升级前验证:

  • Kubernetes 版本兼容性;
  • kubelet 认证和授权;
  • Metrics Server 到 kubelet 的网络连通性;
  • APIService 的证书和聚合层配置;
  • 节点地址类型选择是否符合云环境。

安装后检查:

kubectl -n kube-system get deploy metrics-server
kubectl -n kube-system get pods -l k8s-app=metrics-server
kubectl -n kube-system logs deploy/metrics-server
kubectl get apiservice v1beta1.metrics.k8s.io
kubectl top nodes

如果 Pod 已经处于 Running,但 APIService 仍是不可用,常见原因包括:

  • Metrics Server 无法访问 kubelet;
  • kubelet serving certificate 不被 Metrics Server 信任;
  • 节点向 kubelet 提供的地址不可达;
  • kubelet 的认证或授权配置拒绝请求;
  • 集群网络策略阻断了访问;
  • API Aggregation 层的证书配置错误。

应先看 APIService 的事件和描述:

kubectl describe apiservice v1beta1.metrics.k8s.io

再查看 Metrics Server 日志:

kubectl -n kube-system logs deploy/metrics-server --since=15m

不要一开始就加入 --kubelet-insecure-tls。这个参数会跳过 kubelet serving certificate 的校验,只适合在明确理解风险的临时诊断中使用。生产环境应修复证书签发、信任链或 kubelet 地址配置,否则中间人攻击可能伪造资源指标。


2.3 Metrics Server 的数据为什么不能替代 Prometheus

Metrics Server 的设计目标是为资源指标 API 和 HPA 提供数据,而不是提供通用监控数据库。它通常具有以下边界:

  1. 不负责长期保存历史时间序列;
  2. 不提供 PromQL;
  3. 不负责业务指标采集;
  4. 不提供完整的标签维度和跨组件关联;
  5. 不负责告警路由、静默和通知;
  6. 不等同于节点监控或容器监控平台。

因此:

kubectl top pods

只能回答“现在大约用了多少 CPU 和内存”,不能回答:

过去 24 小时该 Deployment 的 P99 请求延迟是多少?

这两个问题分别对应资源指标和历史时序监控。


三、HPA 为什么需要 Metrics Server

HPA 根据指标调整工作负载的副本数。以 CPU 利用率为例,HPA 不是简单地把“当前 CPU 百分比”乘以某个固定系数,而是根据当前指标和目标指标计算期望副本数。

简化形式为:

desiredReplicas=currentReplicas×currentMetricValuedesiredMetricValuedesiredReplicas = \left\lceil currentReplicas \times \frac{currentMetricValue}{desiredMetricValue} \right\rceil

其中:

  • currentReplicas 是当前副本数;
  • currentMetricValue 是当前平均 CPU 利用率;
  • desiredMetricValue 是 HPA 配置的目标值;
  • ceil 表示向上取整。

例如:

  • 当前副本数为 3;
  • 平均 CPU 利用率为 80%;
  • 目标 CPU 利用率为 50%。

则:

3×8050=4.8=5\left\lceil3 \times \frac{80}{50}\right\rceil = \lceil4.8\rceil = 5

HPA 还会考虑稳定窗口、缩容行为策略、缺失指标和正在删除的 Pod,因此实际行为不一定每次都立即变成 5 个副本。伸缩控制是一个带有延迟和抑制机制的反馈回路:

资源消耗上升
  -> kubelet 产生资源数据
  -> Metrics Server 聚合
  -> HPA 读取指标
  -> HPA 修改 Deployment 的期望副本数
  -> ReplicaSet 创建 Pod
  -> Pod 调度和启动
  -> 应用处理能力增加

这条链路的任何延迟都会影响伸缩效果。尤其是:

  • 应用启动时间很长时,扩容来不及;
  • CPU 不是瓶颈时,CPU HPA 可能无法改善请求延迟;
  • 没有为容器设置合理的 resources.requests 时,CPU 利用率的计算可能不符合预期;
  • 指标缺失时,HPA 会采用保守处理,而不是盲目扩缩。

一个最小的 CPU HPA 示例:

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: web
  namespace: default
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: web
  minReplicas: 2
  maxReplicas: 10
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 0
    scaleDown:
      stabilizationWindowSeconds: 300
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 60

这里的 averageUtilization 是相对于容器 CPU request 的百分比,不是相对于节点全部 CPU 的百分比。因此,应先为容器定义 request:

resources:
  requests:
    cpu: "250m"
    memory: "256Mi"
  limits:
    cpu: "1"
    memory: "512Mi"

如果一个容器 request 为 250m,当前使用量为 125m,那么相对于 request 的利用率是:

125/250=50%125 / 250 = 50\%

即使节点总共有 16 个 CPU,这个 Pod 在 HPA 中仍可能被视为 50% CPU 利用率。


四、Prometheus 的采集模型

4.1 Pull 模型

Prometheus 主要采用 pull 模型:Prometheus 主动定期访问目标的指标 endpoint。

一个典型流程是:

  1. 服务暴露 /metrics
  2. Prometheus 通过静态配置或服务发现找到目标;
  3. 按 scrape interval 发起 HTTP 请求;
  4. 解析指标名称、标签和样本值;
  5. 写入本地 TSDB;
  6. 用 PromQL 查询或计算;
  7. 根据告警规则产生告警事件。

与“应用主动把每个指标推送到监控系统”相比,pull 模型有几个重要特征:

  • Prometheus 可以判断目标是否无法访问;
  • 采集间隔由监控系统统一控制;
  • 目标不必维护监控系统连接;
  • 短生命周期批处理任务可能在被抓取前就结束,不能仅依赖普通 pull 采集。

Prometheus 的 up 指标就是采集结果的基础信号:

up{job="web"}

通常:

  • 1 表示最近一次 scrape 成功;
  • 0 表示目标被发现了,但 scrape 失败;
  • 没有时间序列则可能表示目标根本没有被发现,或者相关序列已过期。

up == 0 与“应用一定不可用”不是同一个命题。可能是 metrics endpoint 失败,而业务端口正常;也可能是网络策略只阻断了监控路径。


4.2 Kubernetes 服务发现与抓取目标

Prometheus 可以通过 Kubernetes API 发现:

  • Node;
  • Pod;
  • Service;
  • Endpoints 或 EndpointSlice;
  • Ingress 等对象。

Prometheus 发现对象后,通常还需要 relabel 规则决定:

  • 使用哪个地址和端口;
  • 是否保留该目标;
  • 将 Namespace、Pod、Service 等元数据转成标签;
  • 是否丢弃某些目标。

在 Kubernetes 中,业务应用通常有以下几种暴露指标的方式:

  1. 应用自身暴露 /metrics
  2. 使用 sidecar 或独立 exporter 转换协议;
  3. 由节点 exporter 暴露节点指标;
  4. 由 kube-state-metrics 暴露对象状态;
  5. 由 Kubernetes 组件暴露控制面或 kubelet 指标。

Prometheus Operator 生态中常用 ServiceMonitorPodMonitor 简化配置,但它们不是 Kubernetes 内置 API,而是 Operator 安装的 CRD。使用前需要确认:

  • 集群中是否安装了对应 Operator;
  • Prometheus 实例是否选择了该 ServiceMonitor
  • CRD 版本是否兼容;
  • label selector 是否匹配;
  • Prometheus 是否有权限访问目标。

因此,看到一个 ServiceMonitor 对象并不意味着一定会发生抓取。


五、Prometheus 指标类型与 PromQL 基础

5.1 Counter:只增不减的累计量

Counter 适合表示:

  • 请求总数;
  • 错误总数;
  • 处理任务总数;
  • 重启次数。

例如:

http_requests_total{method="GET",code="200"} 1200

Counter 会在进程重启时归零。因此直接查询:

http_requests_total

通常没有业务意义。常见做法是使用 rateincrease

rate(http_requests_total[5m])

它表示过去 5 分钟内的每秒平均增长速率。

如果 Counter 从 1200 变为 20,Prometheus 不会简单认为它减少了 1180,而会尝试识别这是一次重置。若指标本身不是 Counter,却错误地使用 rate,结果会失真。


5.2 Gauge:可上下变化的瞬时值

Gauge 适合表示:

  • 当前内存使用量;
  • 当前队列长度;
  • 当前活跃连接数;
  • 温度;
  • 当前副本数。

例如:

container_memory_working_set_bytes

对 Gauge 使用 rate 通常是不正确的,因为 Gauge 的变化本身不是累计过程。要看当前值、最大值或平均值,应使用:

max_over_time(queue_depth[15m])
avg_over_time(queue_depth[15m])

5.3 Histogram:用于分布和分位数估计

Histogram 会把观测值放入预先定义的桶,并暴露:

  • _bucket:累计桶计数;
  • _count:观测次数;
  • _sum:观测值总和。

例如:

http_request_duration_seconds_bucket{le="0.1"} 950
http_request_duration_seconds_bucket{le="0.5"} 990
http_request_duration_seconds_bucket{le="+Inf"} 1000
http_request_duration_seconds_count 1000
http_request_duration_seconds_sum 180

le 表示 bucket 的上界,并且 bucket 是累计的。这里 1000 个请求中:

  • 950 个请求耗时不超过 0.1 秒;
  • 990 个请求耗时不超过 0.5 秒;
  • 1000 个请求被计入无限上界桶。

P95 可以用:

histogram_quantile(
  0.95,
  sum by (le) (
    rate(http_request_duration_seconds_bucket[5m])
  )
)

histogram_quantile 不是从原始请求样本精确计算分位数,而是根据 bucket 分布进行估计。桶越粗,估计误差可能越大。如果 P95 跨越了一个很宽的 bucket,结果的解释需要谨慎。

聚合 Histogram 时必须保留 le

sum by (le) (rate(metric_bucket[5m]))

如果先把 le 聚合掉,就失去了桶边界,无法再计算分位数。


5.4 Summary:客户端侧分位数

Summary 通常在客户端计算分位数并暴露结果。它可以直接提供某个实例的 P95,但不同实例之间通常不能像 Histogram 那样可靠地聚合分位数。

例如,不能一般性地认为:

所有 Pod 的 P95 = 各 Pod P95 的平均值

所以在需要跨实例、跨区域聚合时,Histogram 通常更适合。Summary 并非错误,只是它的聚合语义不同。


六、标签、时间序列和高基数风险

Prometheus 中一条时间序列由以下组合确定:

指标名 + 所有标签键值

因此:

http_requests_total{route="/users",code="200",method="GET"}

和:

http_requests_total{route="/orders",code="200",method="GET"}

是两条不同的时间序列。

如果把 user_id、完整 URL、订单号或 trace ID 作为标签,时间序列数量可能随请求量增长。其结果可能是:

  • Prometheus 内存和磁盘占用上升;
  • 查询变慢;
  • scrape 响应变大;
  • 服务本身生成指标的成本增加;
  • 监控系统因资源耗尽影响其他查询和告警。

因此,路由标签应使用模板化路径:

/users/:id

而不是:

/users/8d1f...

常见做法是:

  • 保留有限的业务维度,如 serviceregionmethodstatus_class
  • 对 HTTP 状态码使用 2xx4xx5xx 等分类;
  • 对用户、请求、订单等高基数标识使用日志或 Trace;
  • 通过 recording rule 预聚合常用查询。

这正是日志、指标和追踪的边界:指标适合低基数聚合,日志适合保存具体事件,Trace 适合关联一次请求在多个服务中的路径。


七、Prometheus 告警的生命周期

Prometheus 告警通常包含两个部分:

  1. Prometheus 通过 PromQL 判断条件;
  2. Alertmanager 负责分组、去重、静默、抑制和通知。

一个告警规则示例:

groups:
  - name: web.rules
    rules:
      - alert: WebHighServerErrorRate
        expr: |
          (
            sum(rate(http_requests_total{
              job="web",
              code=~"5.."
            }[5m]))
            /
            sum(rate(http_requests_total{
              job="web"
            }[5m]))
          ) > 0.05
        for: 10m
        labels:
          severity: page
          service: web
        annotations:
          summary: "web 5xx 错误率过高"
          description: "过去 5 分钟错误率超过 5%,且持续 10 分钟"

这个表达式的分子是每秒 5xx 请求数,分母是每秒全部请求数。二者相除得到错误率。使用 rate 而不是直接使用 Counter,是因为我们关心的是一个时间窗口内的变化速率。

for: 10m 表示条件必须连续成立 10 分钟,才从 pending 进入 firing。状态变化可以表示为:

inactive
   |
   | 条件首次成立
   v
pending
   |
   | 持续满足 for 时长
   v
firing
   |
   | 条件不再成立
   v
inactive

for 的作用是抑制瞬时毛刺,但也会增加检测延迟。如果表达式计算结果为空,或者指标暂时缺失,告警是否恢复取决于 Prometheus 的规则结果和 keep_firing_for 等配置,不能简单等同于“应用恢复”。

检查规则是否被加载:

curl -s http://prometheus.example/api/v1/rules | jq .

检查告警当前状态:

curl -s http://prometheus.example/api/v1/alerts | jq .

在 Prometheus UI 的 /rules/alerts 页面,也可以查看规则加载、表达式结果和告警状态。


八、Alertmanager 解决什么问题

Prometheus 适合判断“是否满足告警条件”,Alertmanager 适合处理“应该通知谁以及如何通知”。

Alertmanager 的核心能力包括:

  • 分组:把同一服务或同一集群的多个告警合并;
  • 去重:避免多个 Prometheus 副本重复发送;
  • 路由:按照 severityteamservice 选择接收者;
  • 静默:维护窗口期间暂时停止通知;
  • 抑制:例如节点整体故障时,抑制该节点上大量 Pod 的次级告警;
  • 恢复通知:告警条件恢复后发送 resolved 事件。

例如,Prometheus 发送的告警标签可能是:

labels:
  alertname: WebHighServerErrorRate
  service: web
  severity: page
  namespace: production

Alertmanager 可以使用这些标签决定:

severity=page       -> 值班电话
severity=warning   -> 团队聊天
service=database   -> 数据库团队
namespace=staging  -> 不发送生产值班通知

告警系统最容易出现的失败不是“没有规则”,而是:

  • 告警数量过多,值班人员无法判断优先级;
  • 告警没有明确的处理动作;
  • Alertmanager 自身不可用却没有监控;
  • Prometheus 与 Alertmanager 网络断开;
  • 告警通知渠道失效;
  • 规则依赖的指标被改名或采集失败。

因此应分别监控:

up{job="prometheus"}
up{job="alertmanager"}
prometheus_notifications_errors_total
prometheus_rule_evaluation_failures_total

具体指标名称可能随组件版本和部署方式变化,生产环境应以当前组件暴露的 /metrics 为准,而不是盲目复制旧版本规则。


九、从监控阈值到 SLO

9.1 SLI、SLO 和 SLA

SLI(Service Level Indicator) 是实际测量的服务指标,例如:

  • 成功请求比例;
  • 请求延迟;
  • 任务按时完成比例;
  • 数据新鲜度。

SLO(Service Level Objective) 是对 SLI 的目标,例如:

生产 API 在 30 天窗口内,99.9% 的有效请求应成功返回。

SLA(Service Level Agreement) 是对外合同或承诺,通常包含违约责任。SLO 可以作为内部目标,不一定等同于 SLA。

SLO 必须先明确分母和适用范围。“99.9% 可用性”至少需要回答:

  • 哪些请求属于有效请求;
  • 4xx 是否计为失败;
  • 5xx 是否计为失败;
  • 超时如何处理;
  • 健康检查请求是否排除;
  • 按请求数、按时间还是按用户计算;
  • 多区域服务如何聚合。

如果分母未定义,后续的错误率和错误预算都可能没有稳定含义。


9.2 错误预算的计算

设目标成功率为 SLOSLO,则错误预算比例为:

ErrorBudget=1SLOErrorBudget = 1 - SLO

如果 30 天可用性目标为 99.9%,则:

ErrorBudget=10.999=0.001ErrorBudget = 1 - 0.999 = 0.001

30 天总时间为:

30×24×60=43200 分钟30 \times 24 \times 60 = 43200 \text{ 分钟}

允许不可用时间为:

43200×0.001=43.2 分钟43200 \times 0.001 = 43.2 \text{ 分钟}

也就是约 43 分 12 秒。

如果过去 30 天累计失败请求占比为 0.0004,则消耗了:

0.0004/0.001=40%0.0004 / 0.001 = 40\%

的错误预算。

错误预算的意义在于把“稳定性目标”转换成可决策的量:

  • 预算充足时,可以更积极地发布或进行架构变更;
  • 预算快速消耗时,应降低变更风险并优先修复可靠性问题;
  • 预算耗尽不意味着系统立即停止服务,而是说明当前实现已经超出约定目标。

9.3 请求型 SLO 的 PromQL

假设应用暴露:

http_requests_total{service="checkout",code="200"}
http_requests_total{service="checkout",code="500"}

30 天成功率可以写成:

1 -
(
  sum(increase(http_requests_total{
    service="checkout",
    code=~"5.."
  }[30d]))
  /
  sum(increase(http_requests_total{
    service="checkout"
  }[30d]))
)

错误预算消耗比例可以写成:

(
  sum(increase(http_requests_total{
    service="checkout",
    code=~"5.."
  }[30d]))
  /
  sum(increase(http_requests_total{
    service="checkout"
  }[30d]))
)
/
(1 - 0.999)

当结果大于 1 时,表示错误预算已经消耗超过 100%。

这个表达式成立的前提是:

  1. 所有相关实例都暴露相同的指标;
  2. code 的分类规则稳定;
  3. 计数器没有因为部署、迁移或标签变更产生语义断裂;
  4. 分子和分母使用同一组流量;
  5. 30 天数据在 Prometheus 的保留期内可查询。

如果 Prometheus 只保留 15 天,就无法直接从本地数据计算完整的 30 天 SLO。生产环境通常使用 recording rules、远程存储或专门的 SLO 组件保存更长时间范围。


十、为什么只按“错误率超过 5%”告警不够

固定阈值告警没有表达错误预算的速度。

设:

  • 目标 SLO 为 99.9%;
  • 允许错误率为 0.1%;
  • 当前错误率为 1%。

则错误预算消耗速度,也称 burn rate,为:

BurnRate=ObservedErrorRateAllowedErrorRate=0.010.001=10BurnRate = \frac{ObservedErrorRate}{AllowedErrorRate} = \frac{0.01}{0.001} = 10

含义是:如果这个错误率持续不变,错误预算将以正常速度的 10 倍消耗。

对于 30 天窗口,若希望 1 小时内消耗掉全部错误预算,则所需 burn rate 为:

30×241=720\frac{30 \times 24}{1} = 720

实际告警会使用更短的检测窗口和更低的 burn rate。例如,常见的多窗口思路是:

  • 1 小时窗口发现快速恶化;
  • 5 分钟窗口确认当前问题仍然存在;
  • 两个窗口同时满足才触发高优先级告警。

PromQL 示例:

(
  sum(rate(http_requests_total{
    service="checkout",
    code=~"5.."
  }[1h]))
  /
  sum(rate(http_requests_total{
    service="checkout"
  }[1h]))
) > 14.4 * (1 - 0.999)
and
(
  sum(rate(http_requests_total{
    service="checkout",
    code=~"5.."
  }[5m]))
  /
  sum(rate(http_requests_total{
    service="checkout"
  }[5m]))
) > 14.4 * (1 - 0.999)

这里:

14.4×0.001=0.014414.4 \times 0.001 = 0.0144

也就是错误率超过 1.44% 时,按 14.4 倍速度消耗 99.9% SLO 的错误预算。

两层窗口的原因是:

  • 仅看 5 分钟,容易被瞬时流量尖峰误触发;
  • 仅看 1 小时,可能在问题已经严重影响用户后才触发;
  • 长窗口和短窗口同时成立,可以兼顾持续性和响应速度。

实际 burn rate 阈值不是 Kubernetes 或 Prometheus 的规范保证,而是 SLO 设计选择。应根据业务的用户影响、响应时间和通知渠道调整。


十一、延迟 SLO 与 Histogram

如果 SLO 是:

99% 的有效请求应在 300ms 内完成

可以直接使用 Histogram 的桶:

1 -
(
  sum(rate(http_request_duration_seconds_bucket{
    service="checkout",
    le="0.3"
  }[5m]))
  /
  sum(rate(http_request_duration_seconds_count{
    service="checkout"
  }[5m]))
)

这个表达式计算的是:

超过 300ms 的请求比例

如果超过 300ms 的请求比例为 0.8%,那么满足 300ms 延迟目标的请求比例为 99.2%。

这种“阈值型延迟 SLO”通常比直接监控 P99 更适合错误预算,因为它可以明确划分:

  • 分母:所有有效请求;
  • 好请求:耗时不超过阈值;
  • 坏请求:耗时超过阈值。

P99 则回答“分位点在哪里”,不直接表示有多少请求超标。并且 P99 对 Histogram 的桶边界敏感,必须结合实际桶配置解释。


十二、缺失数据、低流量和查询边界

12.1 低流量时错误率不稳定

如果 5 分钟内只有一个请求,而它失败了,错误率就是 100%。这可能足以说明一次真实失败,但未必适合立即触发高优先级告警。

可以同时增加请求量条件:

(
  error_rate > 0.01
)
and
(
  sum(rate(http_requests_total{service="checkout"}[5m])) > 1
)

具体阈值应根据服务流量和业务影响确定。低流量服务也可以采用按时间窗口的可用性计算,但要清楚“请求比例”和“时间比例”是不同 SLI。


12.2 指标缺失不是零

以下两个结果不同:

metric_name == 0

表示指标存在且值为零。

absent(metric_name)

表示查询结果中没有该指标。

如果服务下线、标签变更或抓取失败,指标可能消失。此时把空结果当成零,可能掩盖真实故障。对关键采集链路,应单独设计 upabsent 或目标数量告警。

例如:

absent(up{job="checkout"})

可以发现整个 job 没有任何目标,但不能发现某个实例少了一个。后者需要结合服务发现目标数量、期望副本数和实际抓取目标数量进行判断。


12.3 Prometheus 不是无限历史数据库

Prometheus 本地存储具有保留时间和容量限制。长时间窗口查询会受到:

  • 本地数据保留期;
  • 磁盘容量;
  • 时间序列数量;
  • 查询复杂度;
  • 压缩和写入负载

的限制。

生产环境通常通过以下方式扩展:

  • Prometheus remote write;
  • 兼容 Prometheus API 的长时存储;
  • 多 Prometheus 分片;
  • 按集群或区域分层;
  • recording rules 预计算长期查询所需的聚合结果。

这些方案不是 Kubernetes API 的一部分,各自的可靠性、成本、查询一致性和故障恢复行为需要单独验证。


十三、从“服务正常”到“用户正常”的监控层次

仅监控 Pod 是否 Running 不足以代表服务可用。一个 Pod 可以处于 Running,但仍然:

  • Readiness 检查失败;
  • 能接受 TCP 连接但无法处理请求;
  • 依赖的数据库不可用;
  • 只对某个区域或某类用户失败;
  • 延迟已经超过用户可接受范围。

因此监控通常至少分为几层:

基础设施层

关注:

  • Node 是否 Ready;
  • CPU、内存、磁盘、网络;
  • kubelet 和容器运行时;
  • 节点压力和资源耗尽。

Kubernetes 控制面层

关注:

  • API Server 请求错误和延迟;
  • Scheduler 调度失败;
  • Controller Manager 队列和同步延迟;
  • etcd 延迟、容量和 leader 状态。

工作负载层

关注:

  • Deployment 可用副本;
  • Pod Pending、CrashLoopBackOff、频繁重启;
  • HPA 是否达到最大副本;
  • PodDisruptionBudget 是否阻止维护;
  • Service 和 EndpointSlice 是否符合预期。

应用层

关注:

  • 请求成功率;
  • 请求延迟;
  • 饱和度,如线程池、连接池、队列;
  • 依赖错误;
  • 业务操作成功率。

用户体验层

关注:

  • 真实用户或合成探针是否能够完成关键流程;
  • 跨区域、跨网络路径的访问结果;
  • DNS、TLS、Ingress、负载均衡和应用联合结果。

SLO 应尽量建立在用户可感知的应用层指标上,基础设施指标更适合解释原因和提前发现风险。把“CPU 超过 80%”直接定义成“用户不可用”是常见误区,因为高 CPU 可能没有影响响应时间,也可能应用在 CPU 很低时因数据库连接耗尽而完全不可用。


十四、一个故障诊断路径

假设收到“checkout 服务 5xx 错误率过高”告警,可以按数据流逐层验证。

第一步:确认告警表达式本身有数据

sum(rate(http_requests_total{service="checkout"}[5m]))

如果结果为空,先不要诊断应用代码,应检查:

  • Prometheus 规则是否加载;
  • 指标名称是否改变;
  • job 和标签是否匹配;
  • scrape 是否成功;
  • Prometheus 是否有时间序列保留问题。

第二步:确认目标是否可抓取

up{job="checkout"}

如果 up=0,检查:

kubectl get pods -n production -l app=checkout -o wide
kubectl describe pod -n production <pod-name>
kubectl logs -n production <pod-name> --since=15m

注意 up=0 说明指标抓取失败,不一定说明业务请求失败。

第三步:区分应用错误和依赖错误

按标签拆分错误:

sum by (code, route) (
  rate(http_requests_total{
    service="checkout",
    code=~"5.."
  }[5m])
)

如果某个路由集中失败,再结合日志中的 request ID 或 Trace ID 检查数据库、支付服务、消息队列等依赖。

第四步:确认 Kubernetes 是否正在替换 Pod

kubectl get events -n production --sort-by=.lastTimestamp
kubectl get pods -n production -l app=checkout
kubectl rollout history deployment/checkout -n production
kubectl describe deployment checkout -n production

如果故障紧接着发布发生,应检查:

  • 新旧 ReplicaSet 的错误率;
  • Readiness 是否过早成功;
  • 新版本配置和 Secret;
  • 滚动更新期间是否保留足够可用副本;
  • 是否触发了回滚或 CrashLoopBackOff。

第五步:检查是否只是监控链路故障

如果用户访问正常,但 Prometheus 告警异常,应检查:

  • 指标 endpoint 是否超时;
  • Prometheus 到 Pod 的网络策略;
  • ServiceMonitor 或 scrape 配置;
  • Prometheus 自身资源是否不足;
  • Alertmanager 是否收到告警;
  • 通知渠道是否失败。

监控系统本身也必须被监控,否则“没有告警”可能只是告警系统已经失效。


十五、监控配置的生产风险

15.1 只监控 kubectl top

这会导致团队拥有资源使用数据,却没有:

  • 历史趋势;
  • 业务错误率;
  • 时延分布;
  • 告警路由;
  • SLO 和错误预算。

Metrics Server 应被看作 Kubernetes 资源指标接口,而不是完整可观测性平台。

15.2 只监控平均延迟

平均值可能掩盖尾部延迟。例如 99 个请求耗时 10ms,1 个请求耗时 10s:

Average=99×0.01+1×10100=0.1099sAverage = \frac{99 \times 0.01 + 1 \times 10}{100} = 0.1099s

平均延迟约 110ms,看起来可能正常,但 P99 已经接近 10 秒。面向用户的延迟 SLO 通常需要 Histogram 和分位数或阈值桶。

15.3 用 Pod 状态直接定义可用性

Running 只表示容器进程存在,不表示请求能够成功。应至少结合 Readiness、服务请求指标和用户路径探测。

15.4 告警没有行动语义

告警应能回答:

  • 影响哪个服务;
  • 影响范围多大;
  • 何时开始;
  • 违反了哪个目标;
  • 值班人员第一步该检查什么;
  • 是否需要回滚、扩容或切换流量。

summarydescription 不是装饰字段,它们决定告警到达值班人员后能否快速行动。

15.5 通过扩大采集频率解决所有问题

缩短 scrape interval 会增加:

  • Prometheus CPU;
  • 网络流量;
  • 存储样本数量;
  • 查询和压缩负载。

如果目标是发现 30 秒内的高频变化,应先确认指标是否真的需要这么高的采样频率,还是应该由应用内部聚合、日志或 Trace 补充。采集频率、告警窗口和业务响应时间必须共同设计。


十六、指标、日志和追踪如何关联

指标适合发现问题,日志适合解释单个事件,Trace 适合还原一次请求跨服务的路径。

一个可操作的关联方式是:

  1. Prometheus 发现 checkout 的 5xx 错误率升高;
  2. routestatusregion 等低基数标签缩小范围;
  3. 从应用日志检索对应时间段、实例和错误类型;
  4. 从日志中的 trace_id 跳转到 OpenTelemetry Trace;
  5. 在 Trace 中查看数据库、远程服务和队列调用的时延;
  6. 将基础设施指标与该时间段的节点、网络和容器状态对齐。

不要把 trace_id 作为 Prometheus 标签,否则每个请求都可能产生一条新时间序列。Trace ID 应留在日志字段、Trace span 或专门的关联系统中。

日志采集链路中的 stdout、Node Agent、Sidecar、采集和留存,解决的是事件文本及结构化记录的传输与保存;分布式追踪中的 Context 和采样,解决的是请求跨服务传播与链路保留;Prometheus 解决的是低基数、可聚合、可查询的时间序列。三者相互补充,但不能互相替代。


十七、最终的边界判断

可以用下面的问题选择工具:

问题 主要工具
当前 Pod 用了多少 CPU 和内存 Metrics Server、kubectl top
HPA 应根据什么资源指标扩缩 Metrics API,通常由 Metrics Server 提供
Deployment 当前可用副本是否不足 kube-state-metrics + Prometheus
过去 30 天成功率是多少 Prometheus 或长时存储
P95/P99 延迟是多少 Prometheus Histogram + PromQL
什么时候通知值班人员 Prometheus 告警规则 + Alertmanager
是否达到 99.9% SLO SLI 计算、错误预算和长期存储
某次请求为什么失败 日志和分布式 Trace
节点是否因为磁盘压力导致 Pod 异常 节点指标、Kubernetes 状态指标、事件和日志

最重要的因果关系是:

资源指标 API ≠ 历史监控系统
Prometheus 查询 ≠ 告警通知
告警阈值 ≠ SLO
Pod Running ≠ 用户请求成功
指标异常 ≠ 故障根因

一个可维护的 Kubernetes 监控体系,应让资源指标支撑伸缩,让 Prometheus 保存和计算时序,让 Alertmanager 处理通知,让 SLO 约束“什么程度的问题值得打断值班人员”,再用日志和 Trace 完成根因定位。只有这些层次的数据定义一致、采集链路可验证、告警状态可解释,监控才不仅能“看到数字”,还能够支持发布、故障响应和可靠性决策。


系列导航与关联阅读

官方资料

本文依据 Kubernetes、CNCF 与相关项目官方文档重新梳理;正文和生产清单由 WR BLOG 编写。