记一次K8S开发环境容器组频繁重启问题(节点故障排查处理手册)
适用场景:Pod 频繁重启、CrashLoopBackOff、SandboxChanged、节点网络异常
环境:Kubernetes v1.28 / Flannel VXLAN / containerd / Ubuntu 22.04
整理日期:2026-03-08
目录
1. 故障概述
现象
| Pod | 命名空间 | 异常状态 | 重启次数 |
|---|---|---|---|
mysql-78f4d65fbf-hlc65 |
develop | Running 但持续重启 | 763+ 次(3天内) |
phpmyadmin-674c6ddbc9-7h422 |
develop | CrashLoopBackOff | 524+ 次(2天内) |
kube-proxy-qvm7z |
kube-system | CrashLoopBackOff | 9918 次(12天) |
kube-flannel-ds-hwcx4 |
kube-flannel | 持续重启 | Sandbox Attempt 9065 次 |
受影响节点
- node003(192.168.8.203):节点上共 36 个 Pod 全部受到影响
2. 根因分析
根因一:iptables 后端不兼容(主因)
- 现象:kube-proxy Exit Code 2,启动后约 60 秒必然崩溃
- 原因:Ubuntu 22.04 默认使用
iptables v1.8.7 (nf_tables)后端,而 kube-proxy v1.28 依赖iptables-legacy后端,两者规则体系冲突导致 kube-proxy 操作 iptables 规则时直接报错退出
# 故障时的版本
iptables v1.8.7 (nf_tables) ← nft 后端,与 kube-proxy 不兼容
kube-proxy v1.28.14 ← 需要 legacy 后端
根因二:kube-proxy 崩溃引发 flannel 连锁重启
- kube-proxy 每次崩溃 → kubelet 重建 Pod Sandbox → 触发 flannel DaemonSet 检测到网络变更
- flannel 重启时,
install-cniinit 容器会先删除再写入/etc/cni/net.d/10-flannel.conflist - 删除的瞬间 containerd 检测到 CNI 配置消失,触发所有 Pod 网络沙箱强制重建
# containerd 关键错误日志
failed to reload cni configuration after receiving fs change event(REMOVE "/etc/cni/net.d/10-flannel.conflist")
cni plugin not initialized: failed to load cni config
根因三:kube-proxy configmap clusterCIDR 为空(次要)
clusterCIDR: ""导致 kube-proxy 无法正确识别集群网段,加剧了网络规则同步失败
故障影响链
kube-proxy (nft 不兼容) → exit code 2 → 频繁重启 (9918次)
↓
flannel 连锁重启 (Attempt 9065)
↓
flannel init容器 REMOVE /etc/cni/net.d/10-flannel.conflist
↓
containerd: "cni plugin not initialized" → TearDown 所有 Pod Sandbox
↓
node003 全部 Pod 收到 SIGTERM → exit 0 → K8s 因 restartPolicy:Always 重启
↓
MySQL (763次重启) + phpMyAdmin CrashLoopBackOff (级联故障)
3. 故障链路图
┌─────────────────────────────────────────────────────────┐
│ node003 │
│ │
│ iptables (nf_tables) │
│ │ 不兼容 │
│ ▼ │
│ kube-proxy ──Exit Code 2──► 9918次重启 │
│ │ 触发Sandbox重建 │
│ ▼ │
│ kube-flannel ──重启──► init容器 REMOVE CNI conflist │
│ │ │
│ ▼ │
│ containerd ──"cni not initialized"──► TearDown Sandbox │
│ │ │
│ ▼ │
│ 所有Pod SIGTERM (exit 0) ──restartPolicy:Always──► 重启 │
│ │ │
│ ├──► MySQL 763次重启 │
│ └──► phpMyAdmin CrashLoopBackOff (级联) │
└─────────────────────────────────────────────────────────┘
4. 排查步骤
Step 1:确认 Pod 异常状态
# 查看命名空间 Pod 状态
kubectl get pods -n develop
# 查看 Pod 详情(重点看 Exit Code、Reason、Events)
kubectl describe pod <pod-name> -n <namespace>
# 查看当前日志
kubectl logs <pod-name> -n <namespace> --tail=100
# 查看上一次崩溃日志(关键!)
kubectl logs <pod-name> -n <namespace> --previous --tail=100
关键判断指标:
| Exit Code | 含义 |
|---|---|
| 0 | 正常退出(被外部 SIGTERM 杀死,非崩溃) |
| 1 | 应用内部错误 |
| 2 | 系统调用失败(iptables 操作失败等) |
| 137 | OOM Kill 或 SIGKILL(128+9) |
| 143 | SIGTERM 优雅退出(128+15) |
Step 2:判断是节点问题还是应用问题
# 查看 Pod 所在节点
kubectl get pod <pod-name> -n <namespace> -o wide
# 查看节点状态和事件
kubectl describe node <node-name>
# 查看节点上所有 Pod
kubectl get pods --all-namespaces -o wide | grep <node-name>
# 查看节点资源使用
kubectl top node <node-name>
kubectl top pods --all-namespaces --sort-by=memory | grep <node-name>
节点问题信号(本次案例):
SandboxChanged事件大量出现- 同一节点多个 Pod 同时异常
- kube-system 组件(kube-proxy/flannel)也在重启
Step 3:排查 kube-system 组件
# 查看 kube-proxy 状态(重点节点)
kubectl get pods -n kube-system -o wide | grep <node-name>
# 查看 flannel 状态
kubectl get pods -n kube-flannel -o wide | grep <node-name>
# 查看 kube-proxy 日志
kubectl logs <kube-proxy-pod> -n kube-system --tail=50
kubectl logs <kube-proxy-pod> -n kube-system --previous --tail=50
# 查看 flannel 日志
kubectl logs <flannel-pod> -n kube-flannel --tail=50
Step 4:登录节点深查
# SSH 登录故障节点
ssh root@<node-ip>
# 检查 iptables 后端版本(核心!)
iptables --version
update-alternatives --list iptables
# 检查内核模块
lsmod | grep -E "nf_conntrack|ip_tables"
# 检查 containerd CNI 错误
journalctl -u containerd --since "1 hour ago" | grep -E "error|cni|sandbox" | tail -30
# 检查 kubelet 错误
journalctl -u kubelet --since "1 hour ago" | grep -E "sandbox|network|PLEG|error" | tail -30
# 检查 OOM 记录
dmesg | grep -E "oom_kill|Out of memory|Killed process" | tail -20
# 检查内存压力
free -h
Step 5:检查 kube-proxy configmap
kubectl get configmap kube-proxy -n kube-system -o yaml | grep -A5 "clusterCIDR\|mode"
# 获取集群实际 CIDR
kubectl cluster-info dump | grep -m1 "cluster-cidr"
kubectl get node -o jsonpath='{.items[0].spec.podCIDR}'
5. 修复方案
修复一:切换 iptables 为 legacy 模式(在故障节点执行)
⚠️ 这是本次故障的根本修复,必须首先执行
# 在 node003 上执行
update-alternatives --set iptables /usr/sbin/iptables-legacy
update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy
# 验证切换成功
iptables --version
# 期望输出: iptables v1.8.7 (legacy)
# 清理 nft 残留规则,避免规则冲突
nft flush ruleset 2>/dev/null || true
持久化配置(防止节点重启后回退):
cat > /etc/systemd/system/iptables-legacy.service << 'EOF'
[Unit]
Description=Set iptables to legacy mode
Before=kubelet.service containerd.service
After=network.target
[Service]
Type=oneshot
ExecStart=/usr/sbin/update-alternatives --set iptables /usr/sbin/iptables-legacy
ExecStart=/usr/sbin/update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now iptables-legacy.service
修复二:修复 kube-proxy clusterCIDR 配置(在 master 执行)
# 编辑 configmap
kubectl edit configmap kube-proxy -n kube-system
找到并修改:
# 修改前
clusterCIDR: ""
# 修改后(替换为实际集群 CIDR)
clusterCIDR: "10.244.0.0/16"
# 重建 kube-proxy Pod 使配置生效
kubectl delete pod <kube-proxy-pod-name> -n kube-system
# 观察新 Pod 状态
kubectl get pod -n kube-system -l k8s-app=kube-proxy -o wide -w
修复三:迁移业务 Pod 离开故障节点(临时止血)
在根因未修复前,先将关键服务迁移到其他节点
# 对需要保护的 Deployment 设置节点反亲和
for deploy in mysql phpmyadmin; do
kubectl patch deployment $deploy -n develop -p '{
"spec":{"template":{"spec":{"affinity":{"nodeAffinity":{"requiredDuringSchedulingIgnoredDuringExecution":{"nodeSelectorTerms":[{"matchExpressions":[{"key":"kubernetes.io/hostname","operator":"NotIn","values":["node003"]}]}]}}}}}}}'
echo "✅ $deploy 已设置节点反亲和"
done
# 强制重新调度到其他节点
kubectl rollout restart deployment mysql phpmyadmin -n develop
# 观察迁移结果
kubectl get pods -n develop -o wide -w
修复执行顺序
优先级 1:[node003] 切换 iptables → iptables-legacy ← 根本修复,5分钟内见效
优先级 2:[master01] 迁移 mysql/phpmyadmin 离开 node003 ← 业务立即止血
优先级 3:[master01] 修复 kube-proxy configmap CIDR ← 顺带修复,提升稳定性
优先级 4:[node003] 持久化 iptables-legacy 配置 ← 防止重启后复发
6. 验证结果
验证 kube-proxy 修复
# 观察 kube-proxy 是否稳定运行(不再重启)
kubectl get pod -n kube-system -l k8s-app=kube-proxy -o wide -w
# 检查重启次数是否停止增长
kubectl get pod <kube-proxy-pod> -n kube-system
预期:STATUS 变为 Running,RESTARTS 停止增长
验证 flannel 修复
kubectl get pod -n kube-flannel -o wide
# 确认 CNI 配置文件存在且稳定
# 在 node003 上执行
ls -la /etc/cni/net.d/
cat /etc/cni/net.d/10-flannel.conflist
验证 MySQL 和 phpMyAdmin
# 确认已调度到其他节点
kubectl get pods -n develop -o wide
# 确认不再重启
kubectl get pod -n develop -w
预期最终状态
| 组件 | 修复前 | 修复后 |
|---|---|---|
| kube-proxy | CrashLoopBackOff(9918次) | Running 稳定 |
| kube-flannel | 持续重启(9065次) | Running 稳定 |
| MySQL | 重启 764次,每4分钟1次 | 迁移到新节点,稳定运行 |
| phpMyAdmin | CrashLoopBackOff | 随 MySQL 稳定后恢复 |
| node003 全部 Pod | 频繁被 SIGTERM 重启 | 不再异常重启 |
7. 预防措施
7.1 新节点加入时标准化 iptables 配置
在集群初始化 playbook 或节点 cloud-init 中加入:
# 所有节点统一使用 iptables-legacy
update-alternatives --set iptables /usr/sbin/iptables-legacy
update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy
7.2 kube-proxy DaemonSet 健康检查
# 定期巡检 kube-system 组件
kubectl get pods -n kube-system -o wide
kubectl get pods -n kube-flannel -o wide
7.3 配置 kube-proxy configmap(集群初始化时)
clusterCIDR: "10.244.0.0/16" # 与 --cluster-cidr 保持一致
mode: "iptables" # 明确指定,不依赖自动检测
7.4 关键业务 Pod 设置节点反亲和
对数据库等有状态服务,主动避开已知不稳定节点:
spec:
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
preference:
matchExpressions:
- key: node-role.kubernetes.io/worker
operator: Exists
7.5 配置告警规则
建议在监控系统(Prometheus/AlertManager)中增加以下告警:
# Pod 重启次数告警
- alert: PodRestartingTooMuch
expr: rate(kube_pod_container_status_restarts_total[15m]) > 0.1
for: 5m
annotations:
summary: "Pod {{ $labels.pod }} 重启频率异常"
# kube-system 组件异常告警
- alert: KubeSystemPodNotRunning
expr: kube_pod_status_phase{namespace="kube-system", phase!="Running"} == 1
for: 2m
annotations:
summary: "kube-system 关键组件 {{ $labels.pod }} 异常"
8. 常用排查命令速查
Pod 状态排查
# 查看所有异常 Pod
kubectl get pods --all-namespaces | grep -v Running | grep -v Completed
# 查看 Pod 详情(事件最重要)
kubectl describe pod <pod> -n <ns>
# 查看上一次崩溃日志
kubectl logs <pod> -n <ns> --previous --tail=100
# 查看特定节点上的所有 Pod
kubectl get pods --all-namespaces -o wide | grep <node-name>
节点排查
# 节点状态总览
kubectl get nodes -o wide
# 节点详情(含资源使用和事件)
kubectl describe node <node-name>
# 节点资源使用
kubectl top node
# Pod 资源使用(按内存排序)
kubectl top pods --all-namespaces --sort-by=memory
containerd / kubelet 日志
# containerd 错误日志
journalctl -u containerd --since "1 hour ago" | grep -E "error|Error" | tail -30
# kubelet 网络/Sandbox 相关日志
journalctl -u kubelet --since "1 hour ago" | grep -E "sandbox|network|PLEG" | tail -30
# 实时跟踪 kubelet 日志
journalctl -u kubelet -f
iptables 相关
# 查看 iptables 后端(nf_tables 与 kube-proxy 不兼容!)
iptables --version
# 查看可用后端
update-alternatives --list iptables
# 切换到 legacy(修复 kube-proxy 兼容性问题)
update-alternatives --set iptables /usr/sbin/iptables-legacy
update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy
# 清理 nft 残留规则
nft flush ruleset 2>/dev/null || true
CNI 相关
# 检查 CNI 配置文件
ls -la /etc/cni/net.d/
cat /etc/cni/net.d/10-flannel.conflist
# 检查 flannel 接口
ip link show flannel.1
ip route | grep flannel
kube-proxy 排查
# 查看 kube-proxy configmap
kubectl get configmap kube-proxy -n kube-system -o yaml
# 查看集群 CIDR
kubectl cluster-info dump | grep -m1 "cluster-cidr"
# 重建 kube-proxy
kubectl delete pod -n kube-system -l k8s-app=kube-proxy
快速迁移 Pod 离开故障节点
# 设置节点反亲和(排除故障节点)
kubectl patch deployment <deploy-name> -n <namespace> -p '{
"spec":{"template":{"spec":{"affinity":{"nodeAffinity":{"requiredDuringSchedulingIgnoredDuringExecution":{"nodeSelectorTerms":[{"matchExpressions":[{"key":"kubernetes.io/hostname","operator":"NotIn","values":["<node-name>"]}]}]}}}}}}}'
# 强制重新调度
kubectl rollout restart deployment <deploy-name> -n <namespace>
root@master01:~# kubectl get pods -n develop
NAME READY STATUS RESTARTS AGE
mysql-bc8bb88f8-kz2mt 1/1 Running 0 21m
phpmyadmin-764576cd8d-5d28q 1/1 Running 0 21m
附录:本次故障 Exit Code 解读
| 组件 | Exit Code | 原因 |
|---|---|---|
| kube-proxy | 2 | iptables nf_tables 后端操作失败 |
| MySQL | 0 | 收到 SIGTERM 后优雅退出(被 CNI 重建触发) |
| 部分容器 | 137 | 被 SIGKILL 强制终止(级联重启中的 kubelet 清理行为) |
核心经验:当多个 Pod 同时出现
Exit Code 0+SandboxChanged事件时,优先排查节点级网络组件(kube-proxy / CNI),而非应用本身。
本手册基于实际故障案例整理,适用于相同或相似环境下的 Kubernetes 节点网络故障排查。
作者:一毛
本博客所有文章仅用于学习、研究和交流目的,欢迎非商业性质转载。
不管遇到了什么烦心事,都不要自己为难自己;无论今天发生多么糟糕的事,都不应该感到悲伤。记住一句话:越努力,越幸运。

浙公网安备 33010602011771号