第 17 章 可观测性与排障心智:在迷宫里定位一根坏线

墨:老哥,前面 16 章把系统拆明白了。这章解决”拆明白之后,真出事了你怎么找”。分布式系统最怕的不是”错了”,是”错了但不知道错在哪”——一个问题散在几十个组件里。我先扔个暴论:K8s 排障不是背命令,是有一张”因果地图”在脑子里。 命令只是地图的索引。


17.1 可观测性三支柱:metrics / logs / traces

墨:你说”系统可观测”,到底要观测啥? 业界共识三支柱:

  1. Metrics(指标):数值时间序列。如”Pod CPU 使用率""API Server 请求延迟""节点内存可用”。回答”系统整体/局部健康吗、趋势如何”。
  2. Logs(日志):离散文本事件。如”容器打印的 error 行""kubelet 的驱逐记录”。回答”那一刻发生了什么具体事”。
  3. Traces(链路追踪):一次请求跨多个服务的调用路径与耗时。回答”一个慢请求卡在哪一段”。

墨:三者关系像啥? 像医院:Metrics 是体温/血压监护仪(持续数值,看趋势)、Logs 是病历记录(具体事件)、Traces 是造影/CT(看一次病程经过哪。三者互补,缺一个就盲。

17.1.1 “可观测性”≠“监控”

重要区分: 监控(monitoring)是”预先设好警报(如 CPU>90% 告警)“,可观测性(observability)是”系统暴露足够信号,让你能临时提出任何问题并回答”(如”昨晚 3 点那波 5xx,是不是因为 node-7 上某 Pod OOM 引发的连锁?“——这种事先没设警报的问题,靠充分的可观测数据事后能答)。

墨:为什么分布式系统特别需要可观测性而非监控? 因为故障组合爆炸——你事先猜不到所有故障模式,只能靠”出事后有足够数据去查”。监控管”已知的已知”,可观测性管”未知的未知”。K8s 这种复杂系统,后者才是救命的。


17.2 Metrics:Prometheus 的”拉”模型

K8s 生态监控事实标准是 Prometheus。它用拉(pull)模型:Prometheus 周期性去各目标的 /metrics 端点抓取指标(不是目标主动推)。

墨:为什么是拉不是推? 拉的好处:(1) 目标无状态:被监控的 Pod 只需暴露 /metrics,不用知道 Prometheus 在哪、不用配推送地址;(2) Prometheus 掌控节奏:它决定抓多频繁,且能发现目标是否挂了(拉不到=目标失联,天然健康探测);(3) 配置集中:所有抓取配置在 Prometheus 侧,目标加一个就注册一个(靠 ServiceMonitor,第 14.3 的 Prometheus Operator)。

K8s 指标来源三层:

  • cAdvisor(kubelet 内置):每个容器的 CPU/内存/网络指标,Prometheus 从 kubelet 的 /metrics/cadvisor 抓;
  • kube-state-metrics:把 K8s 对象状态(Pod 数、Deployment 期望/实际副本差、PVC 状态)转成指标——告诉你”期望 vs 实际”的差距(和第 3 章呼应,这是声明式系统的天然可观测点);
  • 控制面组件指标:API Server/etcd/调度器各自暴露 /metrics,监控控制面健康(如 etcd 写延迟、API Server 请求率)。

黄金信号(RED/USE): 排障常看四类——延迟(Latency)、流量(Traffic/Rate)、错误(Errors)、饱和度(Saturation)。如 Service 慢了,先看延迟升没升、错误率升没升、节点饱和度(CPU/内存)满没满。这是从”现象”快速定位”哪类资源”的框架。


17.3 Logs:容器输出去哪了

墨:你 kubectl logs pod-x 看到的日志,从哪来的? 容器进程写 stdout/stderr,被容器运行时重定向到节点上的日志文件(如 /var/log/pods/...),kubelet 管理。kubectl logs 是 API Server 经 kubelet 把节点上的日志文件读出来返给你。

关键限制(必记):Pod 删了,节点上的日志文件也删了。 所以 kubectl logs 只能看还在的 Pod的日志。Pod 被 OOM/驱逐重建,旧 Pod 的日志就没了(除非你早收集走了)。

所以生产必须集中收集日志:用 Fluentd/Filebeat(DaemonSet,每节点采节点日志)→ 发到 Elasticsearch/Loki → 存着。即使 Pod 没了,日志已在中心仓库。这呼应第 8 章 sidecar/DaemonSet——日志采集 agent 用 DaemonSet 每节点跑一个。Loki(Grafana)更轻量,和 Prometheus 同生态,近年流行。

墨:日志和 metrics 怎么配合? 指标告诉你”量异常”(如错误率 5%),日志告诉你”为什么”(如查那时段的 error 日志看具体报错)。先指标定位异常时段/范围,再日志深挖原因。这是标准排障流。


17.4 Traces:跨服务的”造影”

墨:一个请求经过网关→订单→库存→数据库,哪里慢?只看各服务指标看不出”这次请求卡在库存还是数据库”。 Traces 解决:给请求发一个 traceID,每跳服务都记录”进入/离开时间 + 调用了谁”,拼成一条调用链。你能看到”这次请求 800ms,其中库存服务调数据库占了 700ms”。

OpenTelemetry(OTel) 是跨厂商的追踪/指标/日志标准,K8s 生态普遍接。Istio 等服务网格还能自动注入追踪(因为流量过 sidecar,天然能采 span),不用你改代码。

演化看点: 早期追踪要改代码埋点,OTel + 服务网格把”埋点”做成基础设施能力(sidecar 自动采)。又是”把横切关注点(追踪/安全/可观测)下沉到基础设施(sidecar/网格)“的思路(和 CRI/CNI/CSI 同构)。


17.5 Events:K8s 排障的第一入口

墨:你 kubectl describe pod-x 看到底部 “Events:” 那几行,是什么? 那是 K8s 的 Events——组件(调度器、kubelet、控制器)把”重要发生的事”记成 Event 对象(存在 etcd,有 TTL 会自动清)。比如:

Events:
  Type     Reason     Age   Message
  ----     ------     ----  -------
  Warning  FailedScheduling  2m  0/10 nodes are available: 3 Insufficient memory...
  Normal   Pulling   1m    Pulling image "myapp:v2"
  Warning  BackOff   30s   Back-off restarting failed container

墨:为什么 Events 是”第一入口”? 因为它直接告诉你组件决策的原因——Pod 为啥没调度(FailedScheduling + 原因清单)、为啥重启(BackOff)、镜像为啥拉不来(Failed 原因)。排障八成从 describe 的 Events 开始,它把”控制循环的决策日志”摊给你看(呼应第 6.10 调度失败事件)。记着:Events 是 K8s 组件在”自述”它做了什么、为什么,是系统给你的排障线索,不是玄学。

注意: Events 只保留近期(默认 1 小时左右的 TTL),久了被 GC。真要追溯历史事件,得接事件聚合(如 event-exporter 发到 ES/Loki)。又是”etcd 不存长历史”的纪律(第 5.5)。


17.6 排障心智模型:一张因果地图

背命令没用,要有一张”从现象倒推因果”的地图。我把常见故障按”自底向上”的因果链列出,你排障时沿链走:

用户侧现象(5xx / 慢 / 连不上)
   │
   ├─ 应用层:Pod 内进程崩了?看 Pod 状态 + 容器日志
   │     ├─ CrashLoopBackOff:应用启动即退(配置错/依赖没起/端口冲突)→ 看日志
   │     ├─ OOMKilled:内存超 limits → 调 limits/查泄漏(第 15.6)
   │     └─ Error / 业务错:看应用日志 + trace
   │
   ├─ 负载层:Pod 没起来?看调度/控制器
   │     ├─ Pending:调度不上(资源/污点/亲和)→ 看 Events FailedScheduling(第 6.10)
   │     ├─ ImagePullBackOff:镜像拉不来(拼错/私有库无权限/网络)→ 看 Events
   │     └─ 副本数不对:控制器期望≠实际 → kube-state-metrics 告警(第 17.2)
   │
   ├─ 网络层:Pod 起来了但通不了?
   │     ├─ Service 不通:Endpoint/Slice 有没有后端?kube-proxy 规则?DNS?(第 9/11)
   │     ├─ 跨 Pod 不通:NetworkPolicy 拦了?(第 9.7/13.6)
   │     └─ 对外不通:Ingress/LB 配置?(第 9.6)
   │
   ├─ 存储层:挂载失败?PVC Pending / 拓扑不对 / 访问模式错(第 10)
   │
   ├─ 节点层:节点 NotReady?kubelet 挂/资源压力/磁盘满(第 16.5/15.7)
   │
   └─ 控制面层:API Server/etcd 异常?写失败/延迟高(第 4/5)

墨:你看出这张图的方法论了吗? 它是**“分层收窄”:先从最外层现象(用户看到 5xx)出发,逐层问”是这层的锅吗”,用各层的”第一信号”(Pod 状态、Events、metrics、规则)快速排除,直到定位到某一层。不是乱试命令,是沿因果链二分排除**。这跟医生”先分系统(呼吸/循环/消化)再定位器官”一个逻辑。


17.7 三个典型故障的实战推演

17.7.1 Pod Pending(调度不上)

kubectl get pod 看到 Pending → describe 看 Events:

  • 0/10 nodes are available: 3 node(s) had untolerated taint, 5 Insufficient memory, 2 node(s) didn't match Pod anti-affinity → 逐条解读:3 台有污点你没容忍、5 台内存不够(requests 超)、2 台反亲和冲突。
  • 对策:加节点/降 requests/加 toleration/改亲和。具体看哪条(第 6.10)。

17.7.2 CrashLoopBackOff(启动即崩)

describe 看 Events Back-off restarting failed container → kubectl logs pod-x --previous 看上一次崩溃的日志(注意 --previous,因为当前容器已重启、要看崩溃那次的日志)→ 常见原因:配置解析错、依赖(DB)没连上、端口被占、探针太严(第 8.6 的 liveness 风暴)。

墨:为什么用 --previous? 因为容器重启后,当前实例的日志是新的(可能还没崩),崩溃原因在”上一个已退出的容器”日志里。--previous 读上一个容器的日志——排障必会。

17.7.3 服务偶发 5xx(最难,散布型)

不是单一 Pod 崩,是”偶尔慢/错”。路径:

  • metrics:看该 Service 错误率/延迟曲线,定位时段;
  • trace:挑一个慢请求看链路,定位卡在哪个服务/依赖;
  • logs:查那个服务的日志,看具体报错(如 DB 连接池耗尽、下游超时);
  • 若怀疑节点:看是不是”总在某个节点上的 Pod”出错 → 节点资源压力/磁盘满(第 15.7/16.5);
  • 若怀疑网络:看是不是特定调用方到特定后端的连接被 NetworkPolicy 拦/Service 端点缺失。

墨:这类”散布型”故障,最该先建什么可观测能力? 先有 RED 指标 + 链路追踪 + 集中日志三件套,否则你只能在几十个 Pod 里瞎 kubectl logs 碰运气。可观测性投资要在故障前做,故障时补就晚了。


17.8 排障的”反模式”(老哥你别踩)

  • 只 kubectl logs 不 describe:漏掉 Events 这个第一入口,绕远路;
  • 重启大法:kubectl delete pod 让它重建,看似好了一会儿,但根因(如节点压力、配置错)还在,必复发;
  • 改线上不存 Git:手工 kubectl edit 改了,忘了记,下次重建又回到错的状态(第 12.9 的 immutable 哲学);
  • 不设 resources:出 OOM 都不知道谁吃的内存(第 15 章总清算);
  • 不看 metrics 只看日志:日志是”点”,metrics 是”面”,没面你不知道异常范围多大。

墨:这些反模式的共同点是什么? 都是”只处理现象、不追因果”。K8s 是声明式系统,根因几乎都在”期望 vs 实际的 gap”或”某层配置错”——你沿 17.6 的地图追因果,才能真修,而不是 restart 掩盖。


17.9 本章演化线小结

  • 可观测三支柱:Metrics(趋势)/Logs(事件)/Traces(链路),互补;可观测≠监控(管未知未知);
  • Prometheus 拉模型(目标无状态、掌控节奏、发现失联);指标源 cAdvisor/kube-state-metrics/控制面;
  • 日志经 kubelet 落节点,Pod 删则失,必集中收集(DaemonSet agent → ES/Loki);
  • Traces 用 traceID 拼调用链,OTel + sidecar 自动埋点;
  • Events 是排障第一入口(组件自述决策原因),但 etcd 只留近期;
  • 排障心智:分层因果地图,自底向上二分排除,不是背命令;
  • 典型:Pending/ImagePullBackOff/CrashLoopBackOff/OOMKilled 的入口信号;
  • 反模式:只 logs 不 describe、重启大法、手工改不存 Git、不设 resources。

17.10 本章思考题

🔧 思考题 1

用户报”订单服务偶发 5xx,一天几次,每次几秒”。你作为 SRE,按顺序会建哪些可观测信号、用 17.6 地图怎么二分定位?给出从”看到现象”到”定位根因”的推测路径(不要求真答案,要求方法对)。

【参考答案】 方法(沿分层地图二分):(1) 先面后点:看订单服务的 RED 指标(错误率曲线),确认 5xx 的时段、频率、是否集中在某些 Pod/节点。(2) 链路定位:挑该时段一个 5xx 请求的 trace,看它卡在调用链的哪一段(订单→库存?订单→DB?还是订单自身)。(3) 按 trace 落点分层排查:若卡在 DB 调用→查 DB 连接池/慢查询指标+DB 日志;若卡在下游服务→对该下游重复(1)(2);若订单自身慢→看订单 Pod 的 CPU/内存饱和度(是否 throttle/OOM 前兆)、节点压力。(4) 日志深挖:定位到具体服务/时段,查其集中日志的 error 模式(如”context deadline exceeded”=下游超时、“connection refused”=依赖没起)。(5) 若只在特定节点出错:查该节点 kubelet 状态、资源压力、磁盘(第 16.5/15.7)。推测路径示例:指标显示 5xx 集中在 node-7 的 Pod → 查 node-7 发现磁盘 inode 满触发驱逐→该 Pod 被赶走重建间隙请求落空→根因是节点日志未轮转。方法核心:“指标定范围 → trace 定链段 → 日志定原因 → 节点/网络/存储分层验证”,绝不 kubectl logs 瞎碰。

🔧 思考题 2

kubectl logs pod-x 返回空,但你知道应用应该打印日志。列出至少 3 种可能原因,并说明每种该怎么验证。这题考你对”日志从容器到你的屏幕”这条链路的理解。

【参考答案】 链路:应用 stdout → 容器运行时重定向节点文件 → kubelet → API Server → 你。空的可能:(1) 应用真的没输出:应用可能把日志写文件而非 stdout(常见 Java 配了 file appender),验证:kubectl exec 进容器看是否有日志文件、应用配置;(2) 你看的是新容器,日志在旧容器:Pod 重启过,当前容器刚起还没日志,验证:kubectl logs pod-x --previous 看上一次;(3) kubelet 日志轮转/采集问题:节点上日志文件被轮转或 kubelet 出故障,验证:登节点看 /var/log/pods/... 是否存在、kubelet 健康;(4) Pod 是多容器,你看错容器:验证:kubectl logs pod-x -c <容器名> 指定容器(默认只打第一个);(5) 日志被 DaemonSet agent 收走但中心库没查对:验证:去 Loki/ES 按 Pod 名查。这题考你理解”logs 不是魔法,是一条有节点的管道”,空要从”应用是否写 stdout、容器是否对、kubelet 是否在、文件是否在”逐段查,而非只怪命令。

🔧 思考题 3

为什么 K8s 的 Events 只保留近期(etcd 不存长历史),而 metrics 可以查几个月?从”etcd 边界”(第 5.5)和”可观测系统分工”角度解释,并给出”我要查上周的某个调度失败事件”的正确做法。

【参考答案】 原因:(1) etd 边界:Events 是 etcd 里的对象,etcd 只存元数据且总量/单对象有上限、MVCC 历史会 compact(第 5.5/5.6),不适合存”海量、短命、只近期有用”的事件流——若 Events 永久留 etcd,会撑爆且拖慢控制面。(2) 分工:Events 是”排障即时线索”(近期才相关),metrics 是”趋势分析”(需长时段),两者该去不同存储。metrics 存 Prometheus/TSDB(为时序优化、可保留数月),Events 存 etcd 短期(近期排障)。正确做法查上周事件:事件要长期留,得在产生时导出——用 event-exporter / fluentbit 把 Events 转发到 ES/Loki(中心日志/检索系统),在那查上周的调度失败事件。即:Events 的”长期留存”不能靠 etcd,要靠”边产生边导出到合适的存储”。这题强化”etcd 不是万能仓库、不同信号去不同存储”的纪律(呼应第 5.3 业务数据不存 etcd、第 10.6 秘密外置)。

🔧 思考题 4(进阶)

你公司要建”K8s 可观测体系”,预算有限只能先做一件。从”排障 ROI”角度,你优先建 metrics、logs、还是 traces?给出你的排序和理由,并说明”只建一件时另外两件的缺口怎么临时补”。

【参考答案】 排序与理由:(1) 第一优先 metrics(含 kube-state-metrics + 节点/Pod 资源 + 控制面指标):ROI 最高,因为它覆盖”最广的异常发现面”——绝大多数故障(Pod 没起、资源压、副本不对、控制面慢)都有指标信号,且 Prometheus 生态成熟、接入快。没有 metrics 你连”哪里异常”都不知道,排障全盲。(2) 第二 logs(集中收集):定位原因必需,但”先有 metrics 知道异常范围,再 logs 深挖”更高效;只建 logs 没 metrics,你不知该查哪个服务的日志。(3) 第三 traces:价值高但接入成本最高(需 OTel 埋点/网格),且多数故障靠 metrics+logs 已能定位,trace 是”精修调用链”的进阶。只建一件时的缺口补法:若只有 metrics——临时靠 kubectl logs/describe + Events 补”原因”(人肉、慢但能应急);若只有 logs——靠 kubectl top(临时 metrics)+Events 补”面”;若只有 traces——几乎没法定位”哪个服务先出问题”(trace 假设你知道范围),最不推荐只建它。结论:预算有限先 metrics(面),再 logs(因),traces 最后(精)。这排序本质是”先广覆盖发现,再深挖原因,最后精细链路”,符合排障的”面→点→链”顺序。


17.11 小结

  • 可观测三支柱 Metrics/Logs/Traces 互补;可观测≠监控,管”未知未知”;
  • Prometheus 拉模型,指标源 cAdvisor/kube-state-metrics/控制面;黄金信号 RED/USE;
  • 日志经 kubelet 落节点,Pod 删则失,必集中收集(DaemonSet→ES/Loki);--previous 看崩溃日志;
  • Traces 用 traceID 拼链,OTel+sidecar 自动埋点;
  • Events 是排障第一入口(组件自述决策),etcd 只留近期,长期需导出;
  • 排障心智:分层因果地图,自底向上二分排除,不背命令;
  • 反模式:只 logs 不 describe、重启大法、手工改不存 Git、不设 resources——都只处理现象不追因果。

下一章(倒数第二章)我们做一次全局演化阅兵——把全书 18 章里出现的所有”老问题被新方法解决”的案例汇总成一张表,提炼 K8s 的演化方法论。看完这章,你就不只是”会用 K8s”,而是”懂一个复杂系统怎么长成现在这样”。