Skip to content

第四部分 大规模可观测性体系

当集群规模从几十台节点增长到数千台节点时,可观测性系统本身就会成为一个"大规模分布式系统"。在数千节点的 RKE2 集群上,监控、日志、告警任何一环照搬社区默认配置,都会在数周内崩溃:Prometheus OOM、存储账单失控、告警风暴淹没值班群。本部分从挑战出发,给出经过生产验证的架构选型、配置参数、告警规则与治理方法,目标只有一个:在数千节点规模下,观测系统既要看得清,也要活得久、花得少

适用版本:Prometheus 2.x(≥ 2.45)、Thanos 0.3x、VictoriaMetrics 最新稳定版(v1.9x 系列)、Grafana 10+、Loki 2.9+/3.x、RKE2 v1.28+。


第 1 章 大规模监控的挑战

1.1 数千节点监控的特殊性

在小集群里,监控是"装个 kube-prometheus-stack 就完事"。在数千节点的 RKE2 集群上,第一个扑面而来的问题是规模本身

维度100 节点集群3000 节点集群倍数
node-exporter / kubelet/cAdvisor~6,000 series/节点~6,000 series/节点(Pod 密度相关)30x
kube-state-metrics~50 万 series~200 万+ series4x+
单次采集总 series~100 万3000 万~1 亿+30~100x
15s 采集下写入速率~7 万 samples/s200 万~700 万 samples/s30~100x
30 天本地保留磁盘(压缩后)~200 GB6~20 TB30~100x

1.1.1 指标基数爆炸(Cardinality Explosion)

基数爆炸是数千节点集群监控的头号杀手。它不是"指标多",而是"同一个指标的标签组合数失控"。

典型爆炸源:

  • container_* 系列指标:Pod × 容器 × 节点 = 海量 series,Job/CronJob 场景下 pod 标签每次运行都产生新值。
  • 应用自定义指标埋点失控:把 user_idrequest_id、含 ID 的 url_path 放进 label。
  • apiserver 指标apiserver_request_duration_seconds_bucket{verb, group, version, resource, ..., le},数千节点 + 大量控制器场景下轻松突破百万 series。
  • ingress-nginxnginx_ingress_controller_request_duration_seconds_bucket 默认带 path/host,一个集群即可产生上亿 series。

排查基数的常用手段:

bash
# Prometheus 内置的基数分析(2.40+)
curl -s http://prometheus:9090/api/v1/status/tsdb | jq '.data.headStats'
promql
# 当前 series 数 Top20 的指标名
topk(20, count by (__name__)({__name__=~".+"}))
# 每个 job 的 series 规模
sort_desc(count by (job)({__name__=~".+"}))

在采集端用 metric_relabel_configs 直接丢弃高基数指标,是最便宜也最有效的手段:

yaml
# ServiceMonitor / ScrapeConfig 片段:丢弃 cAdvisor 高基数指标
metricRelabelings:
  - sourceLabels: [__name__]
    regex: 'container_(network_tcp_usage_total|network_udp_usage_total|tasks_state|file_descriptors).*'
    action: drop

1.1.2 采集压力

  • 抓取延迟:单实例 series 超过 500 万后,15s 间隔内抓不完所有 target,scrape_duration_seconds 超过 interval,出现数据缺口。
  • 内存:经验值 每 100 万 active series ≈ 1~2 GB 内存;单实例撑到 3000 万 series 需要 60~100 GB,且大查询会拖垮抓取。
  • 目标发现压力:Kubernetes SD 的频繁 LIST 让 Prometheus 本身成为 apiserver 的大客户。

1.1.3 存储成本

Prometheus 本地 TSDB 的压缩率通常能做到 1~2 字节/样本。粗算:

300 万 samples/s × 86400 s/天 × 1.5 字节 ≈ 389 GB/天
30 天 ≈ 11.7 TB(单副本)

结论非常直接:数千节点集群绝不能依赖 Prometheus 本地盘做长期存储,必须引入 remote_write + 对象存储的长期存储层(Thanos / VictoriaMetrics / Mimir),并在采集层做降采样和指标裁剪。

1.2 监控体系分层

大规模集群必须分层监控,每层独立的采集、存储与告警策略:

┌──────────────────────────────────────────────────────────┐
│  业务层   订单成功率、支付延迟、SLI/SLO(业务方自定义)      │
├──────────────────────────────────────────────────────────┤
│  应用层   RED 指标(Rate/Errors/Duration)、JVM、队列深度  │
├──────────────────────────────────────────────────────────┤
│  K8s 层   apiserver/etcd/scheduler/kubelet/Pod/节点事件    │
├──────────────────────────────────────────────────────────┤
│  基础设施层  硬件(IPMI)、OS、磁盘 SMART、网卡、交换机      │
└──────────────────────────────────────────────────────────┘
层级核心工具保留策略告警责任人
基础设施层node-exporter、IPMI exporter、SNMP exporter30~90 天IDC/系统组
K8s 层kube-state-metrics、控制平面抓取、RKE2 特有探针30~180 天(长期存储 1 年降采样)平台组(SRE)
应用层Prometheus SDK、OpenTelemetry、ServiceMonitor15~30 天应用团队
业务层自定义 SLI 记录规则、Grafana SLO1 年+(仅记录规则结果)业务方

分层的关键收益:量大的层(K8s/基础设施)激进丢弃与降采样;量小但价值高的层(业务 SLI)长期保留。

1.3 RKE2 特有监控点

RKE2 与 kubeadm 集群的监控差异点,很多"经验帖"不会告诉你:

  1. 控制平面组件的部署形态:RKE2 的 etcd、kube-apiserver、kube-controller-manager、kube-scheduler 以 static pod 运行(manifest 在 /var/lib/rancher/rke2/agent/pod-manifests/),而 kubelet 是 rke2-server 进程内嵌 或由 supervisor 拉起的独立进程(随版本不同)。抓取地址与端口与 kubeadm 集群一致(apiserver 6443、etcd 2379/2381、scheduler 10259、controller-manager 10257),但证书路径不同

    • etcd 客户端证书:/var/lib/rancher/rke2/server/tls/etcd/
    • apiserver 证书:/var/lib/rancher/rke2/server/tls/
  2. RKE2 supervisor:rke2-server / rke2-agent 本身是一个 Go 进程(supervisor 模式拉起各组件)。systemctl status rke2-server 的存活只是第一层;建议用 blackbox 探测 + 进程指标双重监控:

bash
# 节点上检查 rke2-server 组件容器实际运行状态
/var/lib/rancher/rke2/bin/crictl ps | grep -E 'kube-apiserver|etcd|kube-scheduler'

# 检查 supervisor 日志(static pod 反复重建的典型症状在这里)
journalctl -u rke2-server --since "1 hour ago" | grep -E "error|fail" | tail -50
  1. etcd 暴露指标端口:RKE2 默认 etcd 的 metrics 通过 https://<node-ip>:2381/metrics 暴露(2381 为 HTTP 明文指标端口,2379 为客户端端口)。kube-prometheus-stack 的 etcd 抓取 job 需要指向 2381,否则需要配证书。

  2. CNI 差异:RKE2 默认 Canal(Calico + Flannel),网络观测用 Calico felix 指标;换 Cilium 则可上 Hubble(见第 6 章)。

  3. static pod 的"假死":apiserver 挂掉时 kubectl 不可用,但监控不能因此失明——这正是"监控要独立于被监控集群"的原因(见第 8 章)。

一个 RKE2 server 节点的监控抓取面示意:

┌───────────────────────── RKE2 Server 节点 ─────────────────────────┐
│  rke2-server 进程 (supervisor)                                      │
│    ├─ etcd        :2381/metrics (HTTP)  ←── scrape etcd job         │
│    ├─ kube-apiserver  :6443/metrics     ←── scrape apiserver job    │
│    ├─ kube-scheduler  :10259/metrics    ←── scrape scheduler job    │
│    └─ kube-controller-manager :10257    ←── scrape kcm job          │
│  kubelet          :10250/metrics,/metrics/cadvisor                  │
│  node-exporter    :9100/metrics                                     │
│  日志: journalctl -u rke2-server  /  /var/log/pods (static pods)    │
└─────────────────────────────────────────────────────────────────────┘

注意事项:RKE2 升级版本时组件端口与证书路径可能变化(如 supervisor 模式在 v1.24+ 的调整),升级前务必在测试集群核对 ServiceMonitor 的 endpoint 与证书引用,升级后第一时间看 up{job=~".*apiserver.*|.*etcd.*"} 是否为 1。

1.4 小结与最佳实践

  • 先砍基数,再谈架构:不做 metric drop 就上加利存储,等于用钱填无底洞。
  • 采集与存储分离:Prometheus 只保留 2~6 小时本地数据,长期数据进对象存储。
  • 监控出集群:告警链路与元监控(meta-monitoring)至少放到被监控集群之外。
  • 分层治理:每层独立的保留、降采样与告警责任边界。

第 2 章 指标架构选型与实战

2.1 kube-prometheus-stack 在数千节点下的调优

kube-prometheus-stack(Helm chart)仍然是落地的最佳起点,但默认值是为小集群准备的。数千节点下必须调整以下关键点。

2.1.1 Prometheus 分片(Sharding)

