Skip to content

Kubernetes 可观测性与生产故障排查

Kubernetes 中 Pod 会扩缩容、滚动更新、漂移和重建,容器文件系统也可能随实例删除而消失。生产排查不能依赖“登录固定服务器看日志”,而要把用户请求、应用版本、Workload、Pod、Container、Node、Service、Ingress 和控制面证据串成时间线。

可观测性不是“装了 Prometheus 和 ELK”,而是系统能否通过外部输出回答:发生了什么、从什么时候开始、影响谁、问题在哪一层、为什么发生、修复后是否真正恢复。

学习目标

学完后应能:

  1. 区分日志、指标、链路、事件、状态和 Profiling 的证据价值。
  2. 解释 Metrics Server、Prometheus、kube-state-metrics、kubelet/cAdvisor 的数据来源和边界。
  3. 解释容器 stdout 日志如何落到节点、为什么 Pod 删除后可能丢失。
  4. 使用 getdescribelogseventstopexecdebug 分层取证。
  5. 区分当前容器日志、上一次重启实例日志、Init Container 和多容器日志。
  6. 根据 Phase、Condition、ContainerStatus、Reason、ExitCode 和 Event 判断故障层。
  7. 从 Deployment 一直追踪到 ReplicaSet、Pod、Node、EndpointSlice 和 Ingress。
  8. 分析 Pending、ImagePullBackOff、CrashLoopBackOff、OOMKilled、CPU Throttling、NotReady 和 503。
  9. 用 RED、USE、四个黄金信号和业务指标建立 SLI/SLO 告警。
  10. 形成“现象—时间—范围—变更—证据—根因—修复—验证”的事故结论。

一、可观测性和监控有什么区别

监控通常针对已知问题设置固定指标和阈值,例如“5分钟错误率大于1%告警”。可观测性要求系统暴露足够上下文,使工程师能探索未知问题,例如“只有新版、华东区、某支付渠道的超时为什么升高”。

二者不是互斥关系:监控负责尽早发现,可观测数据负责解释和验证。

mermaid
flowchart TD
    A["监控发现SLI或资源异常"] --> B["确定时间窗、范围和版本"]
    B --> C["日志、指标、链路、事件相互关联"]
    C --> D["建立可证伪的根因假设"]
    D --> E["修复或止损"]
    E --> F["用同一SLI和业务指标验证恢复"]

二、六类证据分别回答什么

证据主要回答典型数据不能单独证明
Metrics 指标多少、多久、趋势如何QPS、错误率、P99、CPU、重启数单次请求具体在哪行失败
Logs 日志某时刻代码或组件说了什么异常堆栈、业务状态、GC日志没打印不代表没发生
Traces 链路一次请求经过哪里、耗时在哪Trace、Span、Status、属性未采样请求的完整细节
Events 事件K8s组件最近做了什么判断FailedScheduling、BackOff、FailedMount长期历史;Event会聚合和过期
Object Status 状态当前资源收敛到了哪里Condition、Reason、ExitCode、Ready完整历史变化过程
Profiles 剖析运行时资源消耗集中在哪里CPU火焰图、Heap、锁、线程业务影响范围和平台事件

排查不能只选其中一个。例如接口错误可能同时表现为:

  • RED 指标显示新版错误率升高。
  • Trace 显示订单服务调用库存服务超时。
  • 库存日志显示数据库连接池获取超时。
  • Pod 指标显示 CPU 不高,但连接池 Active 已满。
  • K8s Event 没异常,说明这更像应用/依赖问题而非调度问题。

三、先画清楚观测数据从哪里来

mermaid
flowchart TD
    A["应用与Runtime"] --> B["stdout日志、业务指标、Trace、Profile"]
    C["kubelet与容器运行时"] --> D["容器状态、节点日志、资源统计"]
    E["Kubernetes API"] --> F["对象Status、Event、资源元数据"]
    B --> G["采集Agent或Collector"]
    D --> G
    F --> H["kube-state-metrics或API采集"]
    G --> I["日志/指标/Trace后端"]
    H --> I
    I --> J["Dashboard、告警、查询和Runbook"]

必须知道数据源,才能判断“没数据”意味着什么。Prometheus 没抓到指标可能是应用挂了、ServiceMonitor 选择器不匹配、网络不通、认证失败或采集系统故障,并不自动等于指标值为零。

四、Kubernetes对象状态是第一层证据

4.1 先确认集群、Namespace和时间

事故第一分钟先记录:

text
告警开始时间与时区
用户影响和业务接口
集群、Region、Namespace
服务、版本、镜像Digest
最近发布、配置、证书、扩容或节点变更

确认当前上下文:

bash
kubectl config current-context
kubectl config view --minify
kubectl get namespace

不要在错误集群里看到“Pod不存在”后判断服务已经被删除。

4.2 获取资源全景

