1. 线上节点CPU爆满排查实录
上周五凌晨三点,监控系统突然报警:生产环境某Kubernetes节点CPU使用率突破95%阈值并持续攀升。作为当值SRE,我经历了从告警触发到根因定位的全过程。这种"节点卡死"的故障在分布式系统中极具代表性,今天就把完整的排查思路和避坑经验分享给大家。
这种故障通常呈现两个典型特征:一是节点响应缓慢,kubectl exec进入容器需要10秒以上;二是监控图表显示CPU利用率曲线呈"直墙式"上升。我们集群运行着300+微服务,故障节点承载了32个Pod,其中包含核心订单服务。下面分四个阶段还原整个排查过程。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 应急响应与初步诊断
2.1 告警触发与现象确认
当Prometheus的node_exporter采集到CPU使用率持续5分钟>95%时,触发了企业微信告警。通过以下命令快速确认节点状态:
bash复制kubectl describe node故障节点名 | grep -A 10 "Allocated resources"
输出显示CPU requests已达节点容量的380%,但实际使用量只有60%。这种requests与实际使用量严重不匹配的情况,往往是后续问题的前兆。
关键提示:不要盲目执行kubectl drain!先保留现场取证,我们曾因过早排水丢失过关键线索。
2.2 快速隔离方案
采用"三重隔离法"控制影响范围:
- 通过kubectl cordon标记节点不可调度
- 对非核心Pod执行graceful termination(含30秒优雅退出等待)
- 对核心服务Pod设置反亲和性避免集中部署
同时收集以下关键数据:
bash复制# 系统级快照
kubectl get --raw /api/v1/nodes/故障节点名/proxy/stats/summary > node_stats.json
# 进程级快照
kubectl debug node/故障节点名 -it --image=nicolaka/netshoot -- ps aux --sort=-%cpu
3. 深度根因分析
3.1 进程树分析
通过上述debug容器获取的进程信息显示,某个Java进程持续占用400% CPU(4核满载)。进一步用jstack抓取线程栈:
bash复制kubectl exec 问题Pod名 -- jstack -l 1 > thread_dump.log
分析发现大量"GC task thread#0 (ParallelGC)"线程处于RUNNABLE状态,结合jstat输出确认是Full GC持续运行:
bash复制kubectl exec 问题Pod名 -- jstat -gcutil 1 1000 10
输出中O(老年代)使用率持续100%,FGC次数每分钟达15次(正常应<1次)。
3.2 内存泄漏验证
通过对比Pod的memory.usage_in_bytes和memory.limit_in_bytes:
bash复制kubectl exec 问题Pod名 -- cat /sys/fs/cgroup/memory/memory.usage_in_bytes
发现容器内存使用量是申请值的3倍,但未触发OOM Killer。这是因为Kubernetes的memory limit默认只控制oom_score_adj,并不强制限制内存分配。
3.3 核心问题定位
最终在应用日志中发现规律性报错:
code复制[OrderCache] Failed to evict LRU items - Cache size 2GB exceeds max 1GB
结合HeapDump分析,确认是本地缓存实现存在逻辑缺陷:当缓存达到上限时,清理线程因锁竞争无法正常执行,反而触发持续Full GC。
4. 解决方案与优化措施
4.1 紧急回滚
采用两阶段回滚策略:
- 先将Pod的resources.limits.memory从4Gi调整为8Gi(临时扩容)
- 回滚到上一个稳定版本的Deployment
4.2 长期修复方案
- 改用Caffeine缓存并配置软引用:
java复制Cache<String, Order> cache = Caffeine.newBuilder()
.softValues()
.maximumSize(10_000)
.build();
- 增加GC监控策略:
yaml复制# Prometheus配置
- pattern: 'java.lang<type=GarbageCollector, name=(.*)><>(CollectionCount|CollectionTime)'
name: 'jvm_gc_$1_$2'
- 调整Pod QoS策略:
yaml复制resources:
requests:
memory: "3Gi"
cpu: "1"
limits:
memory: "4Gi"
cpu: "2"
5. 典型问题排查手册
5.1 CPU爆满快速诊断流程
| 步骤 | 命令/操作 | 预期结果 |
|---|---|---|
| 1. 确认节点状态 | kubectl top node |
定位具体问题节点 |
| 2. 检查进程负载 | kubectl debug node/节点名 |
识别异常进程PID |
| 3. 分析线程栈 | jstack -l <PID> |
发现阻塞线程 |
| 4. 检查GC状态 | jstat -gcutil <PID> |
确认GC是否频繁 |
5.2 内存问题排查技巧
- 当
kubectl describe pod显示OOMKilled时:- 优先检查
memory.limit_in_bytes与实际使用量的关系 - 用
kubectl logs --previous获取被kill容器的日志
- 优先检查
- 未触发OOM但频繁GC时:
- 使用
jmap -histo:live <PID>观察对象分布 - 检查
Runtime.getRuntime().maxMemory()是否合理
- 使用
6. 预防性监控建议
6.1 关键监控指标配置
yaml复制# Prometheus告警规则示例
- alert: HighJavaGC
expr: rate(jvm_gc_PS_Scavenge_CollectionCount[5m]) > 5
for: 10m
labels:
severity: warning
annotations:
summary: "高频GC告警 ({{ $labels.pod }})"
- alert: ContainerMemoryNearLimit
expr: (container_memory_working_set_bytes / container_spec_memory_limit_bytes) > 0.8
for: 5m
labels:
severity: critical
6.2 节点健康检查策略
- 配置kubelet的--eviction-hard参数:
code复制--eviction-hard=memory.available<1Gi,nodefs.available<10%
- 定期执行压力测试:
bash复制kubectl run stress-test --image=progrium/stress -- --cpu 4 --vm 2 --vm-bytes 2G
这次故障给我们的核心教训是:内存压力最终会以CPU问题的形式表现。在K8s环境下,不仅要关注容器limit,更要监控JVM堆外内存和GC行为。我们后来在所有Java应用Pod中添加了sidecar容器,专门采集JVM指标,这类问题再未重现。