单 Prometheus 装不下整个集群的指标时,按"抓取目标"分片:

方式一:chart 内置 sharding(按 target 自动 hash)

yaml
# values.yaml(kube-prometheus-stack)
prometheus:
  prometheusSpec:
    shards: 4                    # 每个 target 按 hash 分配到 4 个分片之一
    replicas: 2                  # 每分片 2 副本(HA)
    retention: 6h                # 本地只留 6 小时,长期数据走 remote_write
    scrapeInterval: 30s          # 大规模下放宽到 30s,采集压力减半
    evaluationInterval: 30s
    resources:
      requests: { cpu: "8",  memory: 32Gi }
      limits:   { cpu: "16", memory: 64Gi }

注意:shards 分片后,记录规则(recording rules)若依赖跨分片聚合会算错。因此跨分片的聚合规则应下沉到 Thanos ruler / VM 的 vmalert,或保证规则用到的指标落在同一分片。

方式二:按职能拆多个 Prometheus(推荐用于数千节点)prom-infra(node-exporter/IPMI/交换机,量最大价值低)、prom-k8s(kubelet/cAdvisor/KSM/控制平面,平台组核心)、prom-app(业务指标,应用团队自助),各自独立 HA 对,统一 remote_write 到 Thanos/VM 长期存储,告警由 Ruler/vmalert + Alertmanager 承担。

2.1.2 保留与 remote_write

yaml
prometheus:
  prometheusSpec:
    retention: 6h
    retentionSize: 40GB
    walCompression: true
    remoteWrite:
      - url: http://thanos-receive.monitoring:19291/api/v1/receive
        # 大规模下必须调队列参数,否则 remote_write 积压丢数据
        queueConfig:             # 大规模下必须调队列参数
          capacity: 10000        # 每分片内存队列容量
          maxShards: 200         # 并发分片数(影响远端压力)
          maxSamplesPerSend: 5000
          batchSendDeadline: 5s
        writeRelabelConfigs:
          # 只把需要的指标 remote_write 到长期存储,显著降成本
          - sourceLabels: [__name__]
            regex: 'container_(network_tcp_usage_total|network_udp_usage_total).*'
            action: drop

验证 remote_write 健康状况:

promql
# 队列积压(持续 >0 说明远端吃不下)
prometheus_remote_storage_samples_pending / prometheus_remote_storage_samples_in_total
# 发送失败率
rate(prometheus_remote_storage_samples_failed_total[5m])
/ rate(prometheus_remote_storage_samples_total[5m])

2.1.3 其他关键调优项

参数建议值(3000 节点参考)说明
scrapeInterval30s(节点/控制平面)~ 60s(业务)间隔翻倍,成本减半
--query.max-concurrency / --query.timeout20~40 / 2m防大查询打爆
KSM --metric-denylist丢弃 kube_pod_container_status_.*terminated.*KSM 本身也是基数大户
KSM 分片--shard=0 --num-shards=3KSM 2.x 原生支持分片
cAdvisor metric_relabel_configs丢弃 per-container 的 tcp/udp 细粒度指标收益最大的一项

注意事项:Prometheus shards 扩容/缩容会导致历史数据留在旧分片上,短期查询出现缺口。生产上扩缩分片应选择低峰期,并依赖长期存储层(Thanos Querier / vmselect)做跨分片全局查询。

2.2 Thanos 架构实战

Thanos 是"给 Prometheus 加上全局视图 + 无限存储"的经典方案,两种数据接入模式。

2.2.1 Sidecar 模式 vs Receive 模式

维度Sidecar 模式Receive 模式
数据路径Sidecar 伴随每个 Prometheus 上传块到对象存储Prometheus remote_write 主动推给 Receive
实时性实时数据走 sidecar 直查 Prometheus,秒级延迟写进即可查,实时性好
Prometheus 依赖必须是完整实例(本地保留 ≥ 2×block)可退化到 Agent 模式(无本地存储)
网络上传流量集中在 sidecar写入流量集中在 receive 集群
适用已有多个 Prometheus、改造成本低数千节点新建、采集层无状态化

数千节点新建推荐 Receive 模式:采集层全部用 Agent 模式,无本地盘,弹性伸缩无负担。

2.2.2 组件部署架构

  Prometheus Agent ×N ──remote_write──▶ Thanos Receive (hashring×3, 副本2)
                                              │  写本地 + 上传块

                                        ┌────────────┐
                                        │ 对象存储 S3 │◀── Thanos Compactor
                                        │ (块数据)    │    (降采样/压实/保留)
                                        └─────┬──────┘

  Grafana ──query──▶ Thanos Querier ──┬───────┘ (查历史块)
                                      └────────▶ Store Gateway (缓存+索引)

2.2.3 关键部署配置

yaml
# thanos-receive StatefulSet 片段(3 副本组成 hashring)
containers:
  - name: thanos-receive
    image: quay.io/thanos/thanos:v0.36.1
    args:
      - receive
      - --tsdb.path=/var/thanos/receive
      - --tsdb.retention=12h
      - --grpc-address=0.0.0.0:10901
      - --http-address=0.0.0.0:10902
      - --remote-write.address=0.0.0.0:19291
      - --receive.replication-factor=2            # 双副本写入
      - --receive.hashrings-file=/etc/thanos/hashrings.json
      - --objstore.config-file=/etc/thanos/objstore.yml
      - --label=receive_replica="$(NAME)"         # 去重标签
      - --label=cluster="prod-rke2-01"            # 多集群全局视图的外部标签
yaml
# thanos-compactor 部署片段(单例!多实例会互相锁冲突)
args:
  - compact
  - --data-dir=/var/thanos/compact
  - --objstore.config-file=/etc/thanos/objstore.yml
  - --retention.resolution-raw=30d                # 原始数据 30 天
  - --retention.resolution-5m=180d                # 5 分钟降采样 180 天
  - --retention.resolution-1h=2y                  # 1 小时降采样 2 年
  - --compact.concurrency=4
  - --delete-delay=48h
yaml
# thanos-querier 片段
args:
  - query
  - --http-address=0.0.0.0:10902
  - --grpc-address=0.0.0.0:10901
  - --store=dnssrv+_grpc._tcp.thanos-store-gateway.monitoring.svc.cluster.local
  - --store=dnssrv+_grpc._tcp.thanos-receive.monitoring.svc.cluster.local
  - --query.replica-label=receive_replica        # 按副本标签去重
  - --query.replica-label=prometheus_replica
  - --query.timeout=2m
yaml
# objstore.yml(S3 兼容,如 MinIO / Ceph RGW / 阿里 OSS,供 receive/store/compactor 共用)
type: S3
config:
  bucket: "thanos-prod-01"
  endpoint: "s3.cn-north-1.example.com"
  access_key: "xxx"
  secret_key: "xxx"
  insecure: false

2.2.4 Store Gateway 容量规划

Store Gateway 是查询历史块的"索引缓存层",内存需求 ≈ 活跃块索引总大小的 1/4~1/3。数千节点 30 天 raw 保留下建议每个 16~32 GB 内存,横向 2~4 副本并启用分片:

yaml
args:
  - store
  - --objstore.config-file=/etc/thanos/objstore.yml
  - --index-cache.config-file=/etc/thanos/index-cache.yml   # memcached/redis 索引缓存
  - --chunk-pool-size=12GB
  - --min-time=-30d                                          # 只服务近 30 天的块

最佳实践

  • Compactor 必须单副本,给它独立大内存节点(压实是 CPU/内存密集型)。
  • 对象存储桶开启生命周期规则兜底(即使 Compactor 挂了也不至于无限增长账单)。
  • Receive 的 local storage 用 SSD,它是写入热点。
  • 跨集群统一加 cluster 外部标签,Thanos 才能成为真正的"多集群全局视图"。

2.3 VictoriaMetrics 集群版实战

VictoriaMetrics(下称 VM)集群版是 Thanos 的强劲对手:架构更简单、压缩率更高、资源消耗更低

2.3.1 架构

  vmagent (DaemonSet/部署, 抓取) ──remote_write──▶ vminsert ×N (无状态写入路由)
                                                        │ 按 series hash 分片

                                                  vmstorage ×N (有状态存储节点)

  Grafana ──▶ vmselect ×N (无状态查询) ──────────────────┘

三个组件各自独立扩缩容:写入不够扩 vminsert,查询不够扩 vmselect,容量不够扩 vmstorage

2.3.2 关键部署参数

yaml
# vmstorage(StatefulSet);扩容后新数据按新分片分布,旧数据不迁移
args:
  - --storageDataPath=/vmstorage-data
  - --retentionPeriod=3            # 单位:月;3 个月
  - --memory.allowedPercent=60
  - --dedup.minScrapeInterval=30s  # 与采集间隔一致,双副本采集去重
  - --search.maxUniqueTimeseries=10000000
yaml
# vminsert
args:
  - --storageNode=vmstorage-0:8400,vmstorage-1:8400,vmstorage-2:8400
  - --replicationFactor=2               # 同一条数据写 2 个 storage
  - --maxLabelsPerTimeseries=40         # 防标签失控
  - --maxLabelValueLen=4096
