
K8s 生产环境 10 大"炸雷"复盘:我是如何搞挂生产集群的
这篇文章记录了我这些年在 K8s 生产环境踩过的坑。每一个案例都是血泪教训,有些甚至导致了生产事故。希望通过分享这些经历,能帮助大家避免重蹈覆辙。
声明:以下案例均已脱敏处理,部分细节有所调整。
一、概述
1.1 背景介绍
K8s 已经成为容器编排的事实标准,但它的复杂性也带来了很多潜在的风险点。根据我的经验,生产环境出问题往往不是因为 K8s 本身有 bug,而是:
配置不当 资源规划不合理 缺乏监控告警 操作流程不规范 对某些机制理解不深
1.2 文章结构
本文将按照故障严重程度,从"集群级灾难"到"应用级问题",逐一复盘 10 个真实案例:
1.3 环境信息
二、集群级灾难案例
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 <revision>
# 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 00:00:00 2025 GMT
# notAfter=Jan 15 00:00:00 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 必备监控告警
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 十大教训回顾
6.2 参考资料
Kubernetes 官方故障排查指南 etcd 运维最佳实践 Kubernetes 生产最佳实践
“写在最后:每一次故障都是成长的机会。希望这些血泪教训能帮助大家少走弯路。记住,生产环境没有小事,任何配置变更都要三思而后行。
(版权归原作者所有,侵删)
免责声明:本文内容来源于网络,所载内容仅供参考。转载仅为学习和交流之目的,如无意中侵犯您的合法权益,请及时联系Docker中文社区!

温馨提示:文章内容系作者个人观点,不代表Docker中文对观点赞同或支持。
版权声明:本文为转载文章,来源于 互联网 ,版权归原作者所有,欢迎分享本文,转载请保留出处!

发表评论