主题
第四部分 大规模可观测性体系
当集群规模从几十台节点增长到数千台节点时,可观测性系统本身就会成为一个"大规模分布式系统"。在数千节点的 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 万+ series | 4x+ |
| 单次采集总 series | ~100 万 | 3000 万~1 亿+ | 30~100x |
| 15s 采集下写入速率 | ~7 万 samples/s | 200 万~700 万 samples/s | 30~100x |
| 30 天本地保留磁盘(压缩后) | ~200 GB | 6~20 TB | 30~100x |
1.1.1 指标基数爆炸(Cardinality Explosion)
基数爆炸是数千节点集群监控的头号杀手。它不是"指标多",而是"同一个指标的标签组合数失控"。
典型爆炸源:
container_*系列指标:Pod × 容器 × 节点 = 海量 series,Job/CronJob 场景下pod标签每次运行都产生新值。- 应用自定义指标埋点失控:把
user_id、request_id、含 ID 的url_path放进 label。 - apiserver 指标:
apiserver_request_duration_seconds_bucket{verb, group, version, resource, ..., le},数千节点 + 大量控制器场景下轻松突破百万 series。 - ingress-nginx:
nginx_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: drop1.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 exporter | 30~90 天 | IDC/系统组 |
| K8s 层 | kube-state-metrics、控制平面抓取、RKE2 特有探针 | 30~180 天(长期存储 1 年降采样) | 平台组(SRE) |
| 应用层 | Prometheus SDK、OpenTelemetry、ServiceMonitor | 15~30 天 | 应用团队 |
| 业务层 | 自定义 SLI 记录规则、Grafana SLO | 1 年+(仅记录规则结果) | 业务方 |
分层的关键收益:量大的层(K8s/基础设施)激进丢弃与降采样;量小但价值高的层(业务 SLI)长期保留。
1.3 RKE2 特有监控点
RKE2 与 kubeadm 集群的监控差异点,很多"经验帖"不会告诉你:
控制平面组件的部署形态: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/
- etcd 客户端证书:
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 -50etcd 暴露指标端口:RKE2 默认 etcd 的 metrics 通过
https://<node-ip>:2381/metrics暴露(2381 为 HTTP 明文指标端口,2379 为客户端端口)。kube-prometheus-stack 的 etcd 抓取 job 需要指向 2381,否则需要配证书。CNI 差异:RKE2 默认 Canal(Calico + Flannel),网络观测用 Calico felix 指标;换 Cilium 则可上 Hubble(见第 6 章)。
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 节点参考) | 说明 |
|---|---|---|
scrapeInterval | 30s(节点/控制平面)~ 60s(业务) | 间隔翻倍,成本减半 |
--query.max-concurrency / --query.timeout | 20~40 / 2m | 防大查询打爆 |
KSM --metric-denylist | 丢弃 kube_pod_container_status_.*terminated.* 等 | KSM 本身也是基数大户 |
| KSM 分片 | --shard=0 --num-shards=3 | KSM 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=48hyaml
# 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=2myaml
# 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: false2.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=10000000yaml
# vminsert
args:
- --storageNode=vmstorage-0:8400,vmstorage-1:8400,vmstorage-2:8400
- --replicationFactor=2 # 同一条数据写 2 个 storage
- --maxLabelsPerTimeseries=40 # 防标签失控
- --maxLabelValueLen=4096yaml
# 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=502.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 |
| 查询语言 | PromQL | PromQL + MetricsQL(增强) |
| 采集生态 | 复用 Prometheus 生态 | vmagent 兼容 ServiceMonitor,也有原生 operator |
| 多租户 | 靠 external label 软隔离 | 原生多租户(tenant id),适合多团队共用 |
| 高可用写 | replication-factor 2 | replicationFactor 2 + vmagent 磁盘缓冲(更强) |
| 运维心智 | 组件多,故障域多 | 组件少,黑盒程度高 |
| 开源协议 | Apache 2.0 | Apache 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_seconds | Histogram | 请求延迟,按 verb/resource 拆分 | p99 LIST < 30s,GET p99 < 1s |
apiserver_request_total | Counter | QPS 与错误码分布(code 标签) | 5xx 比例 < 0.1% |
apiserver_longrunning_requests | Gauge | watch 等长连接请求数 | 持续增长警惕 watch 泄漏 |
apiserver_current_inflight_requests | Gauge | 正在处理的请求数(mutating/readOnly) | 接近上限即排队 |
apiserver_flowcontrol_request_concurrency_limit | Gauge | APF 当前并发上限 | 观察是否被动态调低 |
apiserver_flowcontrol_rejected_requests_total | Counter | APF 拒绝(429)数 | >0 即需分析哪个 PL 被打爆 |
apiserver_request_terminations_total | Counter | 请求超时/异常终止 | 突增需排查 |
apiserver_storage_objects | Gauge | 每类资源对象数 | pod/event 数监控 |
apiserver_storage_list_total / ..._returned_objects_total | Counter | LIST 调用与返回对象数 | 单次 LIST 返回百万对象 = 危险 |
etcd_request_duration_seconds(apiserver 侧) | Histogram | apiserver→etcd 请求延迟 | p99 < 500ms |
apiserver_watch_events_total / ..._sizes | Counter/Histogram | watch 事件推送量 | 评估 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(如 system、workload-high、workload-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 | head3.2 etcd 关键指标
| 指标 | 含义 | 告警线(经验) |
|---|---|---|
etcd_disk_wal_fsync_duration_seconds | WAL 落盘延迟(最敏感) | p99 > 500ms 危险,> 1s 严重 |
etcd_disk_backend_commit_duration_seconds | 后端提交延迟 | p99 > 250ms 关注 |
etcd_mvcc_db_total_size_in_bytes | DB 实际大小 | 接近 quota(默认 2G/建议 8G)告警 |
etcd_mvcc_db_total_size_in_use_in_bytes | DB 使用大小 | 与总量差距大 → 需要 compact/defrag |
etcd_server_has_leader | 是否有 leader | =0 立即 P0 |
etcd_server_leader_changes_seen_total | leader 变更次数 | 1 小时内 >1 次需排查 |
etcd_server_proposals_failed_total / ..._pending | 提案失败 / 排队提案 | 持续增长 / >10 危险 |
etcd_network_peer_round_trip_time_seconds | 节点间 RTT | p99 > 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 SSD(
fio验证 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_errors3.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成本治理三板斧:
- 源头丢弃:pipeline_stages drop 掉健康检查、探针 access log、调试级日志。
- 标签瘦身:只保留
namespace / workload / node / level等低基数标签;pod名带随机后缀会放大 stream 数,谨慎保留。 - 分级保留:业务日志 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-low5.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 | 应用消息可 @人,机器人不行 |
| Slack | Incoming Webhook / Alertmanager 原生 slack_configs | 原生支持 thread 聚合 |
| 电话/短信 | 值班平台(PagerDuty/Oncall/自建) Webhook | P0 必须有电话兜底,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 | 追踪存储与 UI | CNCF 毕业项目,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 vmstorage | Compactor 单点(可接受,挂了只影响压实);vmstorage 多副本 | vmstorage 丢节点靠 replicationFactor |
| Alertmanager | 3 副本 cluster 模式(gossip) | 配置错误导致全体不告警 → 需要外部拨测 |
| Grafana | 多副本 + 外部数据库(Postgres/MySQL) | 内置 SQLite 是单点 |
| Loki ingester | ring + replication 3 | 节点挂丢未刷盘 chunk(可接受) |
| 对象存储 | 云 S3 / MinIO 分布式 | 桶误清 → 开版本控制 + 生命周期审计 |
容量规划速查表(3000 节点、30s 采集间隔、裁剪后 ~250 万 samples/s 参考):
| 组件 | 数量 | 单实例规格 | 说明 |
|---|---|---|---|
| Prometheus(分片) | 4 分片 × 2 副本 | 8C / 32G / 200G SSD | 本地只留 6h |
| Thanos Receive | 3 | 8C / 32G / 500G SSD | 本地 12h |
| Store Gateway | 3 | 4C / 24G | + memcached 索引缓存 |
| Compactor | 1 | 8C / 32G | 单例 |
| 对象存储 | — | raw 30d 约 8~12TB | 降采样后另算 |
| Alertmanager | 3 | 1C / 2G | |
| Loki(裁剪后 3TB/天 参考) | ingester 6 / querier 6 | 4C / 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 只会一本正经地胡说八道。