yaml
# vmselect
args:
  - --storageNode=vmstorage-0:8401,vmstorage-1:8401,vmstorage-2:8401
  - --dedup.minScrapeInterval=30s
  - --search.maxQueryDuration=2m
  - --search.maxConcurrentRequests=32
  - --cacheDataPath=/vmselect-cache    # 本地 SSD 查询缓存
yaml
# vmagent(Prometheus Agent 的 VM 版,兼容 prometheus.yml 抓取配置)
args:
  - --promscrape.config=/etc/vmagent/scrape.yml
  - --remoteWrite.url=http://vminsert-0:8480/insert/0/prometheus
  - --remoteWrite.url=http://vminsert-1:8480/insert/0/prometheus
  - --remoteWrite.maxDiskUsagePerURL=10GB   # 磁盘缓冲是杀手锏:vminsert 全挂时数据不丢
  - --memory.allowedPercent=50

2.3.3 Thanos vs VictoriaMetrics 对比表

维度Thanos (Receive)VictoriaMetrics 集群版
架构复杂度高(receive/querier/store/compactor/ruler/sidecar)低(insert/select/storage + agent)
存储压缩率基准(Prometheus 块)更高,通常节省 30~70% 磁盘
内存占用Store Gateway 内存需求大同容量下约为 Thanos 的 1/2~1/3
查询语言PromQLPromQL + MetricsQL(增强)
采集生态复用 Prometheus 生态vmagent 兼容 ServiceMonitor,也有原生 operator
多租户靠 external label 软隔离原生多租户(tenant id),适合多团队共用
高可用写replication-factor 2replicationFactor 2 + vmagent 磁盘缓冲(更强)
运维心智组件多,故障域多组件少,黑盒程度高
开源协议Apache 2.0Apache 2.0(集群版开源,企业功能收费)

2.3.4 选型决策树

是否需要长期存储/全局视图?
├─ 否(<500 节点,单 Prometheus 扛得住)→ kube-prometheus-stack 单实例 HA
└─ 是
   ├─ 已有大量 Prometheus,想渐进改造? ──是──▶ Thanos Sidecar 模式
   ├─ 新建、追求采集层无状态? ──────────────▶ Thanos Receive 或 VM
   │     ├─ 团队熟悉 Prometheus 生态、需要最丰富的社区资料 ──▶ Thanos
   │     └─ 追求低资源消耗、多租户、简单运维 ────────────────▶ VictoriaMetrics
   ├─ 超大规模(万节点级)、多区域? ─────────▶ Mimir / 分集群联邦 + 全局查询层
   └─ 云厂商托管可用? ─────────────────────▶ 托管 Prometheus(省运维、贵)

2.4 采集层:Agent 模式与 Grafana Alloy

2.4.1 Prometheus Agent 模式

Prometheus 2.x 内置 --enable-feature=agent:禁用本地 TSDB 持久化与查询/告警,只做抓取 + remote_write,内存占用降一半以上,且可用 Deployment/DaemonSet 随意扩缩:

yaml
containers:
  - name: prometheus-agent
    image: quay.io/prometheus/prometheus:v2.53.1
    args:
      - --enable-feature=agent                          # 只做抓取+remote_write
      - --config.file=/etc/prometheus/prometheus.yml
      - --storage.agent.path=/prometheus                # 仅 WAL 缓冲
      - --storage.agent.retention.max-time=6h

常见模式:按节点组/可用区部署多个 Agent,各自用 relabel 只抓本组节点,避免单 Agent 目标过多。

2.4.2 Grafana Alloy(原 Grafana Agent 后继)

Alloy 用"组件流水线"模型统一采集指标/日志/链路:

river
// alloy 配置示例:发现节点 kubelet 并抓取,remote_write 到 VM
discovery.kubernetes "kubelet" { role = "node" }

prometheus.scrape "kubelet" {
  targets         = discovery.kubernetes.kubelet.targets
  scrape_interval = "30s"
  forward_to      = [prometheus.remote_write.vm.receiver]
}

prometheus.remote_write "vm" {
  endpoint { url = "http://vminsert:8480/insert/0/prometheus/api/v1/write" }
}

选型建议:已在 Prometheus 生态就继续用 Prometheus Agent / vmagent;如果日志、指标、追踪想用一个 agent 一个 Helm chart 收敛,选 Alloy。

2.5 面试题

Q:Prometheus 分片(shards)和联邦(federation)有什么区别?

答:分片把同一集群的 target 按 hash 拆到多个 Prometheus 上,解决单机容量;联邦是层级聚合,下层把聚合后的少量指标/federate 提供给上层,解决多集群汇总。数千节点单集群应选分片 + 长期存储层——联邦会丢明细标签且配置维护成本极高。


第 3 章 控制平面深度监控(重点)

数千节点集群的任何风吹草动,最先失控的一定是控制平面。本章给出可直接生产的指标解读与告警规则。

3.1 kube-apiserver 关键指标

指标类型关注点经验阈值(3000 节点参考)
apiserver_request_duration_secondsHistogram请求延迟,按 verb/resource 拆分p99 LIST < 30s,GET p99 < 1s
apiserver_request_totalCounterQPS 与错误码分布(code 标签)5xx 比例 < 0.1%
apiserver_longrunning_requestsGaugewatch 等长连接请求数持续增长警惕 watch 泄漏
apiserver_current_inflight_requestsGauge正在处理的请求数(mutating/readOnly)接近上限即排队
apiserver_flowcontrol_request_concurrency_limitGaugeAPF 当前并发上限观察是否被动态调低
apiserver_flowcontrol_rejected_requests_totalCounterAPF 拒绝(429)数>0 即需分析哪个 PL 被打爆
apiserver_request_terminations_totalCounter请求超时/异常终止突增需排查
apiserver_storage_objectsGauge每类资源对象数pod/event 数监控
apiserver_storage_list_total / ..._returned_objects_totalCounterLIST 调用与返回对象数单次 LIST 返回百万对象 = 危险
etcd_request_duration_seconds(apiserver 侧)Histogramapiserver→etcd 请求延迟p99 < 500ms
apiserver_watch_events_total / ..._sizesCounter/Histogramwatch 事件推送量评估 informer 压力

常用 PromQL:

promql
# apiserver 5xx 错误率(5 分钟)
sum(rate(apiserver_request_total{code=~"5.."}[5m])) / sum(rate(apiserver_request_total[5m]))

# LIST pods 的 p99 延迟
histogram_quantile(0.99,
  sum(rate(apiserver_request_duration_seconds_bucket{verb="LIST",resource="pods"}[5m])) by (le))

# 当前 watch 长连接数
sum(apiserver_longrunning_requests)

# APF 拒绝速率(按 priority level)
sum by (priority_level) (rate(apiserver_flowcontrol_rejected_requests_total[5m]))

# 谁在做大 LIST:单次平均返回对象数
rate(apiserver_storage_list_returned_objects_total[5m]) / rate(apiserver_storage_list_total[5m])

APF(API Priority and Fairness)监控要点

数千节点集群必须启用 APF(RKE2 默认启用)。核心思路:APF 把请求分流到不同 PriorityLevel(如 systemworkload-highworkload-low),每个 PL 有独立并发配额。节点心跳(kubelet status/lease)、leader election 属于 system/le PL,绝不能被业务 LIST 挤占

bash
# 查看当前 APF 配置
kubectl get flowschemas,prioritylevelconfigurations

# 找出被打爆的 PL
kubectl get --raw /metrics | grep flowcontrol_rejected_requests_total | sort -k2 -nr | head

3.2 etcd 关键指标

指标含义告警线(经验)
etcd_disk_wal_fsync_duration_secondsWAL 落盘延迟(最敏感p99 > 500ms 危险,> 1s 严重
etcd_disk_backend_commit_duration_seconds后端提交延迟p99 > 250ms 关注
etcd_mvcc_db_total_size_in_bytesDB 实际大小接近 quota(默认 2G/建议 8G)告警
etcd_mvcc_db_total_size_in_use_in_bytesDB 使用大小与总量差距大 → 需要 compact/defrag
etcd_server_has_leader是否有 leader=0 立即 P0
etcd_server_leader_changes_seen_totalleader 变更次数1 小时内 >1 次需排查
etcd_server_proposals_failed_total / ..._pending提案失败 / 排队提案持续增长 / >10 危险
etcd_network_peer_round_trip_time_seconds节点间 RTTp99 > 50ms(同机房异常)
etcd_server_slow_apply_total / slow_read_indexes_total慢应用/慢读突增需排查
bash
# 在 RKE2 server 节点上检查 etcd 健康(证书路径为 RKE2 特有)
export ETCDCTL_API=3
alias ectl='etcdctl --endpoints=https://127.0.0.1:2379 \
  --cacert=/var/lib/rancher/rke2/server/tls/etcd/server-ca.crt \
  --cert=/var/lib/rancher/rke2/server/tls/etcd/client.crt \
  --key=/var/lib/rancher/rke2/server/tls/etcd/client.key'

ectl endpoint status --cluster -w table                          # 健康与 DB 大小
ectl endpoint status -w json | jq '.[].Status | {dbSize, dbSizeInUse}'  # 碎片率
ectl defrag --cluster   # 仅在确认需要时手动碎片整理,逐个 member 执行

注意事项:etcd 磁盘性能直接决定集群稳定性,数千节点必须用 本地 NVMe SSDfio 验证 WAL fsync p99 < 10ms);--quota-backend-bytes 提到 8GB(RKE2 通过 etcd-arg 配置)并保证 compact 正常;DB 暴涨的常见元凶是 ConfigMap/Secret 大对象、CRD 海量实例与 event 风暴。

