K8s 生产故障实战复盘:10 个高危问题与避坑指南

 互联网   2026-03-11 16:56   13 人阅读  0 条评论
K8s 生产故障实战复盘:10 个高危问题与避坑指南  第1张


K8s 生产环境 10 大"炸雷"复盘:我是如何搞挂生产集群的

这篇文章记录了我这些年在 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 数据目录所在磁盘写满了。

为什么会写满?排查发现是因为:

  1. 某个 Operator 存在 bug,疯狂创建 Event 对象
  2. etcd 没有配置自动压缩(compaction)
  3. 磁盘容量规划不足,只给了 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 必备监控告警

监控项
告警阈值
说明
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 生产最佳实践

写在最后:每一次故障都是成长的机会。希望这些血泪教训能帮助大家少走弯路。记住,生产环境没有小事,任何配置变更都要三思而后行。


(版权归原作者所有,侵删)


免责声明:本文内容来源于网络,所载内容仅供参考。转载仅为学习和交流之目的,如无意中侵犯您的合法权益,请及时联系Docker中文社区!


K8s 生产故障实战复盘:10 个高危问题与避坑指南  第2张
本文地址:https://www.kubernetes.top/?id=456
温馨提示:文章内容系作者个人观点,不代表Docker中文对观点赞同或支持。
版权声明:本文为转载文章,来源于 互联网 ,版权归原作者所有,欢迎分享本文,转载请保留出处!

 发表评论


表情

还没有留言,还不快点抢沙发?