这篇文章记录了我这些年在 K8s 生产环境踩过的坑。每一个案例都是血泪教训,有些甚至导致了生产事故。希望通过分享这些经历,能帮助大家避免重蹈覆辙。
声明:以下案例均已脱敏处理,部分细节有所调整。
一、概述
1.1 背景介绍
K8s 已经成为容器编排的事实标准,但它的复杂性也带来了很多潜在的风险点。根据我的经验,生产环境出问题往往不是因为 K8s 本身有 bug,而是:
配置不当
资源规划不合理
缺乏监控告警
操作流程不规范
对某些机制理解不深
1.2 文章结构
本文将按照故障严重程度,从"集群级灾难"到"应用级问题",逐一复盘 10 个真实案例:
| 序号 | 故障类型 | 影响范围 | 严重程度 |
|---|---|---|---|
| 1 | etcd 磁盘写满 | 整个集群 | P0 - 灾难 |
| 2 | API Server OOM | 整个集群 | P0 - 灾难 |
| 3 | 证书过期 | 整个集群 | P0 - 灾难 |
| 4 | 节点批量 NotReady | 多节点 | P1 - 严重 |
| 5 | PDB 配置导致无法驱逐 | 应用级 | P1 - 严重 |
| 6 | 资源配额耗尽 | 命名空间 | P2 - 中等 |
| 7 | 滚动更新卡住 | 应用级 | P2 - 中等 |
| 8 | ConfigMap 热更新踩坑 | 应用级 | P2 - 中等 |
| 9 | HPA 抖动风暴 | 应用级 | P2 - 中等 |
| 10 | 镜像拉取失败雪崩 | 多应用 | P2 - 中等 |
1.3 环境信息
| 组件 | 版本 | 说明 |
|---|---|---|
| Kubernetes | 1.28 - 1.30 | 不同案例发生在不同版本 |
| etcd | 3.5.x | 集群数据存储 |
| 容器运行时 | containerd 1.7+ | 部分老集群还在用 Docker |
| 节点规模 | 50 - 500 节点 | 中大规模集群 |
二、集群级灾难案例
2.1 案例一:etcd 磁盘写满,集群瘫痪
故障现象
那是一个周五下午(没错,就是最经典的周五下午),监控突然开始疯狂告警:
[CRITICAL] API Server 响应超时 [CRITICAL] 多个节点状态变为 Unknown [CRITICAL] 新 Pod 无法调度
登录 master 节点一看,kubectl 命令卡住不动,整个集群基本瘫痪了。
根因分析
# 检查 etcd 状态 systemctl status etcd # 输出:etcd.service: Failed with result 'exit-code' # 查看 etcd 日志 journalctl -u etcd -n 100 # 关键错误: # "mvcc: database space exceeded" # "etcdserver: no space" # 检查磁盘使用 df -h /var/lib/etcd # 输出:100% 使用率
根本原因:etcd 数据目录所在磁盘写满了。
为什么会写满?排查发现是因为:
某个 Operator 存在 bug,疯狂创建 Event 对象
etcd 没有配置自动压缩(compaction)
磁盘容量规划不足,只给了 20GB
解决过程
# 1. 紧急扩容磁盘(如果是云环境) # 或者清理其他文件腾出空间 # 2. 手动压缩 etcd # 获取当前 revision ETCDCTL_API=3 etcdctl endpoint status --write-out=table # 压缩到指定 revision ETCDCTL_API=3 etcdctl compact# 3. 执行碎片整理 ETCDCTL_API=3 etcdctl defrag --endpoints=https://127.0.0.1:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt --cert=/etc/kubernetes/pki/etcd/server.crt --key=/etc/kubernetes/pki/etcd/server.key # 4. 清理过多的 Event kubectl delete events --all -A
预防措施
# 1. 配置 etcd 自动压缩(在 etcd 启动参数中) --auto-compaction-mode=periodic --auto-compaction-retention=1h # 2. 设置配额告警 --quota-backend-bytes=8589934592# 8GB # 3. 添加磁盘监控告警 # Prometheus 告警规则 -alert:EtcdDiskSpaceLow expr:(etcd_mvcc_db_total_size_in_bytes/etcd_server_quota_backend_bytes)>0.8 for:5m labels: severity:critical annotations: summary:"etcd 磁盘空间即将耗尽"
“
教训:etcd 是 K8s 的心脏,一定要重点监控。建议磁盘至少预留 50GB,并配置自动压缩。
2.2 案例二:API Server OOM,集群失联
故障现象
某天早上 9 点,业务高峰期,突然收到告警:
[CRITICAL] kube-apiserver 进程重启 [CRITICAL] 大量 Pod 状态 Unknown [WARNING] kubectl 命令响应缓慢
查看监控发现 API Server 内存使用率飙升到 100%,然后被 OOM Killer 干掉了。
根因分析
# 查看 API Server 日志 journalctl -u kube-apiserver -n 200 | grep -i "oom|memory|killed" # 查看系统日志 dmesg | grep -i "out of memory" # 输出:Out of memory: Killed process 12345 (kube-apiserver) # 检查 API Server 内存配置 ps aux | grep kube-apiserver # 发现没有设置内存限制
根本原因:有人写了一个脚本,用 kubectl get pods -A -o json 获取所有 Pod 的完整信息,而且是每 10 秒执行一次。集群有 5000+ Pod,每次请求返回的 JSON 数据量巨大,API Server 内存被撑爆了。
更坑的是,这个脚本还是在多个节点上并行运行的...
解决过程
# 1. 找出问题请求
# 开启 API Server 审计日志
# 在 kube-apiserver 配置中添加:
--audit-log-path=/var/log/kubernetes/audit.log
--audit-policy-file=/etc/kubernetes/audit-policy.yaml
# 2. 分析审计日志,找出大请求
cat /var/log/kubernetes/audit.log | jq 'select(.responseStatus.code == 200) | {user: .user.username, verb: .verb, resource: .objectRef.resource}' | sort | uniq -c | sort -rn | head -20
# 3. 限制 API Server 资源
# 对于 kubeadm 部署的集群,修改 /etc/kubernetes/manifests/kube-apiserver.yaml
resources:
requests:
cpu: "500m"
memory: "1Gi"
limits:
cpu: "2"
memory: "4Gi"# 根据实际情况调整
# 4. 配置请求限流
--max-requests-inflight=400
--max-mutating-requests-inflight=200
预防措施
# 1. API 优先级和公平性配置(APF) apiVersion:flowcontrol.apiserver.k8s.io/v1beta3 kind:FlowSchema metadata: name:restrict-list-all spec: priorityLevelConfiguration: name:low-priority matchingPrecedence:1000 rules: -subjects: -kind:ServiceAccount serviceAccount: name:"*" namespace:"*" resourceRules: -verbs:["list","watch"] apiGroups:["*"] resources:["pods","events"] namespaces:["*"]
# 2. 监控 API Server 内存
# Prometheus 告警规则
- alert: APIServerHighMemory
expr: process_resident_memory_bytes{job="kube-apiserver"} > 3e9
for: 5m
labels:
severity: warning
annotations:
summary: "API Server 内存使用过高"
“
教训:永远不要在生产环境无限制地 list 所有资源。使用 label selector、field selector 或分页查询。
2.3 案例三:证书过期,一夜回到解放前
故障现象
周一早上来上班,发现所有 kubectl 命令都报错:
Unable to connect to the server: x509: certificate has expired or is not yet valid
整个集群完全无法操作,业务虽然还在跑(已有的 Pod),但无法进行任何变更。
根因分析
# 检查证书有效期 openssl x509 -in /etc/kubernetes/pki/apiserver.crt -noout -dates # 输出: # notBefore=Jan 15 0000 2025 GMT # notAfter=Jan 15 0000 2026 GMT # 已过期! # 检查所有证书 kubeadm certs check-expiration
根本原因:kubeadm 创建的证书默认有效期是 1 年,我们忘记续期了。更惨的是,这个集群是一年前搭建的,当时没有设置证书过期告警...
解决过程
# 1. 备份现有证书 cp -r /etc/kubernetes/pki /etc/kubernetes/pki.bak # 2. 续期所有证书 kubeadm certs renew all # 3. 重启控制平面组件 # 对于 static pod 方式部署的组件 mv /etc/kubernetes/manifests/*.yaml /tmp/ sleep 30 mv /tmp/*.yaml /etc/kubernetes/manifests/ # 4. 更新 kubeconfig kubeadm kubeconfig user --client-name=admin --org=system:masters > /etc/kubernetes/admin.conf cp /etc/kubernetes/admin.conf ~/.kube/config # 5. 验证 kubectl get nodes
预防措施
# 1. 设置证书过期监控 # 使用 kube-prometheus-stack 自带的证书监控 # 或者自定义脚本 #!/bin/bash # check-cert-expiry.sh CERT_PATH="/etc/kubernetes/pki/apiserver.crt" EXPIRY_DATE=$(openssl x509 -in$CERT_PATH -noout -enddate | cut -d= -f2) EXPIRY_EPOCH=$(date -d "$EXPIRY_DATE" +%s) NOW_EPOCH=$(date +%s) DAYS_LEFT=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 )) if [ $DAYS_LEFT -lt 30 ]; then echo"WARNING: Certificate expires in $DAYS_LEFT days" # 发送告警 fi
# 2. Prometheus 告警规则
-alert:KubernetesCertificateExpiration
expr:apiserver_client_certificate_expiration_seconds_count{job="kube-apiserver"}>0andhistogram_quantile(0.01,rate(apiserver_client_certificate_expiration_seconds_bucket{job="kube-apiserver"}[5m]))<604800
for:5m
labels:
severity:critical
annotations:
summary:"Kubernetes 证书即将过期"
“
教训:部署集群后第一件事就是设置证书过期告警!建议在证书过期前 30 天就开始告警。
三、节点级严重故障
3.1 案例四:节点批量 NotReady,业务雪崩
故障现象
某天下午,监控大屏突然一片红:
[CRITICAL] Node node-01 状态变为 NotReady [CRITICAL] Node node-02 状态变为 NotReady [CRITICAL] Node node-03 状态变为 NotReady ... (持续告警)
5 分钟内,集群 30% 的节点都变成了 NotReady,大量 Pod 被驱逐重建,业务出现严重抖动。
根因分析
# 查看节点状态 kubectl get nodes kubectl describe node node-01 # 关键信息: # Conditions: # Ready False KubeletNotReady container runtime is down # 登录问题节点 ssh node-01 systemctl status containerd # 输出:containerd.service: Failed # 查看 containerd 日志 journalctl -u containerd -n 100 # 发现大量 "too many open files" 错误
根本原因:containerd 进程打开的文件描述符超过了系统限制,导致 containerd 崩溃。而 containerd 崩溃后,kubelet 无法与容器运行时通信,节点就变成 NotReady 了。
为什么会打开这么多文件?排查发现是某个应用疯狂写日志,每秒产生上千个日志文件...
解决过程
# 1. 临时提高文件描述符限制 ulimit -n 1048576 # 2. 永久修改系统限制 cat >> /etc/security/limits.conf << EOF * soft nofile 1048576 * hard nofile 1048576 root soft nofile 1048576 root hard nofile 1048576 EOF # 3. 修改 containerd 服务配置 mkdir -p /etc/systemd/system/containerd.service.d/ cat > /etc/systemd/system/containerd.service.d/limits.conf << EOF [Service] LimitNOFILE=1048576 LimitNPROC=1048576 EOF # 4. 重启服务 systemctl daemon-reload systemctl restart containerd # 5. 清理问题应用的日志 find /var/log/pods -name "*.log" -size +100M -delete
预防措施
# 1. 监控文件描述符使用 # node_exporter 自带此指标 node_filefd_allocated / node_filefd_maximum > 0.8 # 2. 配置日志轮转 # 在 kubelet 配置中 --container-log-max-size=100Mi --container-log-max-files=5
“
教训:系统级资源限制(ulimit)很容易被忽视,但一旦触发就是灾难性的。建议在节点初始化时就配置好。
3.2 案例五:PDB 配置不当,节点无法维护
故障现象
计划对集群节点进行内核升级,需要逐个驱逐节点上的 Pod。执行 kubectl drain 时卡住了:
kubectl drain node-05 --ignore-daemonsets --delete-emptydir-data # 输出: # evicting pod default/web-app-xxx # error when evicting pods/"web-app-xxx" -n "default" (will retry after 5s): # Cannot evict pod as it would violate the pod's disruption budget.
等了半小时还是这样,节点维护工作完全无法进行。
根因分析
# 查看 PDB 配置 kubectl get pdb -A kubectl describe pdb web-app-pdb # 输出: # Min Available: 3 # Current: 3 # Desired: 3 # Allowed Disruptions: 0 # 问题在这里!
根本原因:PDB(Pod Disruption Budget)配置了 minAvailable: 3,而 Deployment 的副本数也是 3。这意味着任何时候都不允许少于 3 个 Pod,所以一个都驱逐不了。
解决过程
# 方案一:临时增加副本数
kubectl scale deployment web-app --replicas=4
# 等待新 Pod Ready 后再 drain
# 方案二:临时调整 PDB
kubectl patch pdb web-app-pdb -p '{"spec":{"minAvailable":2}}'
# 方案三:删除 PDB(不推荐,有风险)
kubectl delete pdb web-app-pdb
正确的 PDB 配置
# 推荐使用百分比而不是绝对数值 apiVersion:policy/v1 kind:PodDisruptionBudget metadata: name:web-app-pdb spec: # 方式一:最少可用百分比 minAvailable:"50%" # 方式二:最大不可用数量(推荐) # maxUnavailable: 1 selector: matchLabels: app:web-app
“
教训:PDB 的 minAvailable 不要设置成和副本数相等!建议使用百分比或 maxUnavailable。
四、应用级常见问题
4.1 案例六:资源配额耗尽,新 Pod 无法创建
故障现象
开发同学反馈新部署的应用一直处于 Pending 状态:
kubectl get pods # NAME READY STATUS RESTARTS AGE # new-app-xxx 0/1 Pending 0 30m kubectl describe pod new-app-xxx # Events: # Warning FailedScheduling pod didn't trigger scale-up: # 1 Insufficient cpu, 1 Insufficient memory
根因分析
# 检查命名空间配额 kubectl describe resourcequota -n dev # 输出: # Name: dev-quota # Resource Used Hard # -------- ---- ---- # limits.cpu 8 8 # 已用完! # limits.memory 16Gi 16Gi # 已用完! # requests.cpu 4 4 # requests.memory 8Gi 8Gi
根本原因:命名空间的资源配额已经用完了,但没有人注意到。
解决过程
# 1. 查看当前资源使用情况
kubectl top pods -n dev
# 2. 找出资源占用大户
kubectl get pods -n dev -o custom-columns=
NAME:.metadata.name,
CPU_REQ:.spec.containers[*].resources.requests.cpu,
MEM_REQ:.spec.containers[*].resources.requests.memory
# 3. 清理不需要的资源或扩大配额
kubectl patch resourcequota dev-quota -n dev
-p '{"spec":{"hard":{"limits.cpu":"16","limits.memory":"32Gi"}}}'
“
教训:配置 ResourceQuota 后一定要配套监控告警,在使用率达到 80% 时就应该告警。
4.2 案例七:滚动更新卡住,新旧版本并存
故障现象
发布新版本后,Deployment 一直卡在更新中:
kubectl rollout status deployment/api-server # Waiting for deployment "api-server" rollout to finish: 2 out of 3 new replicas have been updated...
根因分析
kubectl get pods -l app=api-server # NAME READY STATUS RESTARTS AGE # api-server-old-xxx 1/1 Running 0 2d # api-server-new-xxx 0/1 CrashLoopBackOff 5 10m # api-server-new-yyy 0/1 CrashLoopBackOff 5 10m kubectl describe pod api-server-new-xxx # Events: # Warning Unhealthy Readiness probe failed: HTTP probe failed with statuscode: 500
根本原因:新版本代码有 bug,健康检查一直失败。由于默认的滚动更新策略,旧 Pod 不会被删除,导致更新卡住。
解决过程
# 1. 回滚到上一版本 kubectl rollout undo deployment/api-server # 2. 查看回滚状态 kubectl rollout status deployment/api-server # 3. 修复代码后重新发布
预防措施
# 配置合理的更新策略和超时 apiVersion:apps/v1 kind:Deployment spec: progressDeadlineSeconds:600# 10分钟超时 strategy: type:RollingUpdate rollingUpdate: maxSurge:1 maxUnavailable:0
“
教训:发布前一定要在测试环境验证健康检查配置!
4.3 案例八:ConfigMap 热更新踩坑
故障现象
更新了 ConfigMap 中的配置,但应用没有生效:
kubectl edit configmap app-config # 修改了数据库连接字符串 # 但应用还是连接旧的数据库 kubectl logs app-xxx | grep "database" # 输出还是旧的连接地址
根因分析
根本原因:ConfigMap 更新后,已经运行的 Pod 不会自动重新加载配置。这是 K8s 的设计行为,不是 bug。
有两种挂载方式,行为不同:
环境变量方式:永远不会自动更新
Volume 挂载方式:会自动更新文件,但应用需要自己监听文件变化
解决方案
# 方案一:重启 Pod(最简单) kubectl rollout restart deployment/app # 方案二:使用 Reloader 自动重启 # 安装 stakater/Reloader kubectl apply -f https://raw.githubusercontent.com/stakater/Reloader/master/deployments/kubernetes/reloader.yaml # 在 Deployment 上添加注解 kubectl annotate deployment app reloader.stakater.com/auto="true"
“
教训:ConfigMap 更新不会自动触发 Pod 重启,需要额外机制处理。
4.4 案例九:HPA 抖动风暴
故障现象
配置了 HPA 后,Pod 数量疯狂波动:
10:00 - 副本数: 3 10:01 - 副本数: 10 10:02 - 副本数: 3 10:03 - 副本数: 8 ...
根因分析
kubectl describe hpa web-app # 发现 CPU 使用率在阈值附近波动 # 每次扩容后负载下降,然后缩容,负载又上升...
根本原因:HPA 配置的阈值太敏感,加上默认的扩缩容行为,导致抖动。
解决方案
apiVersion: autoscaling/v2 kind:HorizontalPodAutoscaler spec: behavior: scaleDown: stabilizationWindowSeconds:300# 缩容稳定窗口 policies: -type:Percent value:10 periodSeconds:60 scaleUp: stabilizationWindowSeconds:60 policies: -type:Percent value:100 periodSeconds:15
“
教训:HPA 一定要配置 behavior 字段,控制扩缩容速度。
4.5 案例十:镜像拉取失败雪崩
故障现象
某天早上,大量 Pod 启动失败:
kubectl get pods # NAME READY STATUS RESTARTS AGE # app-a-xxx 0/1 ImagePullBackOff 0 5m # app-b-xxx 0/1 ImagePullBackOff 0 5m # app-c-xxx 0/1 ErrImagePull 0 3m
根因分析
kubectl describe pod app-a-xxx # Events: # Warning Failed Failed to pull image "registry.example.com/app:v1": # rpc error: code = Unknown desc = failed to pull and unpack image: # unexpected status code 503 Service Unavailable
根本原因:内部镜像仓库挂了,所有需要拉取镜像的 Pod 都失败了。
解决方案
# 1. 配置镜像拉取策略,减少不必要的拉取 spec: containers: -name:app image:registry.example.com/app:v1 imagePullPolicy:IfNotPresent# 本地有就不拉取 # 2. 配置多镜像仓库备份 # 使用 Harbor 的镜像复制功能
“
教训:镜像仓库是单点故障,必须做高可用或备份。
五、故障预防清单
5.1 必备监控告警
| 监控项 | 告警阈值 | 说明 |
|---|---|---|
| etcd 磁盘使用率 | > 80% | 防止磁盘写满 |
| API Server 内存 | > 80% | 防止 OOM |
| 证书有效期 | < 30 天 | 防止证书过期 |
| 节点 Ready 状态 | NotReady > 5min | 及时发现节点问题 |
| Pod 重启次数 | > 5 次/小时 | 发现应用问题 |
| 资源配额使用率 | > 80% | 防止配额耗尽 |
5.2 运维检查清单
# 每日检查脚本 #!/bin/bash echo"=== 集群健康检查 ===" # 1. 节点状态 echo"--- 节点状态 ---" kubectl get nodes | grep -v "Ready" # 2. 系统 Pod 状态 echo"--- 系统组件 ---" kubectl get pods -n kube-system | grep -v "Running|Completed" # 3. 证书有效期 echo"--- 证书检查 ---" kubeadm certs check-expiration 2>/dev/null || echo"非 kubeadm 集群" # 4. etcd 健康 echo"--- etcd 状态 ---" kubectl get componentstatuses 2>/dev/null # 5. 资源使用 echo"--- 资源使用 ---" kubectl top nodes
六、总结
6.1 十大教训回顾
| 案例 | 核心教训 |
|---|---|
| etcd 磁盘满 | 配置自动压缩,监控磁盘使用 |
| API Server OOM | 限制大查询,配置 APF |
| 证书过期 | 设置过期告警,定期续期 |
| 节点 NotReady | 配置系统资源限制 |
| PDB 配置错误 | 使用百分比或 maxUnavailable |
| 资源配额耗尽 | 监控配额使用率 |
| 滚动更新卡住 | 测试环境验证健康检查 |
| ConfigMap 不生效 | 使用 Reloader 或手动重启 |
| HPA 抖动 | 配置 behavior 稳定窗口 |
| 镜像拉取失败 | 镜像仓库高可用 |
6.2 参考资料
Kubernetes 官方故障排查指南
etcd 运维最佳实践
Kubernetes 生产最佳实践
写在最后:每一次故障都是成长的机会。希望这些血泪教训能帮助大家少走弯路。记住,生产环境没有小事,任何配置变更都要三思而后行。
全部0条评论
快来发表一下你的评论吧 !