3.3 scheduler / kubelet 关键指标

kube-scheduler

promql
# 端到端调度延迟 p99
histogram_quantile(0.99, sum(rate(scheduler_e2e_scheduling_duration_seconds_bucket[5m])) by (le))
# 调度失败率(找不到可调度节点)
rate(scheduler_schedule_attempts_total{result="unschedulable"}[5m])
/ rate(scheduler_schedule_attempts_total[5m])
# 待调度队列深度(突发批量建 Pod 时关键)
scheduler_pending_pods{queue="active"}

kubelet

promql
# PLEG relist p99 > 1s 说明节点上 Pod 操作开始卡顿
histogram_quantile(0.99, sum(rate(kubelet_pleg_relist_duration_seconds_bucket[5m])) by (le))
# 容器创建/启动等运行时操作延迟
histogram_quantile(0.99, sum(rate(kubelet_runtime_operations_duration_seconds_bucket[5m])) by (operation_type, le))
# kubelet 证书轮转错误(RKE2 自动轮转,但要监控是否生效)
kubelet_certificate_manager_client_expiration_renew_errors

3.4 可直接使用的 PrometheusRule 告警规则

以下规则假设通过 kube-prometheus-stack 部署,job 标签按 chart 默认(如你的 job 名不同,用 job=~".*apiserver.*" 模糊匹配)。

yaml
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: rke2-control-plane
  namespace: monitoring
  labels:
    prometheus: k8s
    role: alert-rules
spec:
  groups:
    # ============ apiserver ============
    - name: control-plane.apiserver
      rules:
        - alert: KubeAPIDown
          expr: absent(up{job=~".*apiserver.*"} == 1) or (count(up{job=~".*apiserver.*"} == 1) < 2)
          for: 5m
          labels: { severity: P0, team: platform }
          annotations: { summary: "apiserver 可用实例数不足 2 个,控制平面面临风险" }

        - alert: KubeAPIErrorRateHigh
          expr: |
            sum(rate(apiserver_request_total{code=~"5.."}[10m]))
            / sum(rate(apiserver_request_total[10m])) > 0.02
          for: 10m
          labels: { severity: P1, team: platform }
          annotations:
            summary: "apiserver 5xx 错误率超过 2%"
            runbook: "查 apiserver 日志与 etcd 延迟;按 resource/verb 定位错误源。"

        - alert: KubeAPISlowLIST
          expr: |
            histogram_quantile(0.99,
              sum(rate(apiserver_request_duration_seconds_bucket{verb="LIST"}[10m])) by (le, resource)
            ) > 15
          for: 15m
          labels: { severity: P2, team: platform }
          annotations:
            summary: "LIST {{ $labels.resource }} p99 延迟超过 15s"
            runbook: "用 storage_list_returned_objects 找大 LIST 来源:多为无 selector 全量 LIST 或失控控制器。"

        - alert: KubeAPIInflightSaturated
          expr: |
            max by (request_kind) (apiserver_current_inflight_requests)
            / on (request_kind) group_left
            max by (request_kind) (apiserver_flowcontrol_request_concurrency_limit) > 0.85
          for: 10m
          labels: { severity: P1, team: platform }
          annotations:
            summary: "apiserver inflight 达并发上限 85%({{ $labels.request_kind }})"
            runbook: "检查 APF FlowSchema,确认业务流量没有挤占 system 级别。"

        - alert: KubeAPFRejectingRequests
          expr: sum by (priority_level) (rate(apiserver_flowcontrol_rejected_requests_total[5m])) > 0.1
          for: 10m
          labels: { severity: P1, team: platform }
          annotations:
            summary: "APF 正在拒绝请求,PL={{ $labels.priority_level }}"
            runbook: "确认该 PL 流量来源;system/leader-election 被拒是 P0 前兆。"

    # ============ etcd ============
    - name: control-plane.etcd
      rules:
        - alert: EtcdNoLeader
          expr: min(etcd_server_has_leader) == 0
          for: 1m
          labels: { severity: P0, team: platform }
          annotations:
            summary: "etcd 集群失去 leader"
            runbook: "检查成员网络与磁盘;多数派存活可自愈,否则进入灾难恢复流程。"

        - alert: EtcdLeaderChangesFrequent
          expr: max(increase(etcd_server_leader_changes_seen_total[1h])) > 1
          for: 10m
          labels: { severity: P1, team: platform }
          annotations:
            summary: "etcd 1 小时内 leader 变更超过 1 次"
            runbook: "通常是磁盘慢(看 wal_fsync)或网络抖动(看 peer RTT)。"

        - alert: EtcdWALFsyncSlow
          expr: |
            histogram_quantile(0.99,
              sum(rate(etcd_disk_wal_fsync_duration_seconds_bucket[5m])) by (le, instance)
            ) > 0.5
          for: 10m
          labels: { severity: P1, team: platform }
          annotations:
            summary: "etcd {{ $labels.instance }} WAL fsync p99 超过 500ms"
            runbook: "磁盘性能不足或 IO 争抢;etcd 必须独占 NVMe。"

        - alert: EtcdBackendCommitSlow
          expr: |
            histogram_quantile(0.99,
              sum(rate(etcd_disk_backend_commit_duration_seconds_bucket[5m])) by (le, instance)
            ) > 0.25
          for: 10m
          labels: { severity: P2, team: platform }
          annotations:
            summary: "etcd {{ $labels.instance }} backend commit p99 超过 250ms"

        - alert: EtcdDBSizeApproachingQuota
          expr: |
            max(etcd_mvcc_db_total_size_in_bytes)
            / max(etcd_server_quota_backend_bytes) > 0.8
          for: 30m
          labels: { severity: P1, team: platform }
          annotations:
            summary: "etcd DB 大小超过 quota 的 80%"
            runbook: "检查 compact 是否正常、是否有大对象/海量 event;必要时手动 defrag。"

        - alert: EtcdProposalsFailing
          expr: rate(etcd_server_proposals_failed_total[10m]) > 0.1
          for: 15m
          labels: { severity: P1, team: platform }
          annotations:
            summary: "etcd 提案持续失败"
            runbook: "检查成员健康(endpoint status -w table),关注非多数派状态。"

    # ============ 证书(RKE2 特有) ============
    - name: control-plane.certificates
      rules:
        - alert: RKE2CertificateExpiringSoon
          expr: |
            (kubelet_certificate_manager_client_ttl_seconds < 7 * 24 * 3600)
            or
            (apiserver_client_certificate_expiration_seconds < 14 * 24 * 3600)
          for: 1h
          labels: { severity: P1, team: platform }
          annotations:
            summary: "RKE2 组件客户端证书即将过期"
            runbook: "RKE2 通常自动轮转;若未轮转,检查 rke2-server 或执行 rke2 certificate rotate。"

        - alert: KubeletCertRotationErrors
          expr: increase(kubelet_certificate_manager_client_expiration_renew_errors[1h]) > 0
          for: 30m
          labels: { severity: P2, team: platform }
          annotations: { summary: "kubelet 证书轮转出现错误" }

    # ============ scheduler / kubelet ============
    - name: control-plane.scheduler-kubelet
      rules:
        - alert: KubeSchedulerUnschedulableRateHigh
          expr: |
            sum(rate(scheduler_schedule_attempts_total{result="unschedulable"}[15m]))
            / sum(rate(scheduler_schedule_attempts_total[15m])) > 0.3
          for: 30m
          labels: { severity: P2, team: platform }
          annotations:
            summary: "超过 30% 的 Pod 调度失败"
            runbook: "检查节点资源水位、污点与亲和性;批量发布期间可临时容忍。"

        - alert: KubeletPLEGUnhealthy
          expr: |
            histogram_quantile(0.99,
              sum(rate(kubelet_pleg_relist_duration_seconds_bucket[10m])) by (le, instance)
            ) > 5
          for: 15m
          labels: { severity: P2, team: platform }
          annotations:
            summary: "节点 {{ $labels.instance }} kubelet PLEG relist p99 超过 5s"
            runbook: "常见于节点负载过高或 containerd 卡顿;查 CPU steal 与 IO。"

        - alert: KubeletTooManyPods
          expr: kubelet_running_pods > 100  # 按你的节点 maxPods 调整
          for: 30m
          labels: { severity: P3, team: platform }
          annotations: { summary: "节点 {{ $labels.instance }} 运行 Pod 数接近上限" }

最佳实践

  • 以上阈值是 3000 节点集群的起点,必须用 2~4 周的基线数据校准(for 时长比阈值更能降噪)。
  • 所有 P0/P1 告警必须带 runbook 注解,并在 Grafana 里建对应面板链接。
  • 告警规则与采集间隔匹配:for: 10m 的规则,采集间隔 30s 没问题;采集 60s 时 rate[5m] 只有 5 个点,毛刺多。

3.5 面试题

Q:apiserver 延迟升高的排查顺序是什么?