bash
kubectl get deployment,rs,pod,service,endpointslice -n commerce \
  -l app=order-api -o wide

检查:

  • Deployment 期望、当前、可用、就绪副本数。
  • 新旧 ReplicaSet 分别有多少副本。
  • Pod 在哪些 Node、Pod IP、Ready、Restart Count。
  • Service Selector 是否选中正确 Label。
  • EndpointSlice 是否包含就绪地址和正确端口。

4.3 Pod Phase不是根因

bash
kubectl get pod <pod-name> -n commerce -o yaml

重点区域:

路径用途
.spec.nodeName是否已被调度到Node
.status.phasePod粗粒度阶段
.status.conditionsScheduled、Initialized、Ready等条件
.status.containerStatuses当前与上次容器状态、重启数、镜像ID
.status.initContainerStatusesInit Container执行结果
.metadata.ownerReferences找到上层ReplicaSet/Job
.metadata.deletionTimestamp是否正在删除

CrashLoopBackOffImagePullBackOff 常是 Container Waiting Reason,不是 Pod Phase。只记录“Pod 是 Running”会丢掉容器持续重启或 NotReady 的关键信息。

4.4 一条命令输出容器状态摘要

bash
kubectl get pod <pod-name> -n commerce \
  -o jsonpath='{range .status.containerStatuses[*]}name={.name}{" ready="}{.ready}{" restarts="}{.restartCount}{" current="}{.state}{" last="}{.lastState}{"\n"}{end}'

查看上次终止结果:

bash
kubectl get pod <pod-name> -n commerce \
  -o jsonpath='{range .status.containerStatuses[*]}{.name}{" exit="}{.lastState.terminated.exitCode}{" reason="}{.lastState.terminated.reason}{" started="}{.lastState.terminated.startedAt}{" finished="}{.lastState.terminated.finishedAt}{"\n"}{end}'

五、Event原理、读取方式和边界

Kubernetes 组件在调度失败、镜像拉取、挂载、探针、驱逐等动作中发布 Event。kubectl describe 底部常展示相关事件,但生产排查建议同时查询 Event 对象。

bash
kubectl get events -n commerce --sort-by=.metadata.creationTimestamp
kubectl events -n commerce --for pod/<pod-name>

kubectl events 的可用参数取决于 kubectl 版本;不支持时使用:

bash
kubectl get events -n commerce \
  --field-selector involvedObject.kind=Pod,involvedObject.name=<pod-name> \
  --sort-by=.metadata.creationTimestamp

5.1 Event字段怎么读

字段含义
TypeNormal或Warning,不代表Warning一定是根因
Reason机器可聚合的原因,如FailedScheduling、BackOff
Message/Note组件给出的具体判断和上下文
Source/ReportingController谁发布了事件
Count/Series相似事件可能被聚合后的次数
First/Last observed time第一次和最近观察时间
Regarding/InvolvedObject事件关联对象

5.2 Event为什么不能当长期日志

Event 可能被聚合、限流,并按集群配置过期。看到 x120 over 20m 不代表后端保存了120条完整独立日志。需要长期保留时,把 Event 导出到日志/事件平台,同时保留组件日志。

5.3 高频Reason与下一跳

Reason常见层次下一步
FailedSchedulingScheduler读完整过滤原因、Request、Taint、Affinity、PVC
FailedMount/FailedAttachVolumekubelet/CSI查PVC、PV、VolumeAttachment、CSI日志
FailedCreatePodSandBoxRuntime/CNI查kubelet、Runtime、CNI DaemonSet
ErrImagePull/ImagePullBackOffRegistry/Runtime查镜像引用、Secret、DNS、TLS、限流
Unhealthy探针查探针类型、响应、超时和应用日志
BackOff容器反复失败--previous、ExitCode、lastState
Evicted节点压力/策略查Pod reason、Node Condition、磁盘/内存/PID

六、容器日志从哪里来

应用将日志写到 stdout/stderr 后,容器运行时捕获输出并按 CRI 日志格式写到节点文件。kubelet 管理容器日志轮转,kubectl logs 通过 API Server/kubelet 链路读取对应容器日志。

mermaid
flowchart TD
    A["应用写stdout/stderr"] --> B["容器运行时写节点CRI日志文件"]
    B --> C["kubectl logs经API Server和kubelet读取"]
    B --> D["节点日志Agent采集"]
    D --> E["Loki/Elasticsearch等集中存储"]
    E --> F["按集群、Namespace、Pod、版本、Trace查询"]

容器内直接写 /app/logs/app.log 时,kubectl logs 看不到,除非应用同时输出 stdout 或采集器专门挂载并读取该路径。K8s 推荐应用日志输出 stdout/stderr,由节点级 Agent 统一采集。

6.1 查看单容器日志

bash
kubectl logs <pod-name> -n commerce -c order-api \
  --since=30m --tail=500 --timestamps

持续跟踪:

bash
kubectl logs -f <pod-name> -n commerce -c order-api \
  --since=5m --timestamps

6.2 查看上一次崩溃实例

bash
kubectl logs <pod-name> -n commerce -c order-api \
  --previous --tail=500 --timestamps

--previous 读取同一 Pod、同一容器名称的上一个终止实例,适合 CrashLoopBackOff。它不是完整历史归档;多次重启通常不能靠 kubelet 保留所有旧实例日志,因此必须集中采集。

6.3 多容器Pod

列出容器:

bash
kubectl get pod <pod-name> -n commerce \
  -o jsonpath='{.spec.containers[*].name}{"\n"}'

分别查看主容器和 Sidecar:

bash
kubectl logs <pod-name> -n commerce -c order-api --tail=200
kubectl logs <pod-name> -n commerce -c service-mesh-proxy --tail=200

使用 --all-containers=true 适合快速浏览,但正式证据要保留容器名,否则无法判断错误来自业务还是代理。

6.4 Init Container日志

bash
kubectl get pod <pod-name> -n commerce \
  -o jsonpath='{.spec.initContainers[*].name}{"\n"}'
kubectl logs <pod-name> -n commerce -c init-database --tail=200

Pod 卡在 Init 时,普通业务容器可能根本没有启动,查询错误容器会误导排查。

6.5 Deployment聚合日志的限制

bash
kubectl logs deployment/order-api -n commerce \
  --all-containers=true --prefix --since=10m

它适合临时查看当前选中的 Pod,不是高可靠日志平台。滚动更新、Pod 删除、日志量大和并发流式读取时,应使用集中日志系统并按 Pod UID、容器、版本区分。

6.6 日志必须有哪些字段

推荐结构化 JSON:

json
{
  "timestamp": "2026-07-15T10:18:32.412+08:00",
  "level": "ERROR",
  "service": "order-api",
  "version": "git-8f03a1c",
  "trace_id": "7fdc8a92d0d04f39b8f3d770a3118811",
  "span_id": "f3b94c782ea31c12",
  "request_id": "req-20260715-10001",
  "event": "inventory_reservation_failed",
  "order_id_hash": "sha256:...",
  "error_type": "DownstreamTimeout",
  "duration_ms": 1503
}

不要记录密码、Token、完整身份证、病历、手机号、密钥和支付敏感数据。高基数字段如 orderId 不应随意做 Metrics Label,但可以受控进入日志和 Trace 属性。

七、Metrics数据链路和工具边界

7.1 kubectl top从哪里取数据

kubectl top 通常查询 Resource Metrics API,由 Metrics Server 聚合 kubelet 提供的近期 CPU/内存资源指标。

mermaid
flowchart TD
    A["kubelet暴露节点/Pod资源统计"] --> B["Metrics Server采集聚合"]
    B --> C["Resource Metrics API"]
    C --> D["kubectl top与HPA部分指标"]
bash
kubectl top node
kubectl top pod -n commerce
kubectl top pod -n commerce --containers

kubectl top 适合快速查看近期资源,不是历史监控:

  • 通常没有长时间序列。
  • 无法回看昨晚故障时的资源。
  • 不提供应用QPS、错误率和P99。
  • 短采样窗口可能错过瞬时峰值。
  • CPU百分比、Working Set等口径需结合实现理解。

7.2 Prometheus做什么

Prometheus 按配置周期抓取指标端点,把样本按时间序列保存。每条序列由指标名和 Label 集合唯一确定,例如:

text
http_server_requests_seconds_count{
  service="order-api",
  namespace="commerce",
  version="git-8f03a1c",
  method="POST",
  route="/orders",
  status="500"
}

采集间隔是15秒时,两个采样点之间发生的短尖峰可能被聚合;Counter 重启归零,查询应使用 rate()/increase() 等函数处理变化,不直接用绝对值比较实例。

7.3 kube-state-metrics做什么

kube-state-metrics 读取 Kubernetes API 对象并把其状态转换为指标,例如:

  • Deployment期望/可用副本数。
  • Pod Phase和Condition。
  • Container Restart Count。
  • PVC状态。
  • Node Condition。

它通常不测量容器真实 CPU/内存,也不等同于 Metrics Server。它把“API对象声明和状态”暴露成可查询时间序列。

7.4 kubelet/cAdvisor类指标

kubelet能够提供节点和容器资源相关统计,常见监控栈会采集 CPU、内存、文件系统、网络等容器指标。具体端点、认证和指标稳定性受 Kubernetes 版本与发行版影响,不能在生产中假设所有内部指标永远不变。

7.5 Request、Limit、Usage和Throttle

数据含义
CPU RequestScheduler容量承诺和CPU权重的重要依据
CPU Limit常通过CFS配额限制可用CPU时间,超出可被Throttle
Memory Request调度和节点压力下QoS/驱逐决策的重要输入
Memory Limit容器cgroup内存上限,超限可能OOM Kill
Usage当前或采样窗口实际消耗
Working Set估算活跃内存的一种口径,不等于Java Heap

