凌晨 3 点的报警:K8s 集...

凌晨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

问题分析

  1. initialDelaySeconds=10秒:应用实际启动需要25-30秒(包括数据库连接池初始化、配置中心拉取),探针过早开始检测
  2. failureThreshold=2:只允许失败2次就重启,在网络抖动时极易误杀
  3. 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泄漏

根因分析

  1. Pod频繁重启:Liveness探针配置不当导致Pod每分钟重启数十次
  2. IP回收延迟:Calico的IP垃圾回收(GC)默认间隔5分钟,在高频重启场景下跟不上消耗速度
  3. 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'

事后总结

技术债务清单

  1. 监控盲区:缺少CNI IP池使用率告警,导致IP耗尽时无法提前预警
  2. 探针配置模板:团队缺少统一的Liveness/Readiness探针配置规范
  3. 资源规划:未建立Pod内存使用基线,limits配置凭经验拍脑袋
  4. 网络架构: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地址管理。生产环境没有侥幸,只有对底层原理的敬畏和对监控数据的信仰。

相关推荐
petrel20152 小时前
【Spark】性能与联通性的终极博弈:Spark on K8s 主机网络改造深度实战
大数据·网络·spark·kubernetes·claude code
切糕师学AI2 小时前
Kubernetes CRD(自定义资源,CustomResourceDefinition)详解
云原生·容器·kubernetes
一个向上的运维者3 小时前
从 K8s Device Plugin 到 Volcano 多元算力管理:GPU 显存共享实战与深度解析
云原生·容器·kubernetes
merlin-mm3 小时前
云平台构建 RDMA高性能网络
网络·云原生·容器·kubernetes
极客on之路3 小时前
docker 和 Kubernetes(K8s) 总结
docker·容器·kubernetes
罗技1235 小时前
在 Kubernetes 上用 Fluent Bit 收集 Nginx 日志到 Easysearch
nginx·kubernetes·jenkins
丈剑走天涯12 小时前
kubernetes Jenkins 二进制安装指南
java·kubernetes·jenkins
roman_日积跬步-终至千里13 小时前
【k8s 实战】使用 Helm 在 Minikube 部署 StarRocks(实战避坑指南)
云原生·容器·kubernetes
IT枫斗者13 小时前
CentOS 7 一键部署 K8s 1.23 + Rancher 2.7 完整指南
java·linux·spring boot·后端·kubernetes·centos·rancher