答:自底向上:① 看 etcd_disk_wal_fsync_duration_seconds 确认磁盘层;② 看 etcd_request_duration_seconds(apiserver 视角)确认 apiserver→etcd 链路;③ 看 apiserver_flowcontrol_* 是否 APF 排队/拒绝;④ 按 verb/resource 拆 apiserver_request_duration_seconds 找到慢请求类型(LIST 最常见);⑤ 用 apiserver_storage_list_returned_objects_total 找出大 LIST 来源,结合审计日志定位客户端(某个失控 controller 或 kubectl get 大表)。


第 4 章 日志体系

4.1 大规模日志架构选型对比

维度Loki (Grafana)EFK (Elasticsearch)ClickHouse (+ 自研/三方 UI)
索引模型只索引标签,正文不索引(类 Prometheus)全文倒排索引列式存储 + 稀疏索引
存储成本最低(约为 ES 的 1/10~1/20)最高(索引 + 副本,磁盘膨胀 1.5~3x)低(压缩率高,列式优势)
查询体验LogQL,标签过滤快,全文检索慢Kibana,全文检索最强SQL,灵活但需自建 UI(或用 Grafana 插件)
写入吞吐高(微服务可水平扩展)中等(索引成本高,bulk 慢时易积压)极高
运维复杂度中(微服务模式组件多但逻辑简单)高(JVM 调优、分片管理、ILM)中高(集群/分片/副本管理)
生态Grafana 原生集成ELK 全家桶成熟需组装(Vector/FluentBit → Kafka → CH)
适用K8s 日志默认选择,成本敏感强全文检索需求、合规审计超大规模(PB 级)、已有 CH 运维能力

数千节点 RKE2 的推荐Loki 微服务模式 为默认;有强全文检索/合规需求的审计日志走独立小集群 ES 或直接写对象存储;日志量 > 50 TB/天 且团队有 ClickHouse 能力时考虑 CH。

4.2 日志采集:containerd 日志路径与采集器

RKE2 使用 containerd,容器日志落在:

/var/log/pods/<namespace>_<pod>_<uid>/<container>/0.log
                ↓ (软链接)
/var/log/containers/<pod>_<namespace>_<container>-<id>.log
                ↓ (实际文件)
/var/lib/rancher/rke2/agent/containerd/...(containerd 管理的真实文件)

要点:

  • kubelet 负责日志轮转(containerLogMaxSize 默认 10Mi、containerLogMaxFiles 默认 5);高日志量业务建议显式调大,否则还没采完就被轮转覆盖。
  • 采集器必须以 DaemonSet 部署,挂载 /var/log/pods/var/log/containers,并用 CRI 解析器解析日志行头(2024-01-01T00:00:00Z stdout F ...)。

4.2.1 promtail DaemonSet 关键配置

yaml
# promtail values.yaml 关键片段
config:
  snippets:
    scrapeConfigs: |
      - job_name: kubernetes-pods
        kubernetes_sd_configs: [{ role: pod }]
        pipeline_stages:
          - cri: {}                    # 解析 containerd CRI 格式
          - drop:                      # 成本治理第一刀:丢弃无用日志
              source: [namespace]
              expression: "(kube-system|monitoring)"
              drop_counter_reason: "system_namespace"
        relabel_configs:
          - source_labels: [__meta_kubernetes_pod_controller_name]
            target_label: workload
          - source_labels: [__meta_kubernetes_namespace]
            target_label: namespace
          - action: labeldrop
            regex: (pod_template_hash|controller_revision_hash)   # 防高基数标签

4.2.2 Grafana Alloy 采集(新趋势)

river
local.file_match "pods" { path_targets = [{ __path__ = "/var/log/pods/*/*/*.log" }] }

loki.source.file "pods" {
  targets    = local.file_match.pods.targets
  forward_to = [loki.process.k8s.receiver]
}

loki.process "k8s" {
  stage.cri {}
  stage.drop {
    source              = "namespace"
    expression          = "(kube-system|monitoring)"
    drop_counter_reason = "system_namespace"
  }
  forward_to = [loki.write.default.receiver]
}

loki.write "default" { endpoint { url = "http://loki-gateway:3100/loki/api/v1/push" } }

注意事项:DaemonSet 采集器要设置 priorityClassName: system-node-critical 与合理 resource limits;采集器本身 OOM 重启会导致 positions 文件位点回退,产生重复日志。promtail/Alloy 的 positions.yaml 必须放持久目录(hostPath /run/promtail 等)。

4.3 审计日志采集与裁剪

数千节点集群的 apiserver 审计日志是海量的(全量记录每天可达 TB 级)。必须裁剪。

RKE2 中开启审计(server 节点 /etc/rancher/rke2/config.yaml):

yaml
kube-apiserver-arg:
  - audit-log-path=/var/lib/rancher/rke2/server/logs/audit.log
  - audit-policy-file=/etc/rancher/rke2/audit-policy.yaml
  - audit-log-maxage=7
  - audit-log-maxbackup=10
  - audit-log-maxsize=100

裁剪后的审计策略示例(只记高风险操作):

yaml
# /etc/rancher/rke2/audit-policy.yaml
apiVersion: audit.k8s.io/v1
kind: Policy
# 1) 一切默认丢弃
omitStages: ["ResponseStarted"]
rules:
  # 2) 高频读操作不记
  - level: None
    verbs: ["get", "list", "watch"]
  # 3) 心跳/lease 不记
  - level: None
    resources:
      - group: "coordination.k8s.io"
        resources: ["leases"]
  - level: None
    resources:
      - group: ""
        resources: ["nodes", "endpoints"]
  # 4) 写操作只记元数据
  - level: Metadata
    verbs: ["create", "update", "patch", "delete"]
  # 5) 敏感资源与高危操作记完整请求体
  - level: RequestResponse
    resources:
      - group: ""
        resources: ["secrets"]
      - group: "rbac.authorization.k8s.io"
  - level: RequestResponse
    verbs: ["impersonate", "exec", "portforward", "proxy"]

采集方式:审计日志是节点本地文件,在 server 节点用 promtail/Alloy 单独 job 采集,打 job=apiserver-audit 标签,路由到独立 Loki 租户(保留 180 天合规)。

4.4 Loki 大规模部署:微服务模式

  promtail/Alloy ×N


  loki-gateway (nginx 路由/租户头)


  distributor ×N ──▶ ingester ×N (ring, 副本 3)
                          │ 刷盘 chunk

                    ┌────────────┐      ┌──────────────┐
                    │ 对象存储 S3 │◀─────│ compactor     │(压实/删除过期)
                    └─────┬──────┘      └──────────────┘

  querier ×N ◀────────────┘  (查历史)

  query-frontend ×N (查询拆分/缓存) ── memcached

  ruler ×N (记录规则/告警)

  Grafana

关键配置:

yaml
# loki values.yaml(microservices 模式要点)
loki:
  commonConfig:
    replication_factor: 3
  storage:
    type: s3
    bucketNames: { chunks: loki-chunks-prod, ruler: loki-ruler-prod }
    s3:
      endpoint: s3.cn-north-1.example.com
      s3ForcePathStyle: false
  limits_config:
    retention_period: 720h              # 30 天;审计租户在 per-tenant 覆盖
    ingestion_rate_mb: 32               # 每租户写入限速(MB/s),防单租户打爆
    ingestion_burst_size_mb: 64
    max_streams_per_user: 100000
    max_query_series: 100000
    split_queries_by_interval: 30m
  compactor:
    retention_enabled: true             # 必须开启,否则 retention_period 不生效
    delete_request_store: s3
ingester:       { replicas: 6, maxUnavailable: 1, persistence: { size: 100Gi } }  # SSD
distributor:    { replicas: 4 }
querier:        { replicas: 6 }
queryFrontend:  { replicas: 3 }
queryScheduler: { replicas: 2 }         # 大查询排队,保护 querier

成本治理三板斧:

  1. 源头丢弃:pipeline_stages drop 掉健康检查、探针 access log、调试级日志。
  2. 标签瘦身:只保留 namespace / workload / node / level 等低基数标签;pod 名带随机后缀会放大 stream 数,谨慎保留。
  3. 分级保留:业务日志 7~14 天,平台日志 30 天,审计日志 180 天(独立租户),对象存储 + 生命周期兜底。
promql
# Loki 自身成本观测:各租户日写入量 Top10 / 各 namespace 日志速率
topk(10, sum by (tenant) (increase(loki_distributor_bytes_received_total[24h])))
topk(10, sum by (exported_namespace) (rate(loki_distributor_lines_received_total[5m])))

最佳实践:在 Grafana 里建一个"日志成本看板":按租户/namespace 的写入量 TopN、被 drop 的日志占比、对象存储桶增长曲线。日志账单失控一定先爆在看板上,而不是月底账单里。


第 5 章 事件与告警治理

5.1 K8s Event 采集

K8s Event 默认只保留 1 小时,且 apiserver 里 event 对象是控制平面的负担来源。数千节点集群必须外采事件

推荐 kubernetes-event-exporter(eventrouter 已停止维护,仅存量使用):