CPU 使用率没有到 100% 仍可能发生 CPU Throttling。例如容器 Limit 为 1核,节点有16核,应用短时需要4核,会被限制在1核,节点总体CPU可能很空但请求延迟升高。

应同时观察:

  • CPU Usage相对Request/Limit。
  • Throttled periods/time。
  • Run queue或应用线程池等待。
  • P95/P99延迟。
  • GC和Safepoint。

7.6 指标基数为什么会打爆监控

每个不同 Label 组合产生一条时间序列。如果把 userId、orderId、完整URL、traceId 放进 Label,序列数会高速增长,导致采集、内存、磁盘和查询成本上升。

Metrics Label 应使用有限集合:service、namespace、cluster、version、route模板、status类别。高基数上下文放日志或Trace。

八、应用指标怎么设计

8.1 RED方法

适合请求型服务:

  • Rate:请求速率。
  • Errors:错误数量/比例。
  • Duration:请求耗时分布。

不要只看平均耗时。少量极慢请求可能被平均值掩盖,应关注 P50、P95、P99,并理解 Histogram Bucket 的精度。

8.2 USE方法

适合资源:

  • Utilization:资源使用比例。
  • Saturation:排队、Throttle、等待程度。
  • Errors:资源错误。

CPU 80% 是 Utilization;线程池队列长度和CPU Throttle属于 Saturation;磁盘IO错误属于 Error。

8.3 四个黄金信号

  • Latency:延迟。
  • Traffic:流量。
  • Errors:错误。
  • Saturation:饱和度。

8.4 业务指标不可缺少

订单系统还应观测:

  • 创建订单成功率。
  • 支付成功率和回调延迟。
  • 库存预占失败率。
  • MQ生产/消费速率与Lag。
  • 补偿任务积压量。
  • 不同版本处理结果差异。

技术指标正常但支付成功率下降,仍然是严重事故。

九、Trace如何串起一次请求

Trace由多个Span组成,每个Span记录一个操作的开始、结束、父子关系、状态和属性。入口服务创建或继续 Trace Context,下游通过HTTP Header或消息属性传播。

mermaid
flowchart TD
    A["Gateway Span"] --> B["Order API Span"]
    B --> C["MySQL Span"]
    B --> D["Inventory HTTP Span"]
    B --> E["RocketMQ Producer Span"]
    E --> F["异步Consumer使用消息上下文继续链路"]

9.1 TraceId和SpanId

  • TraceId 标识整条端到端链路。
  • SpanId 标识其中一次操作。
  • Parent SpanId 表示调用关系。

日志中写入 TraceId 后,可以从告警中的异常版本定位 Trace,再用 TraceId 搜索各服务日志。

9.2 采样意味着什么

全量 Trace 成本高,常采用头部或尾部采样:

  • Head Sampling 在请求开始时决定,成本可控,但可能漏掉后来才知道的错误。
  • Tail Sampling 收集完成后按错误/延迟决定保留,诊断价值高但需要Collector缓存和更多资源。

“查不到 Trace”不能证明请求没发生,可能只是未采样、上下文传播丢失、Collector背压或导出失败。

9.3 异步消息链路

生产者应把 Trace Context 放入消息Header,消费者提取并创建消费Span。重试、批量和异步处理可能不是简单父子调用,应根据语义使用链接关系,避免虚构一条长时间占用的同步Span。

十、Profiling和火焰图在K8s中的位置

当指标证明“CPU高”但不知道代码热点时,使用 JFR、async-profiler、pprof 等获取 Profile。火焰图把采样栈聚合:横向宽度代表样本占比,不代表时间顺序;纵向表示调用栈深度。

在容器中采集前必须确认:

  • 目标Pod、容器、PID和版本。
  • 工具与Runtime/JDK兼容。
  • 容器权限和安全策略允许。
  • 采样时长与额外开销可控。
  • Profile文件不会包含敏感信息。
  • Pod可能重建,产物要及时安全导出。

Profile 用来解释热点和内存持有,不能代替用户影响、K8s Event 和发布时间线。

十一、exec、debug和节点诊断边界

11.1 exec进入现有容器

bash
kubectl exec -it <pod-name> -n commerce -c order-api -- sh

适合做最小只读检查,例如:

bash
kubectl exec <pod-name> -n commerce -c order-api -- \
  sh -c 'printenv | sort'

但环境变量可能包含 Secret,不要把完整输出复制到工单。精简/Distroless镜像没有Shell是正常现象,不应为了方便在生产镜像安装大量调试工具。

11.2 Ephemeral Debug Container

集群和权限允许时,可临时附加调试容器:

bash
kubectl debug -it <pod-name> -n commerce \
  --image=registry.example.com/ops/debug-tools:approved \
  --target=order-api

具体进程可见性取决于Runtime和目标配置。调试镜像必须受控、扫描和审计,不能随意拉公网latest镜像,也不能默认携带生产凭据。

11.3 Node调试

bash
kubectl debug node/<node-name> -it \
  --image=registry.example.com/ops/node-debug:approved

节点调试接近高权限操作,应限制到运维角色并记录审计。先通过 API 对象和集中日志定位,只有证据指向节点、Runtime、CNI、磁盘或内核时再进入节点。

十二、生产排查总流程

mermaid
flowchart TD
    A["收到告警或用户反馈"] --> B["确认影响、时间窗、集群、Namespace和版本"]
    B --> C["查看SLI和业务指标确认范围"]
    C --> D["检查Deployment、ReplicaSet和Pod状态"]
    D --> E{"Pod在哪个阶段失败"}
    E -- "未调度" --> F["Event、Request、Taint、Affinity、PVC"]
    E -- "节点准备失败" --> G["kubelet、Runtime、CNI、CSI、镜像"]
    E -- "运行后异常" --> H["当前/previous日志、退出码、探针、资源"]
    E -- "Ready但请求失败" --> I["EndpointSlice、Service、Ingress、Trace和依赖"]
    F --> J["建立根因和止损方案"]
    G --> J
    H --> J
    I --> J
    J --> K["用SLI、业务指标和新请求验证"]

12.1 第一步:确认用户影响

回答:

  • 是所有用户还是某地域/租户/接口?
  • 是完全不可用、错误率上升还是延迟上升?
  • 从哪个精确时间开始?
  • 影响哪个版本?
  • 告警是否由真实业务SLI触发?

先确认影响可以决定是否立即回滚、切流或限流,而不是陷入单个Pod日志。

12.2 第二步:关联最近变更

检查:

  • Deployment Revision和镜像ID/Digest。
  • ConfigMap/Secret版本或Checksum Annotation。
  • Ingress、Service、NetworkPolicy修改。
  • HPA、Request/Limit变化。
  • 节点维护、证书、DNS和云负载均衡变更。

相关不等于因果。发布恰好发生在告警前是强线索,但仍需版本分组指标或回滚结果验证。

12.3 第三步:按层取证

bash
kubectl get deployment,rs,pod -n commerce -l app=order-api -o wide
kubectl describe deployment order-api -n commerce
kubectl describe pod <pod-name> -n commerce
kubectl logs <pod-name> -n commerce -c order-api --since=30m --timestamps
kubectl logs <pod-name> -n commerce -c order-api --previous --tail=500 --timestamps
kubectl top pod <pod-name> -n commerce --containers
kubectl get endpointslice -n commerce -l kubernetes.io/service-name=order-api -o yaml

12.4 第四步:止损与保留现场

根据风险选择:

  • 暂停继续发布。
  • 回滚到已知可用 Digest。
  • 将异常版本流量权重降为0。
  • 扩容经过验证的旧版本。
  • 限流或关闭非核心功能。
  • 隔离异常 Node。

止损前记录 Pod YAML、Event、日志、镜像ID、节点、关键指标和Trace。盲目删除所有异常Pod可能让 --previous 和节点现场一起丢失。

12.5 第五步:验证恢复

至少验证:

  • 用户侧成功率、P95/P99恢复。
  • 业务成功率恢复。
  • 新旧版本错误率差异消失。
  • Ready Endpoint数量稳定。
  • Pod Restart/OOM/Throttle不再增长。
  • MQ Lag、数据库连接池等下游恢复。
  • 持续观察一个合理窗口,没有再次恶化。

十三、场景一:Pod一直Pending

13.1 先判断有没有Node

bash
kubectl get pod <pod-name> -n commerce -o wide
kubectl describe pod <pod-name> -n commerce

NODE 为空且 PodScheduled=False,优先看 Scheduler;已有 Node 但 Pending,更可能是节点侧镜像、网络、存储或Init过程。

13.2 Scheduler常见原因

text
0/6 nodes are available:
2 Insufficient cpu,
2 node(s) had untolerated taint,
2 node(s) didn't match Pod's node affinity.

它表示六个节点被三类不同条件过滤。处理方法可能分别是扩容/调整Request、增加合理Toleration或修正Affinity,不能统一归因“资源不足”。

13.3 PVC导致Pending

bash
kubectl get pvc,pv -n commerce
kubectl describe pvc <pvc-name> -n commerce
kubectl get storageclass

检查 StorageClass、AccessMode、容量、Topology、BindingMode 和 CSI Provisioner。

十四、场景二:ImagePullBackOff

ErrImagePull 是一次拉取失败;反复失败后进入 ImagePullBackOff 退避。排查:

