这是一篇非常接地气的实战经验分享,翻译时我着重保留了原文那种“踩坑后痛定思痛”的口语化语气,同时确保技术术语的准确性和易读性。
Kubernetes 常见问题与解决方案:我用血泪换来的避坑指南
上周五晚上 10 点,我正准备关电脑享受周末,钉钉突然狂响——线上有个关键服务挂了。我打开终端一查,Pod 状态栏赫然显示着 OOMKilled。那一刻我立刻想起来:这个 Deployment 的 YAML 里,我压根没写 resources.limits。
这是我玩 Kubernetes 以来踩得最痛的一个坑,但绝不是唯一一个。今天我想把这几年在 K8s 里摸爬滚打攒下的常见问题和解决方案整理出来,希望能帮你少走点弯路。
坑一:不设资源 requests 和 limits,等于埋雷
这是我最常犯、也见别人最常犯的错误。
Kubernetes 不强制要求你写 resources 字段,Pod 照样能跑。这就给人一种“没事,不写也行”的错觉。我早期在本地 minikube 上测试时从不写这玩意儿,一切正常。等部署到生产集群,问题全来了:
- 没有 requests:调度器不知道 Pod 需要多少资源,可能把一堆 Pod 塞到同一个节点,导致 CPU 抢占、性能暴跌
- 没有 limits:某个 Pod 内存泄漏,疯狂吃内存,把同节点其他 Pod 挤得被 OOMKilled 驱逐
我那次周五晚上的事故就是这么来的——一个 Java 应用了无限制的内存,把隔壁的关键 Go 服务给挤死了。
解决方案:从保守值开始,逐步调优。
resources:
requests:
cpu: "100m" # 0.1 核
memory: "128Mi"
limits:
cpu: "500m" # 0.5 核
memory: "512Mi"
然后用 kubectl top pods 观察实际用量,再调整。如果你用阿里云 ACK,控制台的监控图表更直观。配合 HPA(水平自动扩缩容),能根据指标自动增减 Pod 副本数。
我的教训:本地环境没问题不代表生产没问题。资源限制不是可选项,是必选项。
坑二:忽略存活探针和就绪探针
我第二个惨痛教训跟探针有关。
有次部署了一个 Spring Boot 应用,容器启动了,Kubernetes 认为它“运行中”——因为 JVM 进程没退出。但实际上 Spring 还在初始化,根本没准备好接请求。Service 已经把流量打进去了,用户看到的就是一堆 502 错误。更糟的是,有一次应用内部死锁了,进程还活着,K8s 也不知道该重启它。
解决方案:务必配置 livenessProbe 和 readinessProbe。
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 30 # 给应用启动时间
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
- livenessProbe(存活探针):检查失败就重启容器。救你于死锁、线程池耗尽等“进程在但应用死了”的场景
- readinessProbe(就绪探针):检查失败就把 Pod 从 Service 端点摘除。防止流量打到还没准备好的 Pod
如果应用启动很慢(说的就是 Java),加个 startupProbe,给更长的初始宽限期,避免被误杀。
我的经验:一开始用简单的 TCP socket 探针也行,总比没有强。但最终要过渡到业务语义的 HTTP 端点。
坑三:镜像拉取慢或失败
这个问题我在阿里云 ACK 上踩过好几次。现象就是 Pod 一直 ImagePullBackOff 或 ErrImagePull。
排查下来,常见原因有三类:
1. 公网访问没开或带宽太小
通过公网拉镜像时,集群需要公网访问能力。在 ACK 控制台检查是否开启了公网 API Server 访问,带宽峰值是否够用。
2. ACR 镜像密钥问题
从阿里云容器镜像服务 ACR 拉取私有镜像时,需要配置 imagePullSecret 或安装免密组件。我之前跨账号拉镜像时,免密组件配错了,折腾了两个小时。
解决方案:
- 同账号:按照文档配置免密组件
- 跨账号:正确配置跨账号免密,或手动创建 Secret
kubectl create secret docker-registry acr-secret \
--docker-server=registry.cn-hangzhou.aliyuncs.com \
--docker-username=your-username \
--docker-password=your-password
然后在 Deployment 里引用:
imagePullSecrets:
- name: acr-secret
3. Docker Hub 海外源网络不稳
这是国内用户的老大难问题。解决方案有两个:
- 用 ACR 的制品订阅功能,把海外镜像同步到国内
- 创建全球加速 GA 实例,加速拉取
我的建议:生产环境别直接依赖 Docker Hub。把镜像都同步到 ACR 或自建仓库,省心。
坑四:刚创建的集群就提示 Pod 数量不足
这个问题让我困惑了很久。明明买了 4 核 8G 的节点,怎么刚建好集群就说 Pod 不够用?
原因是:Kubernetes 自身组件(kube-proxy、CoreDNS、日志采集等)要占用资源,每个节点还要预留系统资源。阿里云文档里说得很清楚——购买后查看节点可用 CPU 和内存,比实例规格定义的少是正常的。
解决方案:
- 建集群时预留余量,别按“刚好够”来算
- 用
kubectl describe node <node-name>看Allocatable字段,这才是 Pod 真正能用的资源 - 规划节点池时,把系统组件开销算进去
坑五:KubeConfig 证书过期导致连不上集群
某天早上 kubectl get pods 突然报 certificate is valid for 错误,我的心跳都漏了一拍。
ACK 托管集群的 KubeConfig 证书有有效期。解决方法:
- 在 ACK 控制台重新下载 KubeConfig
- 或用
aliyun CLI更新证书
注意:KubeConfig 误删后可以从回收站恢复,但恢复按钮置灰说明有冲突——比如同一 RAM 用户在回收站有多条记录。这时需要根据集群 ID 和创建时间来识别正确的记录。
安全实践:别把 KubeConfig 长期存在跳板机上。用 RBAC 给不同团队配最小权限,定期轮换证书。
坑六:集群版本升级的恐惧
K8s 版本迭代快,旧版本终会被废弃。但升级集群这事儿,每次都让我紧张。
几个关键经验:
1. 不能永远不升级。过期版本的集群可能还能跑,但出了问题没人给你修,安全漏洞也没人补。
2. 不支持跨多个次版本升级。比如从 1.20 到 1.24,必须 1.20→1.21→1.22→1.23→1.24 一步步来。
3. 1.22 到 1.24 的 Docker→containerd 切换是大坑。提前在测试环境演练,确认镜像兼容性。
4. 升级前必须跑前置检查。常见报错及处理:
- 废弃 API 报错:找到使用了废弃 API 的资源,改用新 API
- 组件版本过低:先升级组件再升级集群
the aliyun service is not running:检查节点的 ECS 实例状态和阿里云插件PLEG not healthy:通常是节点 Docker 或 containerd 异常,先修复节点
5. 升级不支持回退。所以一定要备份 etcd(托管集群阿里云帮你做了),在业务低峰期操作。
坑七:Pod 异常排查的通用思路
最后分享一个我总结的 Pod 故障排查流程,适用于大部分场景:
# 第一步:看 Pod 状态
kubectl get pods
# 第二步:看 Pod 事件,这步最关键
kubectl describe pod <pod-name>
# 第三步:看容器日志
kubectl logs <pod-name> [-c <container-name>]
# 第四步:如果是 CrashLoopBackOff,看上一次的日志
kubectl logs <pod-name> --previous
常见状态含义:
- Pending:调度失败,通常是资源不足或 PVC 挂载问题
- ImagePullBackOff:镜像拉取失败,参考坑三
- CrashLoopBackOff:容器启动后崩溃,看日志找原因
- OOMKilled:内存超限,参考坑一加 limits
- Terminating:卡在删除,通常是有 finalizer 未完成或挂载点忙
在 ACK 控制台排查更方便——集群详情页有故障诊断功能,能自动分析常见问题。
写在最后
Kubernetes 的学习曲线确实陡,但大部分坑都有规律可循。总结一下我的核心建议:
- 资源限制必须写,从保守值开始,用监控调优
- 探针必须配,否则 K8s 就是个瞎子
- 镜像拉取提前规划,别依赖海外源
- 集群升级要趁早,别拖到过期才慌
- 排查问题有套路:先 describe 看事件,再 logs 看日志
老实说,K8s 的复杂性不会消失,但理解了这些常见问题的模式后,你至少能从“两眼一抹黑”变成“有方向地排查”。那些周五晚上的报警,也会少很多。