yaml
# event-exporter config.yaml 关键片段
logFormat: json
trottlePeriod: 30s                        # 相同事件 30s 去重
route:
  routes:
    - receiver: "loki"
      drop:                                # 第一层降噪:丢弃无价值 Normal 事件
        - type: "Normal"
          reason: "Scheduled|Pulling|Pulled|Created|Started"
    - match: [{ type: "Warning" }]         # Warning 全量保留,它有价值
      receiver: "loki"
receivers:
  - name: "loki"
    loki:
      url: "http://loki-gateway:3100/loki/api/v1/push"
      labels: { job: k8s-events }

同时建议把事件并行输出到对象存储做长期审计——事件是故障复盘的第一手时间线。

在告警侧,把 Warning 事件直接转为告警指标(event-exporter 自带 /metrics 暴露 event_exporter_events_total):

yaml
- alert: K8sWarningEventStorm
  expr: sum(rate(event_exporter_events_total{type="Warning"}[5m])) > 100
  for: 10m
  labels: { severity: P2 }
  annotations:
    summary: "Warning 事件速率超过 100/s,可能存在批量故障"

5.2 告警分级体系(P0-P3)

级别定义响应 SLA通道示例
P0集群不可用 / 数据丢失风险5 分钟响应,立即电话/呼叫电话 + 短信 + IM @所有人etcd 无 leader、apiserver 全部宕、大面积节点 NotReady
P1核心能力受损,有短时缓冲15 分钟响应IM 群 @值班 + 电话(夜间)apiserver 5xx > 2%、etcd fsync 慢、CNI 控制面异常
P2潜在风险,工作时间处理2 小时响应IM 群消息证书 14 天内过期、磁盘使用率 85%、调度失败率高
P3提醒/趋势类下个工作日看板/邮件/周报Pod 数接近上限、容量水位预测

分级原则:P0/P1 数量必须少到值得被叫醒。如果一个 P1 每周响 10 次,它要么是阈值错了,要么根本不是 P1。

5.3 Alertmanager 路由配置实战

yaml
# alertmanager.yml
global:
  smtp_smarthost: smtp.example.com:465
  smtp_from: alert@example.com
  resolve_timeout: 5m

route:
  receiver: default-p3
  group_by: ['cluster', 'alertname', 'namespace']
  group_wait: 30s            # 首条告警等 30s,攒一攒再发
  group_interval: 5m         # 同组新增告警合并间隔
  repeat_interval: 4h        # 未恢复告警重复提醒间隔
  routes:
    - matchers: ['severity = "P0"']
      receiver: oncall-phone
      group_wait: 0s         # P0 零等待
      repeat_interval: 30m
      continue: true         # P0 同时抄送 IM 大群
    - matchers: ['severity = "P1"']
      receiver: oncall-im
      group_wait: 15s
      repeat_interval: 1h
    - matchers: ['severity = "P2"']
      receiver: im-platform
    - matchers: ['severity = "P3"']
      receiver: email-daily
      group_by: ['alertname']
      group_wait: 1h         # P3 攒批发送
      repeat_interval: 24h

receivers:
  - name: oncall-phone      # 自建网关统一出口:对接电话/值班平台,失败降级短信
    webhook_configs:
      - url: http://alert-gateway.ops:8080/phone
        send_resolved: true
  - name: oncall-im
    webhook_configs:
      - url: http://alert-gateway.ops:8080/dingtalk?mention=oncall
  - name: im-platform
    webhook_configs:
      - url: http://alert-gateway.ops:8080/wecom?chat=platform-sre
  - name: email-daily
    email_configs:
      - to: sre-all@example.com
  - name: default-p3
    webhook_configs:
      - url: http://alert-gateway.ops:8080/dingtalk?chat=obs-low

5.4 告警降噪:inhibit、分组、静默

抑制(inhibit):根因告警抑制次生告警——数千节点集群一次故障动辄触发上千条告警,抑制是救命功能。

yaml
inhibit_rules:
  # 节点 NotReady 时,抑制该节点上所有 Pod 级告警
  - source_match:
      alertname: "KubeNodeNotReady"
    target_matchers:
      - severity =~ "P2|P3"
    equal: ['node']

  # apiserver 全挂时,抑制一切"指标采集失败/scrape 目标 down"
  - source_match:
      alertname: "KubeAPIDown"
      severity: "P0"
    target_matchers:
      - alertname =~ "TargetDown|KubeletDown|KubeStateMetricsDown"
    equal: ['cluster']

  # etcd 无 leader 时,抑制 etcd 慢盘/提案等次级告警
  - source_match:
      alertname: "EtcdNoLeader"
    target_matchers:
      - alertname =~ "Etcd.*"
      - severity =~ "P1|P2"
    equal: ['cluster']

静默(silence):变更窗口期(升级 RKE2、扩容、换证书)必须提前静默:

bash
# 创建 3 小时静默:整个集群的 P2/P3(升级窗口)
amtool silence add severity=~"P2|P3" cluster=prod-rke2-01 \
  --alertmanager.url=http://alertmanager:9093 \
  --duration=3h --comment="RKE2 v1.30 升级窗口" --author=sre-xxx
# 精确静默某条规则
amtool silence add alertname=EtcdWALFsyncSlow instance=10.0.1.11:2381 \
  --duration=1h --comment="etcd 磁盘更换"

分组(grouping)实战要点group_by 太粗 → 一条消息几百个告警没人看;太细 → 消息爆炸。经验:P0/P1 按 alertname + cluster 分组,P2/P3 再加 namespace

5.5 值班平台对接

常见通道实现方式:

通道实现方式要点
钉钉群群机器人 Webhook(加签/关键词)消息超 20KB 会被截断,模板要精简
企业微信群机器人 / 应用消息 API应用消息可 @人,机器人不行
SlackIncoming Webhook / Alertmanager 原生 slack_configs原生支持 thread 聚合
电话/短信值班平台(PagerDuty/Oncall/自建) WebhookP0 必须有电话兜底,IM 会漏
自建网关一个 HTTP 服务做格式转换 + 降级(IM 失败转短信)生产强烈推荐,统一出口便于审计