bash
kubectl describe pod <pod-name> -n commerce
kubectl get pod <pod-name> -n commerce \
  -o jsonpath='{range .spec.containers[*]}{.name}{" image="}{.image}{"\n"}{end}'

检查:

  1. Registry和Repository拼写。
  2. Tag/Digest是否存在;生产优先不可变Digest。
  3. imagePullSecret是否存在于同一Namespace并被引用。
  4. ServiceAccount是否配置正确Secret。
  5. Node到Registry的DNS、路由、代理、TLS和时间。
  6. Registry限流、配额和服务端错误。
  7. 镜像平台架构是否支持目标Node。

不要在日志中输出Registry密码,也不要为了绕过TLS临时关闭证书校验后长期遗留。

十五、场景三:CrashLoopBackOff

15.1 获取当前状态与上次退出

bash
kubectl describe pod <pod-name> -n commerce
kubectl logs <pod-name> -n commerce -c order-api --previous --tail=500 --timestamps
kubectl get pod <pod-name> -n commerce \
  -o jsonpath='{.status.containerStatuses[?(@.name=="order-api")].lastState.terminated}'

15.2 常见根因

  • 应用启动配置错误并主动退出。
  • Command/Args 或工作目录错误。
  • 依赖数据库/DNS不可用且应用选择退出。
  • livenessProbe过早杀死慢启动应用。
  • 内存超限后 OOMKilled。
  • 文件系统只读、权限或挂载错误。
  • Secret内容格式错误。

BackOff 是 kubelet 防止高频重启的退避保护,不是根因名称。

15.3 为什么只看当前日志不够

当前容器刚重新启动,可能尚未打印原始异常;真正崩溃堆栈在上一实例,所以要使用 --previous。如果 Pod 本身已被替换,只能依赖集中日志平台。

十六、场景四:OOMKilled与Java OOM

16.1 获取K8s证据

bash
kubectl get pod <pod-name> -n commerce \
  -o jsonpath='{range .status.containerStatuses[*]}{.name}{" reason="}{.lastState.terminated.reason}{" exit="}{.lastState.terminated.exitCode}{" restarts="}{.restartCount}{"\n"}{end}'

kubectl describe pod <pod-name> -n commerce
kubectl top pod <pod-name> -n commerce --containers

reason=OOMKilledexitCode=137 支持“容器进程被内存控制/OOM机制终止”,但不能直接证明 Java Heap 泄漏。

16.2 容器内存不只有Java Heap

text
容器内存
├── Java Heap
├── Metaspace与Code Cache
├── Direct Buffer与Mapped Memory
├── Thread Stack
├── JNI/Native Library
├── GC数据结构
└── 其他进程与部分内核记账

如果 -Xmx 几乎等于容器 Limit,即使 Heap 未达到 Java Heap OOM,也可能因 Native/Direct/线程栈把 cgroup 总内存推过上限而被 OOM Kill。

16.3 历史指标必须存在

容器已经退出后 kubectl top 只能看到新实例或没有数据。需要 Prometheus 记录故障前:

  • Container Working Set/Usage。
  • RSS与Cache口径。
  • JVM Heap Used/Committed/Max。
  • Direct Buffer、Metaspace、线程数。
  • GC暂停和分配速率。
  • Restart/OOM指标。

结合 Heap Dump、Native Memory Tracking、JFR 或系统证据继续判断,不能只靠一张当前内存截图。

十七、场景五:CPU不高但接口很慢

可能原因:

  • CPU Limit导致Throttling,节点总体CPU仍不高。
  • 线程等待数据库、Redis、HTTP或锁。
  • 线程池/连接池排队。
  • Stop-The-World或Safepoint。
  • DNS、网络重传、Sidecar延迟。
  • 下游变慢。

证据组合:

text
RED延迟与错误
→ 版本和Pod维度拆分
→ CPU Usage与Throttling
→ Trace定位慢Span
→ 线程池/连接池/GC指标
→ 必要时线程Dump或CPU Profile

不要看到CPU 20%就断言“应用没有性能问题”。等待型瓶颈本来就可能不消耗CPU。

十八、场景六:Pod Ready但Service访问503

按链路检查:

bash
kubectl get pod -n commerce -l app=order-api --show-labels
kubectl get service order-api -n commerce -o yaml
kubectl get endpointslice -n commerce \
  -l kubernetes.io/service-name=order-api -o yaml
kubectl get ingress -n commerce
kubectl describe ingress <ingress-name> -n commerce

分层判断:

  1. Service Selector是否选中目标Pod。
  2. EndpointSlice是否有地址且Condition Ready。
  3. Service targetPort是否对应容器真实监听端口/端口名。
  4. 应用是否只监听 127.0.0.1 而非Pod接口。
  5. NetworkPolicy是否阻断来源。
  6. Ingress Backend的Service名和端口是否正确。
  7. Ingress Controller是否读取到配置并能连接Endpoint。
  8. 503由Ingress、Service Mesh还是应用返回,查看响应Header和对应日志。

