Kubernetes 可观测性与生产故障排查
Kubernetes 中 Pod 会扩缩容、滚动更新、漂移和重建,容器文件系统也可能随实例删除而消失。生产排查不能依赖“登录固定服务器看日志”,而要把用户请求、应用版本、Workload、Pod、Container、Node、Service、Ingress 和控制面证据串成时间线。
可观测性不是“装了 Prometheus 和 ELK”,而是系统能否通过外部输出回答:发生了什么、从什么时候开始、影响谁、问题在哪一层、为什么发生、修复后是否真正恢复。
学习目标
学完后应能:
- 区分日志、指标、链路、事件、状态和 Profiling 的证据价值。
- 解释 Metrics Server、Prometheus、kube-state-metrics、kubelet/cAdvisor 的数据来源和边界。
- 解释容器 stdout 日志如何落到节点、为什么 Pod 删除后可能丢失。
- 使用
get、describe、logs、events、top、exec、debug分层取证。 - 区分当前容器日志、上一次重启实例日志、Init Container 和多容器日志。
- 根据 Phase、Condition、ContainerStatus、Reason、ExitCode 和 Event 判断故障层。
- 从 Deployment 一直追踪到 ReplicaSet、Pod、Node、EndpointSlice 和 Ingress。
- 分析 Pending、ImagePullBackOff、CrashLoopBackOff、OOMKilled、CPU Throttling、NotReady 和 503。
- 用 RED、USE、四个黄金信号和业务指标建立 SLI/SLO 告警。
- 形成“现象—时间—范围—变更—证据—根因—修复—验证”的事故结论。
一、可观测性和监控有什么区别
监控通常针对已知问题设置固定指标和阈值,例如“5分钟错误率大于1%告警”。可观测性要求系统暴露足够上下文,使工程师能探索未知问题,例如“只有新版、华东区、某支付渠道的超时为什么升高”。
二者不是互斥关系:监控负责尽早发现,可观测数据负责解释和验证。
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 没异常,说明这更像应用/依赖问题而非调度问题。
三、先画清楚观测数据从哪里来
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和时间
事故第一分钟先记录:
告警开始时间与时区
用户影响和业务接口
集群、Region、Namespace
服务、版本、镜像Digest
最近发布、配置、证书、扩容或节点变更确认当前上下文:
kubectl config current-context
kubectl config view --minify
kubectl get namespace不要在错误集群里看到“Pod不存在”后判断服务已经被删除。
4.2 获取资源全景
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不是根因
kubectl get pod <pod-name> -n commerce -o yaml重点区域:
| 路径 | 用途 |
|---|---|
.spec.nodeName | 是否已被调度到Node |
.status.phase | Pod粗粒度阶段 |
.status.conditions | Scheduled、Initialized、Ready等条件 |
.status.containerStatuses | 当前与上次容器状态、重启数、镜像ID |
.status.initContainerStatuses | Init Container执行结果 |
.metadata.ownerReferences | 找到上层ReplicaSet/Job |
.metadata.deletionTimestamp | 是否正在删除 |
CrashLoopBackOff、ImagePullBackOff 常是 Container Waiting Reason,不是 Pod Phase。只记录“Pod 是 Running”会丢掉容器持续重启或 NotReady 的关键信息。
4.4 一条命令输出容器状态摘要
kubectl get pod <pod-name> -n commerce \
-o jsonpath='{range .status.containerStatuses[*]}name={.name}{" ready="}{.ready}{" restarts="}{.restartCount}{" current="}{.state}{" last="}{.lastState}{"\n"}{end}'查看上次终止结果:
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 对象。
kubectl get events -n commerce --sort-by=.metadata.creationTimestamp
kubectl events -n commerce --for pod/<pod-name>kubectl events 的可用参数取决于 kubectl 版本;不支持时使用:
kubectl get events -n commerce \
--field-selector involvedObject.kind=Pod,involvedObject.name=<pod-name> \
--sort-by=.metadata.creationTimestamp5.1 Event字段怎么读
| 字段 | 含义 |
|---|---|
| Type | Normal或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 | 常见层次 | 下一步 |
|---|---|---|
| FailedScheduling | Scheduler | 读完整过滤原因、Request、Taint、Affinity、PVC |
| FailedMount/FailedAttachVolume | kubelet/CSI | 查PVC、PV、VolumeAttachment、CSI日志 |
| FailedCreatePodSandBox | Runtime/CNI | 查kubelet、Runtime、CNI DaemonSet |
| ErrImagePull/ImagePullBackOff | Registry/Runtime | 查镜像引用、Secret、DNS、TLS、限流 |
| Unhealthy | 探针 | 查探针类型、响应、超时和应用日志 |
| BackOff | 容器反复失败 | 查--previous、ExitCode、lastState |
| Evicted | 节点压力/策略 | 查Pod reason、Node Condition、磁盘/内存/PID |
六、容器日志从哪里来
应用将日志写到 stdout/stderr 后,容器运行时捕获输出并按 CRI 日志格式写到节点文件。kubelet 管理容器日志轮转,kubectl logs 通过 API Server/kubelet 链路读取对应容器日志。
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 查看单容器日志
kubectl logs <pod-name> -n commerce -c order-api \
--since=30m --tail=500 --timestamps持续跟踪:
kubectl logs -f <pod-name> -n commerce -c order-api \
--since=5m --timestamps6.2 查看上一次崩溃实例
kubectl logs <pod-name> -n commerce -c order-api \
--previous --tail=500 --timestamps--previous 读取同一 Pod、同一容器名称的上一个终止实例,适合 CrashLoopBackOff。它不是完整历史归档;多次重启通常不能靠 kubelet 保留所有旧实例日志,因此必须集中采集。
6.3 多容器Pod
列出容器:
kubectl get pod <pod-name> -n commerce \
-o jsonpath='{.spec.containers[*].name}{"\n"}'分别查看主容器和 Sidecar:
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日志
kubectl get pod <pod-name> -n commerce \
-o jsonpath='{.spec.initContainers[*].name}{"\n"}'
kubectl logs <pod-name> -n commerce -c init-database --tail=200Pod 卡在 Init 时,普通业务容器可能根本没有启动,查询错误容器会误导排查。
6.5 Deployment聚合日志的限制
kubectl logs deployment/order-api -n commerce \
--all-containers=true --prefix --since=10m它适合临时查看当前选中的 Pod,不是高可靠日志平台。滚动更新、Pod 删除、日志量大和并发流式读取时,应使用集中日志系统并按 Pod UID、容器、版本区分。
6.6 日志必须有哪些字段
推荐结构化 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/内存资源指标。
flowchart TD
A["kubelet暴露节点/Pod资源统计"] --> B["Metrics Server采集聚合"]
B --> C["Resource Metrics API"]
C --> D["kubectl top与HPA部分指标"]kubectl top node
kubectl top pod -n commerce
kubectl top pod -n commerce --containerskubectl top 适合快速查看近期资源,不是历史监控:
- 通常没有长时间序列。
- 无法回看昨晚故障时的资源。
- 不提供应用QPS、错误率和P99。
- 短采样窗口可能错过瞬时峰值。
- CPU百分比、Working Set等口径需结合实现理解。
7.2 Prometheus做什么
Prometheus 按配置周期抓取指标端点,把样本按时间序列保存。每条序列由指标名和 Label 集合唯一确定,例如:
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 Request | Scheduler容量承诺和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或消息属性传播。
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进入现有容器
kubectl exec -it <pod-name> -n commerce -c order-api -- sh适合做最小只读检查,例如:
kubectl exec <pod-name> -n commerce -c order-api -- \
sh -c 'printenv | sort'但环境变量可能包含 Secret,不要把完整输出复制到工单。精简/Distroless镜像没有Shell是正常现象,不应为了方便在生产镜像安装大量调试工具。
11.2 Ephemeral Debug Container
集群和权限允许时,可临时附加调试容器:
kubectl debug -it <pod-name> -n commerce \
--image=registry.example.com/ops/debug-tools:approved \
--target=order-api具体进程可见性取决于Runtime和目标配置。调试镜像必须受控、扫描和审计,不能随意拉公网latest镜像,也不能默认携带生产凭据。
11.3 Node调试
kubectl debug node/<node-name> -it \
--image=registry.example.com/ops/node-debug:approved节点调试接近高权限操作,应限制到运维角色并记录审计。先通过 API 对象和集中日志定位,只有证据指向节点、Runtime、CNI、磁盘或内核时再进入节点。
十二、生产排查总流程
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 第三步:按层取证
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 yaml12.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
kubectl get pod <pod-name> -n commerce -o wide
kubectl describe pod <pod-name> -n commerceNODE 为空且 PodScheduled=False,优先看 Scheduler;已有 Node 但 Pending,更可能是节点侧镜像、网络、存储或Init过程。
13.2 Scheduler常见原因
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
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 退避。排查:
kubectl describe pod <pod-name> -n commerce
kubectl get pod <pod-name> -n commerce \
-o jsonpath='{range .spec.containers[*]}{.name}{" image="}{.image}{"\n"}{end}'检查:
- Registry和Repository拼写。
- Tag/Digest是否存在;生产优先不可变Digest。
- imagePullSecret是否存在于同一Namespace并被引用。
- ServiceAccount是否配置正确Secret。
- Node到Registry的DNS、路由、代理、TLS和时间。
- Registry限流、配额和服务端错误。
- 镜像平台架构是否支持目标Node。
不要在日志中输出Registry密码,也不要为了绕过TLS临时关闭证书校验后长期遗留。
十五、场景三:CrashLoopBackOff
15.1 获取当前状态与上次退出
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证据
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 --containersreason=OOMKilled、exitCode=137 支持“容器进程被内存控制/OOM机制终止”,但不能直接证明 Java Heap 泄漏。
16.2 容器内存不只有Java Heap
容器内存
├── 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延迟。
- 下游变慢。
证据组合:
RED延迟与错误
→ 版本和Pod维度拆分
→ CPU Usage与Throttling
→ Trace定位慢Span
→ 线程池/连接池/GC指标
→ 必要时线程Dump或CPU Profile不要看到CPU 20%就断言“应用没有性能问题”。等待型瓶颈本来就可能不消耗CPU。
十八、场景六:Pod Ready但Service访问503
按链路检查:
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分层判断:
- Service Selector是否选中目标Pod。
- EndpointSlice是否有地址且Condition Ready。
- Service
targetPort是否对应容器真实监听端口/端口名。 - 应用是否只监听
127.0.0.1而非Pod接口。 - NetworkPolicy是否阻断来源。
- Ingress Backend的Service名和端口是否正确。
- Ingress Controller是否读取到配置并能连接Endpoint。
- 503由Ingress、Service Mesh还是应用返回,查看响应Header和对应日志。
“Pod Ready”只证明它通过了自己配置的 Readiness。如果探针只返回固定200,没有验证关键初始化,Ready本身也可能是假阳性。
十九、场景七:Node NotReady
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%可能是正常突发。告警需要窗口、持续时间、比例和影响范围。例如:
5分钟错误预算快速燃烧
且请求量高于最低阈值
且至少两个实例或一个完整版本受影响具体阈值来自 SLO、压测和历史基线,不应复制固定数字到所有服务。
20.3 告警必须带上下文
至少包含:
- 集群、Namespace、服务、环境。
- 当前版本和最近发布链接。
- 指标当前值、阈值和持续时间。
- 受影响接口/区域。
- Dashboard、日志、Trace查询链接。
- Runbook和负责人。
20.4 告警降噪
同一Node故障可能触发几十个Pod告警。通过拓扑关联、告警分组和抑制把根因告警置顶,但不能完全隐藏用户影响告警。
二十一、商业Demo:可观测的Spring Boot服务
下面只展示 K8s 侧约束;应用需要实现结构化日志、Actuator/Micrometer指标和 Trace Context。
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 和日志需要脱敏。
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>最终事故结论使用:
现象:用户看到什么
开始时间:精确时间和时区
影响范围:接口、区域、租户、版本
变更:发布、配置、基础设施变化
证据:指标、日志、Trace、Event、状态
根因:什么机制在什么条件下失败
为什么未被提前发现:测试/告警/发布闸门缺口
止损:回滚、切流、扩容、限流
永久修复:代码、配置、容量、平台治理
验证:哪些SLI和业务指标恢复,观察多久
预防:自动化测试、Runbook、告警和演练二十三、常见错误做法
- 告警后先删除所有异常Pod,导致上次日志和现场丢失。
- 只看
kubectl top当前值,推断半小时前资源正常。 - 只按 Pod 名查询日志,滚动更新后无法按版本聚合。
- 将 orderId、userId 放进 Prometheus Label,造成高基数。
- 将健康检查写成固定返回200,产生Ready假阳性。
- 看到137就直接判定Java Heap泄漏。
- 看到CPU不高就排除线程池、锁、IO和Throttle。
- 只看应用日志,不看Event、Condition和EndpointSlice。
- 用公网未固定Tag的调试镜像进入生产Pod。
- 告警只写“服务异常”,没有集群、版本、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与状态补充调度、镜像、探针和资源证据。它们相互验证,没有一种信号能独立覆盖全部根因。
二十五、学习实验与验收
实验一:保留重启前日志
让测试容器第一次启动退出、第二次保持运行,分别比较:
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不高”和“容器不被限速”不是一回事。
实验四:建立完整证据链
选择一个失败发布,输出:
- 业务SLI变化截图/查询。
- Deployment Revision和镜像Digest。
- 异常Pod Condition与Event。
- 当前/previous日志。
- Trace慢Span或错误Span。
- EndpointSlice和Ingress链路。
- 根因、止损、修复与验证指标。
验收清单
- 能解释六类信号各自能证明和不能证明什么。
- 能解释Metrics Server、Prometheus和kube-state-metrics区别。
- 能找到多容器、Init Container和previous日志。
- 能从ContainerStatus提取ExitCode、Reason和Restart Count。
- 能根据Event把故障路由到Scheduler、CRI、CNI或CSI。
- 能解释CPU Throttle与节点总体CPU低并存。
- 能解释OOMKilled与Java Heap OOM区别。
- 能从Ready Pod追踪到EndpointSlice、Service和Ingress。
- 能设计包含版本、TraceId且不泄密的结构化日志。
- 能写出带上下文、Runbook和恢复验证的告警。