Alertmanager 经 webhook 转换器对接钉钉时,receivers 中配置 webhook_configs.url 指向转换器端点(如 http://dingtalk-webhook:8060/dingtalk/ops-group/send),并用 http_config.authorization 携带机器人加签 token。

5.6 告警治理指标:MTTA / MTTR

告警治理不是"配完路由就结束",要用指标驱动改进:

指标定义目标(参考)数据来源
MTTA告警触发 → 值班人确认的平均时间P0 < 5min,P1 < 15min值班平台 webhook 回执
MTTR告警触发 → 恢复的平均时间按级别定,复盘驱动Alertmanager resolved 时间戳
告警总量/日按 severity 统计P0+P1 < 5 条/日/集群ALERTS 指标
重复告警率repeat 触发的占比< 20%Alertmanager 日志
静默命中率静默期间被吞掉的告警数审计用,防"静默忘记删"amtool silence query
promql
count by (severity) (ALERTS{alertstate="firing"})   # 当前 firing 告警按级别统计
sum(increase(ALERTS_FOR_STATE{severity=~"P0|P1"}[1d]))  # 每日 P1+ 告警趋势(评估噪音)

最佳实践:每月一次"告警复盘会":Top10 最吵的告警要么修阈值、要么修系统、要么降级删除。一条没人会处理的告警,存在的唯一作用是让真正的告警被忽略。


第 6 章 链路追踪与 eBPF 观测

6.1 OpenTelemetry / Jaeger / Tempo 简介

项目定位特点
OpenTelemetry (OTel)采集与协议标准(Traces/Metrics/Logs 三支柱)厂商中立,SDK + Collector,已成为事实标准
Jaeger追踪存储与 UICNCF 毕业项目,UI 强,存储依赖 ES/Cassandra/Kafka
Tempo (Grafana)追踪存储只按 trace_id 索引,存对象存储,成本极低,与 Grafana/Loki 联动好

数千节点平台的典型用法:

应用 (OTel SDK / 自动注入 javaagent)


OTel Collector (DaemonSet: 接收本节点遥测 + 预聚合/采样)


OTel Collector (Deployment 网关层: 尾部采样 tail sampling)
   ├──traces──▶ Tempo ──▶ 对象存储
   ├──metrics──▶ Prometheus/VM (spanmetrics 生成 RED 指标)
   └──logs─────▶ Loki

尾部采样是大规模落地的关键——网关层统一决策"哪些 trace 保留"(如错误 trace 100% 保留,正常 trace 1% 采样):

yaml
# otel-collector 网关层配置片段
processors:
  tail_sampling:
    decision_wait: 10s
    num_traces: 5000000
    policies:
      - name: keep-errors
        type: status_code
        status_code: { status_codes: [ERROR] }
      - name: keep-slow
        type: latency
        latency: { threshold_ms: 3000 }
      - name: sample-1-percent
        type: probabilistic
        probabilistic: { sampling_percentage: 1 }

6.2 Cilium Hubble 网络可观测性实战

若 RKE2 使用 Cilium CNI(RKE2 支持 --cni=cilium),Hubble 提供零侵入的 L3-L7 流量可观测性,是大规模网络问题定位的利器。

bash
# 启用 Hubble(Helm 升级 cilium)
helm upgrade cilium cilium/cilium -n kube-system --reuse-values \
  --set hubble.enabled=true \
  --set hubble.relay.enabled=true \
  --set hubble.ui.enabled=true \
  --set hubble.metrics.enabled="{dns,drop,tcp,flow,port-distribution,httpV2}"

# 大规模集群控制 Hubble 指标基数(只出聚合指标,避免 per-flow 爆炸)
  --set hubble.metrics.enableOpenMetrics=true

常见定位命令:

bash
# 观察某 Pod 的丢包(内核丢包原因:策略拒绝/conntrack 满/网卡丢包)
hubble observe --pod prod/payment-xxx --verdict DROPPED
# DNS 失败与跨服务 TCP 重传/RST
hubble observe --namespace prod --protocol dns --last 100
hubble observe --from-pod prod/a --to-pod prod/b --protocol tcp
# 大规模下先用聚合指标 hubble_drop_total 缩小范围,再上 observe 看明细

Prometheus 侧核心指标:

promql
# 策略拒绝 Top(排查网络策略误伤)
topk(10, sum by (source_namespace, destination_namespace, reason) (rate(hubble_drop_total[5m])))
# DNS 失败率
sum(rate(hubble_dns_queries_total{rcode!="NoError"}[5m])) / sum(rate(hubble_dns_queries_total[5m]))

注意事项:Hubble 的 per-flow 数据走节点本地 ring buffer(默认容量有限,数千节点高流量下几秒就刷完),用于实时排障;需要历史流量审计时导出到外部(hubble export / 对接 ES)。开启 httpV2 指标有性能开销,先在测试集群压测。

6.3 Pixie / DeepFlow 简介

  • Pixie(CNCF,New Relic 捐出):基于 eBPF 的即时 APM,无需改代码即可看到服务拓扑、HTTP/gRPC/MySQL 请求级延迟。px deploy 一键安装,数据存在集群内。适合临时深度排障,长期全量运行内存开销需评估(每节点 1GB+)。
  • DeepFlow(国产开源,云杉网络):eBPF 全栈可观测(网络流日志 + 应用调用链 + 性能剖析),社区版可自建,对国产化/信创场景友好。

何时需要 APM

场景指标+日志够不够是否需要 APM/追踪
单体应用、调用链短不需要
微服务 < 20 个,问题主要在自己服务内基本够可选(Tempo 1% 采样)
微服务 100+,跨团队调用,"慢"经常找不到归属不够需要,且要 tail sampling 保错误 trace
性能优化/容量分析不够需要持续剖析(Pyroscope/Parca)

原则:指标发现"有问题",日志定位"哪错了",追踪回答"慢在哪一跳"。三者通过 exemplar / trace_id 互相关联(Grafana 中 metrics → traces → logs 一键跳转),才是完整闭环。


第 7 章 AI 辅助观测(亮点章节)

数千节点集群每天产生的告警、事件、日志远超人类值班员的处理带宽。AI 辅助观测不是"赶时髦",而是把重复性诊断劳动(查事件、看日志、翻 runbook)交给机器,让人专注决策。本章给出可落地的方案,也讲清楚 AI 的边界。

7.1 K8sGPT Operator 在大规模集群的部署与资源控制

K8sGPT 用 LLM 分析集群中的异常(Pending Pod、CrashLoop、事件、配置问题),以 CRD 形式输出诊断结果。

bash
helm repo add k8sgpt https://charts.k8sgpt.ai
helm install k8sgpt k8sgpt/k8sgpt-operator -n k8sgpt-system --create-namespace

# 配置后端(对接内部 LLM 网关,生产务必走自建网关做审计与配额)
kubectl apply -f - <<'EOF'
apiVersion: core.k8sgpt.ai/v1alpha1
kind: K8sGPT
metadata:
  name: k8sgpt-prod
  namespace: k8sgpt-system
spec:
  ai:
    enabled: true
    model: qwen2.5-72b-instruct       # 自建 vLLM/ollama 或商用 API
    backend: openai
    baseUrl: https://llm-gateway.internal/v1
    secret:
      name: llm-api-key
      key: token
  noCache: false
  integrations:
    trivy: { enabled: false }          # 大规模集群镜像扫描太重,先关
  filters: [Pod, Node, Event, Deployment]   # 只跑高价值分析器,控制成本
EOF

大规模集群的资源与成本控制要点

控制项建议
分析范围namespace 白名单,先覆盖核心平台命名空间,逐步放量
filters只启用 Pod/Node/Event 等 3~5 个分析器,按需追加
LLM 调用量noCache: false 开启结果缓存;同签名问题去重后再调 LLM
成本控制经内部 LLM 网关按 token 限流 + 审计;敏感集群用私有化模型
operator 资源requests 200m/256Mi 起步;分析周期默认 10m,大集群放宽到 30m
数据出集群诊断内容会发给 LLM:涉密集群必须私有化部署模型
bash
kubectl get results -A                    # 查看诊断结果
kubectl describe result <name> -n <ns>    # 查看某条诊断详情

7.2 告警 → K8sGPT 自动诊断流水线

把 AI 接到告警链路上,让每条 P1/P2 告警自带初步诊断

Prometheus 告警 firing


Alertmanager ──webhook──▶ ai-diagnose-webhook (自建小服务)
                              │ 1. 接收告警,按 fingerprint 去重/限流
                              │ 2. 采集上下文:
                              │    - kubectl describe / events(告警对象)
                              │    - 最近日志 tail 200 行(Loki API)
                              │    - K8sGPT Result(若已存在直接引用)
                              │ 3. 调 LLM:固定 prompt 模板 + 上下文
                              │ 4. 结果写入告警富化字段

                     重新推送 IM/值班平台(原始告警 + AI 诊断 + runbook 链接)

最小可用的诊断 webhook 伪代码(Go/Python 皆可):

python
# ai-diagnose-webhook 核心逻辑(Flask 示例,省略工程细节)
@app.route("/diagnose", methods=["POST"])
def diagnose():
    for a in request.json["alerts"]:
        if rate_limited(a["fingerprint"]):       # 同一告警 30 分钟内只诊断一次
            continue
        ctx = collect_context(a)                 # events + logs + describe
        diagnosis = cache.get(signature(ctx)) or llm.chat(DIAGNOSE_PROMPT.format(alert=a, ctx=ctx))
        cache.set(signature(ctx), diagnosis, ttl=3600)
        send_to_im(build_card(a, diagnosis, runbook_url(a)))
    return "ok"

诊断 prompt 模板要点:限定输出结构(可能原因 Top3 / 建议排查命令 / 是否需要升级人工),禁止自由发挥。

7.3 HolmesGPT / Robusta 告警富化

不想自建 webhook,可以用现成方案:

  • Robusta(开源):对接 Alertmanager,告警触发自动执行 playbook(抓日志、describe、截图面板),支持 HolmesGPT 根因分析。
  • HolmesGPT:Robusta 团队的 AI agent 引擎,多步"调查"模式(提假设 → 执行 kubectl/Loki 查询验证 → 收敛结论),效果优于单次 prompt。
yaml
# Robusta Helm values 片段:告警富化 + HolmesGPT
globalConfig:
  alert_labels_for_ai_investigation: ["alertname", "namespace", "pod"]
customPlaybooks:
  - triggers:
      - on_prometheus_alert: { alert_name: KubePodCrashLooping }
    actions:
      - logs_enricher: {}
      - pod_events_enricher: {}
      - ask_holmes: { investigation_type: "alert" }   # AI 调查并附结论

落地顺序建议:先用 Robusta 的确定性富化(自动 attach 日志/事件/面板截图)覆盖全部 P1 告警——这一步不需要 LLM、零幻觉、收益立竿见影;再对高频告警开启 HolmesGPT 调查。

7.4 AI 根因分析的现状与边界

现状(能做到的)

  • 单对象/单告警级初步诊断:CrashLoopBackOff、ImagePull 失败、资源不足、探针配置错误等"教科书级"问题,准确率很高。
  • 告警富化与降噪:把 10 分钟人工查上下文压缩到 30 秒。
  • 值班助手:自然语言查指标/日志(text-to-PromQL/LogQL 仍需人工复核)。

边界(做不到的)

  • 跨系统根因推理:一次"支付超时"牵扯 LB → ingress → service → 网卡丢包 → 数据库,AI 缺少全局依赖图谱时只能猜。
  • 幻觉风险:AI 会编造不存在的指标与"看起来合理"的结论。诊断必须标注置信度,且不允许直接执行变更(只读诊断、人工执行)。
  • 数据合规:日志可能含用户数据,金融/政企场景必须私有化模型。
  • 新故障模式:训练数据中不存在的故障(新型内核 bug、特定硬件问题)AI 基本无能为力。

最佳实践:把 AI 定位为"永不疲倦的初级值班员"——它先做 5 分钟的初级排查并把证据摆好,人来拍板。凡是 AI 给出的修复建议,必须经过审批流;诊断结论进复盘库,持续回灌改进 prompt。

7.5 面试题

Q:AI 告警诊断落地最大的工程挑战是什么?

答:不是模型能力,而是上下文工程与反馈闭环:① 诊断质量取决于上下文(事件、日志、拓扑)是否准确完整,前提是观测数据治理良好;② 必须有限流/去重/缓存,否则告警风暴烧穿 token 预算;③ 要有效果度量(诊断采纳率、MTTA 改善)与人工反馈通道,否则系统会在"看起来有用"的幻觉中退化。


第 8 章 监控自监控(Meta-Monitoring)

最讽刺的故障场景:集群挂了,监控也挂了,值班群静悄悄。数千节点环境下,观测系统本身就是关键基础设施,必须被独立监控。

8.1 监控系统的高可用与容量规划

高可用清单

组件HA 方式单点风险
Prometheus / Agent每分片 2 副本,长期存储层去重本地盘挂 → 最多丢 WAL 尾部,靠 remote_write 补救
Thanos Receive / VM vminsert多副本 + replication-factor 2全部不可用时 vmagent 磁盘缓冲兜底
Thanos Compactor / VM vmstorageCompactor 单点(可接受,挂了只影响压实);vmstorage 多副本vmstorage 丢节点靠 replicationFactor
Alertmanager3 副本 cluster 模式(gossip)配置错误导致全体不告警 → 需要外部拨测
Grafana多副本 + 外部数据库(Postgres/MySQL)内置 SQLite 是单点
Loki ingesterring + replication 3节点挂丢未刷盘 chunk(可接受)
对象存储云 S3 / MinIO 分布式桶误清 → 开版本控制 + 生命周期审计

容量规划速查表(3000 节点、30s 采集间隔、裁剪后 ~250 万 samples/s 参考):

组件数量单实例规格说明
Prometheus(分片)4 分片 × 2 副本8C / 32G / 200G SSD本地只留 6h
Thanos Receive38C / 32G / 500G SSD本地 12h
Store Gateway34C / 24G+ memcached 索引缓存
Compactor18C / 32G单例
对象存储raw 30d 约 8~12TB降采样后另算
Alertmanager31C / 2G
Loki(裁剪后 3TB/天 参考)ingester 6 / querier 64C / 16G 起以实际写入速率压测为准

8.2 Meta-Monitoring:谁来监控监控

原则:用最小、最独立、最不可能同时挂的系统监控观测系统

┌─────────────────────────────────────────────────────┐
│  独立的 meta-monitoring(小型,跑在观测集群之外)       │
│  - 单 Prometheus + blackbox exporter + 独立告警通道     │
│  - 部署在另一个小集群/独立 VM,告警走独立短信通道         │
└─────────────────────────────────────────────────────┘
        │ 拨测/抓取

  主监控系统各组件的 /metrics、Grafana 登录页、
  Alertmanager API、对象存储桶、LLM 网关...

核心规则(独立的 meta PrometheusRule):

yaml
groups:
  - name: meta-monitoring
    rules:
      # 1. 观测管道活性:抓不到 Prometheus 自身指标
      - alert: MonitoringPrometheusDown
        expr: absent(up{job="prometheus"}) or max(up{job="prometheus"}) == 0
        for: 3m
        labels: { severity: P0 }
        annotations: { summary: "主监控 Prometheus 全部失联" }

      # 2. remote_write 积压:采集到但写不进长期存储
      - alert: RemoteWriteBacklog
        expr: max(prometheus_remote_storage_samples_pending) > 100000
        for: 15m
        labels: { severity: P1 }

      # 3. Watchdog 心跳告警:永远 firing,验证告警端到端链路活性
      - alert: Watchdog
        expr: vector(1)
        labels: { severity: P3, watchdog: "true" }
        annotations: { summary: "心跳告警:持续 firing,用于验证告警链路活性" }

      # 4. Alertmanager 集群不健康
      - alert: AlertmanagerClusterDegraded
        expr: min(alertmanager_cluster_members) < 2
        for: 10m
        labels: { severity: P1 }

      # 5. 长期存储写入停止(Thanos receive 无新块)
      - alert: ThanosReceiveIngestStalled
        expr: sum(rate(thanos_receive_tsdb_head_appends_total[15m])) == 0
        for: 20m
        labels: { severity: P1 }

      # 6. 规则评估失败 / 采集目标大量失联(>20%,多为 SD 或网络问题)
      - alert: PrometheusRuleEvaluationFailures
        expr: increase(prometheus_rule_evaluation_failures_total[15m]) > 0
        for: 15m
        labels: { severity: P2 }
      - alert: ScrapeTargetsMassDown
        expr: sum(up == 0) / sum(up) > 0.2
        for: 10m
        labels: { severity: P1 }

Watchdog 心跳告警的用法:它永远 firing,Alertmanager 定期发 repeat 消息到值班平台;值班平台侧配置"超过 X 分钟没收到 Watchdog 就打电话"——这覆盖了"Alertmanager 静默挂掉/通道失效"这一最难发现的故障模式(死信检测)。

最佳实践:meta-monitoring 的告警通道必须与主系统零共享依赖;每季度做一次"监控灾难演练"(手动停掉主 Prometheus/Alertmanager,验证 meta 告警 5 分钟内到达值班人);观测系统各组件的容量指标纳入主监控并按周 review 增长曲线。


第 9 章 常见面试题(10 题带答案要点)

1. 数千节点集群 Prometheus 方案怎么选?

要点:先裁剪基数再谈架构,采集与存储分离。3000 节点:分片或 Agent 模式采集 + Thanos Receive / VictoriaMetrics 集群版做长期存储与全局查询;本地保留 2~6 小时,对象存储 30 天 raw + 降采样 2 年。选型:生态/资料选 Thanos,资源/多租户/简单选 VM。

2. 什么是指标基数爆炸?怎么治理?

要点:同一指标的标签组合数失控(pod 名、user_id、带 ID 的 path 进 label)。治理三板斧:① 采集端 metric_relabel_configs drop;② 埋点规范禁止高基数值做 label;③ 定期 count by (__name__) 审计 TopN,配合 Loki/VM 的 stream 限制兜底。

3. apiserver 变慢怎么排查?

要点:自底向上——etcd wal fsync(磁盘)→ apiserver 侧 etcd_request_duration → APF 排队/拒绝 → 按 verb/resource 拆延迟找慢类型 → storage_list_returned_objects 找大 LIST → 审计日志定位客户端。常见根因:磁盘 IO、失控控制器全量 LIST、APF 配额被业务挤占。

4. etcd 最关键的三个监控指标是什么?

要点:① etcd_disk_wal_fsync_duration_seconds(磁盘最敏感,p99 > 500ms 危险);② etcd_server_has_leader + leader 变更次数;③ DB 大小 / quota(配合 compact/defrag)。加分项:proposal 失败与 pending、peer RTT。

5. Thanos Sidecar 和 Receive 模式区别?何时用哪个?

要点:Sidecar 伴随 Prometheus 上传块、实时查询直查 Prometheus,适合存量改造;Receive 走 remote_write,Prometheus 可退化为无状态 Agent,实时性好、采集层弹性强,适合新建大规模集群;代价是写入流量集中与 hashring 运维复杂度。

6. 怎么做告警降噪?

要点:四层——① 源头:合理阈值 + for 时长,修系统而非调告警;② 分组:group_by + group_wait/interval 攒批;③ 抑制:inhibit_rules 用根因告警抑制次生告警;④ 静默:变更窗口 silence + 到期审计。最后用 MTTA/告警量/重复率做治理度量。

7. Loki 为什么比 ES 便宜?代价是什么?

要点:只索引标签不索引正文(倒排索引是 ES 磁盘膨胀主因),chunk 压缩存对象存储,成本约为 ES 的 1/10~1/20。代价:无标签过滤的全文检索慢、标签基数失控摧毁性能、复杂搜索弱。适合按 namespace/工作负载维度查 K8s 日志。

8. 监控 RKE2 和监控 kubeadm 集群有什么差异?

要点:① 控制平面是 static pod,证书在 /var/lib/rancher/rke2/server/tls/;② etcd 指标走 2381 明文端口;③ rke2-server/agent supervisor 进程本身要监控;④ 默认 CNI 是 Canal,网络观测方案不同;⑤ 升级时端口/证书可能变化,需核对 ServiceMonitor。

9. 如何防止"集群挂了但没人收到告警"?

要点:meta-monitoring——① 独立最小监控系统 + 独立告警通道,与主系统零共享依赖;② Watchdog 永久 firing 心跳告警 + 值班平台死信检测;③ 定期灾难演练(主动停 Alertmanager 验证链路);④ 通道多路冗余(IM + 电话 + 短信)。

10. AI 辅助告警诊断怎么落地?有哪些坑?

要点:先做确定性告警富化(Robusta 自动 attach 日志/事件/面板),再接 LLM 诊断(K8sGPT/HolmesGPT 或自建 webhook),只出建议、变更必须人工审批。坑:① 上下文工程决定质量;② 成本(限流/去重/缓存,告警风暴烧穿 token 预算);③ 幻觉(标置信度、结论可回溯);④ 合规(敏感环境私有化模型)。


本部分总结:大规模可观测性的本质是在成本、完整性、可靠性之间做工程权衡。记住四条主线:先砍基数再谈架构;采集与存储分离、长期数据进对象存储;告警治理用 P0-P3 分级 + 抑制 + MTTA/MTTR 度量;监控必须被独立监控。AI 是加速器,但它放大的是你观测数据治理的水平——数据治理得好,AI 事半功倍;数据一团糟,AI 只会一本正经地胡说八道。