“Pod Ready”只证明它通过了自己配置的 Readiness。如果探针只返回固定200,没有验证关键初始化,Ready本身也可能是假阳性。

十九、场景七:Node NotReady

bash
kubectl get node
kubectl describe node <node-name>
kubectl get pod -A --field-selector spec.nodeName=<node-name> -o wide

检查:

  • Ready Condition的Reason/Message和转换时间。
  • MemoryPressure、DiskPressure、PIDPressure、NetworkUnavailable。
  • Lease/心跳是否停止。
  • 节点网络、kubelet、Runtime状态。
  • 磁盘容量和inode。
  • 证书过期、时间漂移。
  • CNI DaemonSet状态。

先评估节点上业务和数据风险,再执行 cordon/drain。带本地存储、有状态副本不足或严格PDB时,强制驱逐可能扩大事故。

二十、告警应该怎样设计

20.1 从用户症状告警

优先:

  • 成功率/SLO燃烧率。
  • P95/P99延迟。
  • 订单、支付、采集等业务失败率。
  • 可用副本不足并实际影响容量。

资源告警用于解释和提前预警,不应替代用户症状。

20.2 避免单点瞬时告警

瞬间一个Pod CPU 90%可能是正常突发。告警需要窗口、持续时间、比例和影响范围。例如:

text
5分钟错误预算快速燃烧
且请求量高于最低阈值
且至少两个实例或一个完整版本受影响

具体阈值来自 SLO、压测和历史基线,不应复制固定数字到所有服务。

20.3 告警必须带上下文

至少包含:

  • 集群、Namespace、服务、环境。
  • 当前版本和最近发布链接。
  • 指标当前值、阈值和持续时间。
  • 受影响接口/区域。
  • Dashboard、日志、Trace查询链接。
  • Runbook和负责人。

20.4 告警降噪

同一Node故障可能触发几十个Pod告警。通过拓扑关联、告警分组和抑制把根因告警置顶,但不能完全隐藏用户影响告警。

二十一、商业Demo:可观测的Spring Boot服务

下面只展示 K8s 侧约束;应用需要实现结构化日志、Actuator/Micrometer指标和 Trace Context。

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-api
  namespace: commerce
  labels:
    app: order-api
spec:
  replicas: 3
  selector:
    matchLabels:
      app: order-api
  template:
    metadata:
      labels:
        app: order-api
        version: git-8f03a1c
      annotations:
        observability.example.com/runbook: "order-api"
    spec:
      containers:
        - name: order-api
          image: registry.example.com/order-api@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
          ports:
            - name: http
              containerPort: 8080
            - name: metrics
              containerPort: 8081
          env:
            - name: SERVICE_NAME
              value: order-api
            - name: SERVICE_VERSION
              value: git-8f03a1c
            - name: LOG_FORMAT
              value: json
          resources:
            requests:
              cpu: 250m
              memory: 512Mi
            limits:
              cpu: "1"
              memory: 1Gi
          startupProbe:
            httpGet:
              path: /actuator/health/liveness
              port: http
            periodSeconds: 5
            failureThreshold: 24
          readinessProbe:
            httpGet:
              path: /actuator/health/readiness
              port: http
            periodSeconds: 5
            timeoutSeconds: 2
            failureThreshold: 3
          livenessProbe:
            httpGet:
              path: /actuator/health/liveness
              port: http
            periodSeconds: 10
            timeoutSeconds: 2
            failureThreshold: 3

这里没有直接写 ServiceMonitor,因为它是 Prometheus Operator 的 CRD,不是 Kubernetes 内置资源。使用哪种采集对象取决于项目监控栈;无论采用注解发现、ServiceMonitor还是OpenTelemetry Collector,都要验证 Target 真正为 Up、Label正确且指标持续到达。

二十二、事故证据包模板

事故中可保存以下只读证据。执行前确认命令不会泄露 Secret;YAML 和日志需要脱敏。

bash
kubectl get deployment order-api -n commerce -o yaml
kubectl get rs,pod,service,endpointslice -n commerce -l app=order-api -o wide
kubectl describe deployment order-api -n commerce
kubectl describe pod <pod-name> -n commerce
kubectl get events -n commerce --sort-by=.metadata.creationTimestamp
kubectl logs <pod-name> -n commerce -c order-api --since=30m --timestamps
kubectl logs <pod-name> -n commerce -c order-api --previous --tail=500 --timestamps
kubectl top pod <pod-name> -n commerce --containers
kubectl describe node <node-name>

最终事故结论使用:

text
现象:用户看到什么
开始时间:精确时间和时区
影响范围:接口、区域、租户、版本
变更:发布、配置、基础设施变化
证据:指标、日志、Trace、Event、状态
根因:什么机制在什么条件下失败
为什么未被提前发现:测试/告警/发布闸门缺口
止损:回滚、切流、扩容、限流
永久修复:代码、配置、容量、平台治理
验证:哪些SLI和业务指标恢复,观察多久
预防:自动化测试、Runbook、告警和演练

