凌晨3点的K8s集群雪崩:从CrashLoopBackOff到CNI IP池耗尽的完整复盘
事故概览
时间线 :2025-01-15 03:17 AM
影响范围 :生产集群 prod-bj-01,1247个Pod进入CrashLoopBackOff,核心业务API可用性跌至12%
MTTR :2小时43分钟
根因:Liveness探针配置不当 + CNI IP地址池耗尽 + 内存泄漏三重暴击
这是我职业生涯中见过的最诡异的级联故障。表面看是探针配置问题,实际是底层网络资源枯竭引发的连锁反应。
排查阶段1:快速定位异常Pod
基础排查命令清单
bash
# 1. 全局扫描CrashLoopBackOff状态的Pod
kubectl get pods --all-namespaces --field-selector=status.phase!=Running,status.phase!=Succeeded
# 2. 按重启次数倒序排列(重启次数高的往往是根因)
kubectl get pods -A --sort-by='.status.containerStatuses[0].restartCount' | tail -20
# 3. 查看具体Pod的Events(关键信息都在这)
kubectl describe pod <pod-name> -n <namespace> | grep -A 20 Events
# 4. 检查容器退出码
kubectl get pod <pod-name> -n <namespace> -o jsonpath='{.status.containerStatuses[0].lastState.terminated.exitCode}'
# 5. 查看容器日志(包括上一次崩溃的日志)
kubectl logs <pod-name> -n <namespace> --previous
# 6. 检查节点资源压力
kubectl top nodes
kubectl describe node <node-name> | grep -A 10 "Allocated resources"
# 7. 检查CNI网络插件状态(Calico为例)
calicoctl node status
calicoctl ipam show --show-blocks
# 8. 检查iptables规则数量(超过10万条会严重影响性能)
iptables-save | wc -l
# 9. 查看系统内核日志中的OOM记录
dmesg -T | grep -i "out of memory"
journalctl -k | grep "oom-kill"
# 10. 检查Pod的资源限制与实际使用
kubectl top pod <pod-name> -n <namespace> --containers
排查阶段2:发现Liveness探针的致命配置
问题YAML(事故现场)
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-gateway
namespace: production
spec:
replicas: 50
template:
spec:
containers:
- name: gateway
image: registry.internal/api-gateway:v2.3.1
resources:
requests:
memory: "256Mi"
cpu: "200m"
limits:
memory: "512Mi" # ⚠️ 内存限制过低
cpu: "1000m"
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 10 # ⚠️ 启动时间不足
periodSeconds: 5 # ⚠️ 探测频率过高
timeoutSeconds: 2 # ⚠️ 超时时间过短
failureThreshold: 2 # ⚠️ 容错次数过低
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 3
问题分析:
- initialDelaySeconds=10秒:应用实际启动需要25-30秒(包括数据库连接池初始化、配置中心拉取),探针过早开始检测
- failureThreshold=2:只允许失败2次就重启,在网络抖动时极易误杀
- periodSeconds=5 + timeoutSeconds=2:每5秒探测一次,2秒超时,在高负载下健康检查接口响应变慢时必然触发重启
修复后的YAML
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-gateway
namespace: production
spec:
replicas: 50
template:
spec:
containers:
- name: gateway
image: registry.internal/api-gateway:v2.3.1
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "1Gi" # ✅ 内存限制提升至1GB
cpu: "2000m"
livenessProbe:
httpGet:
path: /healthz
port: 8080
scheme: HTTP
initialDelaySeconds: 45 # ✅ 给足启动时间
periodSeconds: 10 # ✅ 降低探测频率
timeoutSeconds: 5 # ✅ 增加超时容忍度
failureThreshold: 5 # ✅ 允许5次失败
successThreshold: 1
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 30
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
startupProbe: # ✅ 新增启动探针
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 0
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 30 # 最多等待150秒启动
关键改进:
- 引入
startupProbe:专门处理慢启动场景,避免Liveness探针在启动阶段误杀 - Liveness探针的
failureThreshold从2提升到5:容忍短暂的网络抖动 - 内存限制从512Mi提升到1Gi:解决OOMKilled问题
排查阶段3:OOMKilled的内存泄漏追踪
确认OOM事件
bash
# 查看Pod被OOM Kill的记录
kubectl get events --all-namespaces --field-selector reason=OOMKilling
# 输出示例:
# NAMESPACE LAST SEEN TYPE REASON OBJECT MESSAGE
# production 2m ago Warning OOMKilling pod/api-gateway-7d8f9c Memory cgroup out of memory: Killed process 1247
内存泄漏定位
bash
# 1. 进入存活的Pod查看内存使用
kubectl exec -it <pod-name> -n production -- sh
# 2. 使用pmap查看进程内存映射
pmap -x 1 | tail -20
# 3. 如果是Java应用,dump堆内存
kubectl exec <pod-name> -n production -- jmap -dump:format=b,file=/tmp/heap.hprof 1
# 4. 使用MAT或JProfiler分析heap.hprof文件
# 发现:Jedis连接池未正确释放,导致每次请求泄漏约2MB内存
代码层面修复(Java示例):
java
// 问题代码
public class RedisClient {
public String get(String key) {
Jedis jedis = jedisPool.getResource();
return jedis.get(key); // ⚠️ 未释放连接
}
}
// 修复后
public class RedisClient {
public String get(String key) {
try (Jedis jedis = jedisPool.getResource()) { // ✅ try-with-resources自动释放
return jedis.get(key);
}
}
}
排查阶段4:CNI网络插件IP池耗尽
发现IP地址耗尽
bash
# 检查Calico IP池使用情况
calicoctl ipam show --show-blocks
# 输出示例:
# +----------+-------------------+-----------+------------+--------------+
# | GROUPING | CIDR | IPS TOTAL | IPS IN USE | IPS FREE |
# +----------+-------------------+-----------+------------+--------------+
# | IP Pool | 10.244.0.0/16 | 65536 | 65521 | 15 (0.02%) | # ⚠️ 几乎耗尽
# +----------+-------------------+-----------+------------+--------------+
# 查看哪些节点占用了大量IP
calicoctl ipam show --show-blocks | grep -A 5 "10.244"
# 检查是否有IP泄漏(已删除的Pod未释放IP)
kubectl get pods -A -o wide | wc -l # 实际运行的Pod数量
calicoctl ipam show | grep "IPS IN USE" # IPAM记录的使用数量
# 如果两者差距大于1000,说明存在IP泄漏
根因分析
- Pod频繁重启:Liveness探针配置不当导致Pod每分钟重启数十次
- IP回收延迟:Calico的IP垃圾回收(GC)默认间隔5分钟,在高频重启场景下跟不上消耗速度
- IP池规划不足 :
10.244.0.0/16只有65536个IP,集群有120个节点,每节点最多500个Pod,理论峰值需要60000个IP
紧急扩容IP池
bash
# 1. 查看当前IP池配置
calicoctl get ippool -o yaml > ippool-backup.yaml
# 2. 创建新的IP池(不能直接修改现有池的CIDR)
cat <<EOF | calicoctl apply -f -
apiVersion: projectcalico.org/v3
kind: IPPool
metadata:
name: new-ipv4-pool
spec:
cidr: 10.245.0.0/16 # 新增一个/16网段
ipipMode: Always
natOutgoing: true
blockSize: 26 # 每个节点分配/26子网(64个IP)
EOF
# 3. 禁用旧IP池(不删除,避免现有Pod断网)
calicoctl patch ippool default-ipv4-ippool -p '{"spec":{"disabled":true}}'
# 4. 重启所有Pod以使用新IP池(分批进行)
kubectl rollout restart deployment -n production
长期优化:调整IP回收策略
yaml
# 修改Calico配置,加快IP回收
apiVersion: v1
kind: ConfigMap
metadata:
name: calico-config
namespace: kube-system
data:
cni_network_config: |-
{
"name": "k8s-pod-network",
"cniVersion": "0.3.1",
"plugins": [
{
"type": "calico",
"ipam": {
"type": "calico-ipam",
"assign_ipv4": "true",
"assign_ipv6": "false"
},
"policy": {
"type": "k8s"
},
"kubernetes": {
"kubeconfig": "/etc/cni/net.d/calico-kubeconfig"
}
}
]
}
# 调整IP垃圾回收间隔(默认5分钟,改为1分钟)
typha_service_name: "calico-typha"
veth_mtu: "1440"
ipam_gc_interval: "1m" # ✅ 关键参数
排查阶段5:iptables规则爆炸
检查iptables规则数量
bash
# 统计iptables规则总数
iptables-save | wc -l
# 输出:127834 # ⚠️ 超过10万条,严重影响网络性能
# 查看Kubernetes相关的规则
iptables-save | grep KUBE | wc -l
# 输出:98234
# 分析规则增长原因
iptables-save | grep KUBE-SVC | head -20
问题:每个Service会创建大量iptables规则,在Service数量达到5000+时,规则数量呈指数增长。
切换到IPVS模式
bash
# 1. 修改kube-proxy配置
kubectl edit configmap kube-proxy -n kube-system
# 修改mode字段
apiVersion: v1
kind: ConfigMap
metadata:
name: kube-proxy
namespace: kube-system
data:
config.conf: |-
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
mode: "ipvs" # ✅ 从iptables切换到IPVS
ipvs:
scheduler: "rr" # 轮询调度
minSyncPeriod: 5s
syncPeriod: 30s
# 2. 重启kube-proxy
kubectl rollout restart daemonset kube-proxy -n kube-system
# 3. 验证IPVS规则
ipvsadm -Ln | head -20
性能对比:
| 指标 | iptables模式 | IPVS模式 |
|---|---|---|
| 规则数量 | 127834条 | 5234条 |
| Service查找时间 | O(n) | O(1) |
| 网络延迟P99 | 45ms | 8ms |
完整排障Checklist
Level 1:Pod层面
-
kubectl describe pod查看Events中的错误信息 - 检查容器退出码(137=OOMKilled, 143=SIGTERM, 1=应用错误)
- 查看当前和上一次的容器日志
- 验证镜像是否存在且可拉取
- 检查资源requests/limits配置是否合理
- 确认Liveness/Readiness探针配置(重点关注initialDelaySeconds和failureThreshold)
Level 2:节点层面
-
kubectl top nodes检查节点资源使用率 -
kubectl describe node查看节点Conditions(DiskPressure, MemoryPressure, PIDPressure) - 检查节点上的kubelet日志:
journalctl -u kubelet -f - 验证节点上的容器运行时状态:
systemctl status containerd - 检查节点磁盘空间:
df -h
Level 3:网络层面
- 检查CNI插件状态(Calico/Flannel/Cilium)
- 验证IP地址池使用情况:
calicoctl ipam show - 检查iptables规则数量:
iptables-save | wc -l - 测试Pod间网络连通性:
kubectl exec -it <pod> -- ping <target-pod-ip> - 检查DNS解析:
kubectl exec -it <pod> -- nslookup kubernetes.default - 验证Service的Endpoints:
kubectl get endpoints <service-name>
Level 4:集群层面
- 检查API Server响应时间:
kubectl get --raw /healthz -v=8 - 查看etcd健康状态:
etcdctl endpoint health - 检查Controller Manager和Scheduler日志
- 验证集群证书有效期:
kubeadm certs check-expiration - 检查集群事件:
kubectl get events --all-namespaces --sort-by='.lastTimestamp'
事后总结
技术债务清单
- 监控盲区:缺少CNI IP池使用率告警,导致IP耗尽时无法提前预警
- 探针配置模板:团队缺少统一的Liveness/Readiness探针配置规范
- 资源规划:未建立Pod内存使用基线,limits配置凭经验拍脑袋
- 网络架构:iptables模式在大规模集群下性能瓶颈明显
改进措施
yaml
# 新增Prometheus告警规则
groups:
- name: k8s-network
rules:
- alert: CalicoIPPoolExhaustion
expr: (calico_ipam_allocations_in_use / calico_ipam_allocations) > 0.85
for: 5m
labels:
severity: critical
annotations:
summary: "Calico IP池使用率超过85%"
- alert: HighPodRestartRate
expr: rate(kube_pod_container_status_restarts_total[15m]) > 0.1
for: 10m
labels:
severity: warning
annotations:
summary: "Pod重启频率异常"
最终数据
- 故障时长:2小时43分钟
- 影响Pod数量:1247个
- 根因数量:3个(探针配置 + 内存泄漏 + IP耗尽)
- 修复后稳定性:连续运行72小时无重启,P99延迟从45ms降至8ms
凌晨3点的报警是最好的老师。这次事故让我们深刻理解了Kubernetes网络栈的每一层细节,从应用层的探针配置,到传输层的iptables规则,再到网络层的IP地址管理。生产环境没有侥幸,只有对底层原理的敬畏和对监控数据的信仰。