二十三、常见错误做法

  1. 告警后先删除所有异常Pod,导致上次日志和现场丢失。
  2. 只看 kubectl top 当前值,推断半小时前资源正常。
  3. 只按 Pod 名查询日志,滚动更新后无法按版本聚合。
  4. 将 orderId、userId 放进 Prometheus Label,造成高基数。
  5. 将健康检查写成固定返回200,产生Ready假阳性。
  6. 看到137就直接判定Java Heap泄漏。
  7. 看到CPU不高就排除线程池、锁、IO和Throttle。
  8. 只看应用日志,不看Event、Condition和EndpointSlice。
  9. 用公网未固定Tag的调试镜像进入生产Pod。
  10. 告警只写“服务异常”,没有集群、版本、Dashboard和Runbook。

二十四、面试标准回答

Kubernetes生产问题怎么排查

先确认用户影响、时间窗、集群、Namespace、版本和最近变更;再从业务SLI定位异常版本,查看Deployment、ReplicaSet、Pod Condition和Event判断失败层。未调度查Scheduler约束,节点准备失败查Runtime/CNI/CSI/镜像,运行后异常查当前与previous日志、退出码、探针和资源,Ready但访问失败查EndpointSlice、Service、Ingress和Trace。止损前保留现场,修复后用同一SLI和业务指标持续验证。

kubectl top和Prometheus有什么区别

kubectl top通常读取Metrics Server提供的近期CPU/内存Resource Metrics,适合快速查看当前资源,不保存完整历史,也没有业务指标。Prometheus周期抓取时间序列,可保存历史、查询Rate/Histogram并告警;kube-state-metrics则把K8s API对象状态转成指标,三者数据来源和用途不同。

CrashLoopBackOff怎么查

CrashLoopBackOff表示容器反复失败后kubelet进入重启退避,不是根因。先看describe Event、ContainerStatus的lastState/ExitCode/Reason,再用logs --previous获取上次崩溃日志,结合Command、配置、依赖、探针、权限和OOM证据判断。不能一上来删除Pod,否则可能丢失现场。

OOMKilled是否等于Java Heap OOM

不等于。OOMKilled说明容器进程被内存控制/OOM机制终止;容器内存还包括Heap、Metaspace、Direct Buffer、线程栈、Native和其他进程。需要结合容器历史内存、JVM各内存区、线程数、GC、Heap Dump或NMT判断,不能仅凭137得出Heap泄漏。

日志、指标和Trace怎样关联

指标先发现哪一版本、接口和时间窗异常;Trace用TraceId定位一次请求的慢Span或错误下游;日志携带TraceId、版本、Pod和结构化错误还原代码现场;K8s Event与状态补充调度、镜像、探针和资源证据。它们相互验证,没有一种信号能独立覆盖全部根因。

二十五、学习实验与验收

实验一:保留重启前日志

让测试容器第一次启动退出、第二次保持运行,分别比较:

bash
kubectl logs <pod> -c <container>
kubectl logs <pod> -c <container> --previous

解释为什么当前日志和上一次日志不同,以及 Pod 被替换后为什么需要集中日志。

实验二:制造Readiness失败

把 Readiness 路径改错,观察:

  • Pod Phase是否仍可能Running。
  • Ready Condition如何变化。
  • EndpointSlice是否移除就绪地址。
  • Service请求结果如何变化。

实验三:制造CPU Limit

在隔离测试环境对CPU密集程序设置较低Limit,比较 Usage、Throttling和P99。证明“节点CPU不高”和“容器不被限速”不是一回事。

实验四:建立完整证据链

选择一个失败发布,输出:

  1. 业务SLI变化截图/查询。
  2. Deployment Revision和镜像Digest。
  3. 异常Pod Condition与Event。
  4. 当前/previous日志。
  5. Trace慢Span或错误Span。
  6. EndpointSlice和Ingress链路。
  7. 根因、止损、修复与验证指标。

验收清单

  1. 能解释六类信号各自能证明和不能证明什么。
  2. 能解释Metrics Server、Prometheus和kube-state-metrics区别。
  3. 能找到多容器、Init Container和previous日志。
  4. 能从ContainerStatus提取ExitCode、Reason和Restart Count。
  5. 能根据Event把故障路由到Scheduler、CRI、CNI或CSI。
  6. 能解释CPU Throttle与节点总体CPU低并存。
  7. 能解释OOMKilled与Java Heap OOM区别。
  8. 能从Ready Pod追踪到EndpointSlice、Service和Ingress。
  9. 能设计包含版本、TraceId且不泄密的结构化日志。
  10. 能写出带上下文、Runbook和恢复验证的告警。

关联知识点