IT策士 10余年一线大厂经验,专注 IT 思维、架构、职场进阶。我会在各个平台持续发布最新文章,助你少走弯路。
在本系列的第一篇文章中,我们从一个docker run hello-world起步。走到今天,你已经能把一个 Flask + Redis 应用完整地部署到 K8s 集群上,配置好 Ingress、HPA、Prometheus 监控、Loki 日志,甚至用 ArgoCD 实现了 GitOps 自动部署。你的集群和应用已经具备了生产级的运行能力。
但如果让我说,从“能用 K8s”到“能维护 K8s”最关键的跃迁是什么,我的答案是:排错能力。
Pod 一直 Pending、容器反复 CrashLoopBackOff、Service 访问不通、节点突然 NotReady——这些场景你迟早会遇到。在 Docker Compose 时代,排错通常只需docker logs和docker inspect,因为所有容器都在一台机器上。而在 K8s 中,故障可能发生在 Pod、节点、网络、存储、调度等任何一个层面,排错需要一套系统性的方法论,而不仅仅是记住几条命令。
今天这篇,就是你的K8s 排错实战手册。我将覆盖 5 类最高频的故障场景,给出标准的排查路径和解决思路,并结合贯穿案例的 Flask + Redis 应用来模拟真实排错过程。
一、排错方法论:建立你的排查框架
面对一个故障,不要盲目地试命令。先建立一套清晰的排查框架,能帮你更快地定位根因。我常用的框架是四层排查法:
第一层:看状态
kubectl get看资源状态,kubectl describe看 Events 事件日志。这一层能解决 60% 的问题。第二层:看日志
kubectl logs看容器 stdout/stderr,Loki 聚合查历史日志。这一层能再解决 20%。第三层:看内部
kubectl exec进入容器调试,curl测试网络连通性,nslookup检查 DNS 解析。这一层覆盖 15% 的疑难杂症。第四层:看底层
kubectl get events -A看集群全局事件,kubectl top看资源消耗,kubectl describe node看节点状态。这一层解决最后 5% 的深层次问题。
每当你面对一个故障,从第一层开始逐层深入,不要跳级。
二、场景一:Pod 一直 Pending
2.1 现象
kubectl get pods显示某个 Pod 的STATUS为Pending,且持续几分钟以上。
2.2 排查路径与解决
第一步:查看 Pod 的 Events,让 K8s 直接告诉你原因。
kubectl describe pod<pod-name>查看输出末尾的 Events 部分,这里通常有最直接的线索。
原因一:资源不足
Events: Type Reason Age From Message ---- ------ ---- ---- ------- Warning FailedScheduling 30s default-scheduler0/1 nodes are available:1Insufficient cpu.解决方案:降低 Pod 的 CPU/内存 Requests,或增加节点资源。
原因二:PVC 无法绑定
Events: Warning FailedScheduling 30s default-scheduler0/1 nodes are available: persistentvolumeclaim"redis-pvc"not found.这是因为 Pod 引用了不存在的 PVC。检查 PVC 是否已创建以及名称是否一致:
原因三:镜像拉取失败(ImagePullBackOff)
Events: Warning Failed 60s kubelet Failed to pull image"flask-redis-counter:99.0":rpc error: code=NotFound desc=failed to pull and unpack image Warning Failed 45s kubelet Error: ErrImagePull Normal BackOff 30s kubelet Back-off pulling image"flask-redis-counter:99.0"ImagePullBackOff意味着 kubelet 尝试拉取镜像但失败了。常见原因有三个:
镜像标签写错了(比如 3.0 写成了 99.0)
镜像是本地构建的,但没有
minikube image load私有镜像仓库需要配置
imagePullSecrets
解决方案:确认镜像标签正确;如果是 Minikube 本地镜像,执行minikube image load <镜像名>:<标签>;如果是私有仓库,创建 Secret 并在 Deployment 中引用imagePullSecrets。
三、场景二:Pod 反复重启(CrashLoopBackOff)
3.1 现象
kubectl get pods显示 Pod 的RESTARTS计数不断增长,STATUS为CrashLoopBackOff。
NAME READY STATUS RESTARTS AGE flask-deployment-xxxxxxxxx-xxxxx0/1 CrashLoopBackOff53m3.2 排查路径与解决
CrashLoopBackOff表示容器启动后立即退出,kubelet 根据重启策略不断重启,但因为连续失败,重启间隔被指数退避(10s → 20s → 40s → …,最长 5 分钟)。首先要做的是查看容器日志,搞清楚它为什么退出。
kubectl logs<pod-name>--previous--previous参数查看上一次容器的日志,因为当前容器可能还没启动就退出了。
常见原因一:应用启动错误
以 Flask 应用为例,如果app.py中有语法错误,容器会启动后立即退出。日志中会直接显示 Python traceback,根据报错信息修改代码即可。
常见原因二:探针配置过严
Events: Warning Unhealthy 45s kubelet Liveness probe failed: Get"http://10.244.1.10:5000/health":context deadline exceeded(Client.Timeout exceeded)Liveness probe 反复失败,kubelet 不断重启容器。解决方案是调整探针参数——增加initialDelaySeconds、periodSeconds,或增大failureThreshold。
常见原因三:OOMKilled(内存超限)
kubectl describe pod<pod-name>|grep-A5"State"State: Running Started: Mon,27May202510:00:00 +0000 Last State: Terminated Reason: OOMKilled Exit Code:137OOMKilled且Exit Code: 137表示容器因内存超限被杀。解决方案是增大limits.memory,或排查应用是否有内存泄漏。
四、场景三:Service 访问不通
4.1 现象
在 Pod 内通过 Service 名称访问另一个服务,返回Could not resolve host或Connection refused。
kubectlexecdeploy/flask-deployment --curlhttp://redis-service:6379# curl: (6) Could not resolve host: redis-service4.2 排查路径与解决
Service 访问问题的排查遵循一条固定的验证链:DNS → Endpoints → Pod 连通性。任何一环断了,整个链路就不通。
第一步:检查 Service 是否存在
kubectl get svc redis-service第二步:检查 Service 的 Endpoints 是否有值
kubectl get endpoints redis-service如果ENDPOINTS列为空,说明 Service 没有匹配到任何健康的 Pod。检查 Service 的selector是否与 Pod 的labels完全一致:
kubectl get svc redis-service-ojsonpath='{.spec.selector}'kubectl get pods-l<上面输出的标签>第三步:检查 DNS 解析
kubectl run-it--rmdebug--image=alpine --nslookupredis-service如果 DNS 解析失败,可能是 CoreDNS Pod 异常:
kubectl get pods-nkube-system-lk8s-app=kube-dns第四步:直接 ping Pod IP
如果 DNS 正常但连接失败,用 Pod IP 直接测试网络层是否通:
kubectl get endpoints redis-service# 记录 ENDPOINTS 列中的 IPkubectl run-it--rmdebug--image=alpine --ping<Pod IP>五、场景四:节点 NotReady
5.1 现象
kubectl get nodes显示某个节点STATUS为NotReady。
NAME STATUS ROLES AGE VERSION minikube NotReady control-plane 1d v1.31.05.2 排查路径与解决
节点NotReady意味着该节点上的所有 Pod 都面临被驱逐的风险。首先查看节点详情:
kubectl describenode<node-name>在输出末尾的Conditions部分查看具体原因。
常见原因一:资源压力
Conditions: Type Status Reason MemoryPressure True KubeletHasInsufficientMemory DiskPressure True KubeletHasDiskPressure节点内存或磁盘不足,kubelet 自动将节点标记为不健康,不再接受新 Pod 调度。解决方案是清理磁盘、释放内存,或增加节点。
常见原因二:kubelet 停止
在节点上检查 kubelet 服务状态:
minikubesshsudosystemctl status kubelet如果 kubelet 服务停止了,重启它:
sudosystemctl restart kubelet六、结合监控与日志快速定位
在真实生产环境中,单个命令的排查效率太低。你需要结合前面搭建的可观测性体系来快速定位问题。
6.1 通过 Prometheus + Grafana 快速发现异常
在 Grafana 仪表板中,你可以一眼看到整个集群的健康状态。以我们贯穿案例的 Flask 应用为例,Grafana 仪表板上的关键指标包括:
Pod CPU Usage面板显示某个 Flask Pod 的 CPU 突然飙升至 2 核,远超过设定的 500m Limits,同时Pod Restarts面板显示该 Pod 的重启次数从 0 跳升至 5。
Container Memory Usage面板显示 Redis Pod 内存使用持续上涨至 256Mi,接近 Limits,而PVC Usage面板显示 Redis 数据卷使用率已达 92%。
这些指标异常会在对应的 Alertmanager 告警规则中触发通知。例如,我们之前配置的PodFrequentlyRestarting规则会在 Pod 在 5 分钟内重启超过 2 次时发送告警:
*“Alert: PodFrequentlyRestarting, Severity: warning, Pod flask-deployment-xxxxxxxx-xxxxx has restarted 5 times in the last 5 minutes.”*
6.2 通过 Loki 快速查看相关日志
当监控指标显示异常后,你可以在 Grafana 中无缝切换到 Loki 查询页面。在 Explore 视图中选择 Loki 数据源,输入 LogQL 查询:
{app="flask-counter"}|="ERROR"Loki 返回的日志会显示具体的错误堆栈。如果错误集中在某个特定 Pod 实例,可以进一步缩小查询范围:
{app="flask-counter",pod="flask-deployment-xxxxxxxx-xxxxx"}监控指标告诉你“哪里出问题了”,Loki 日志告诉你“为什么会出问题”。两者在同一个 Grafana 界面中协同工作,形成完整的可观测性闭环。
七、命令速查表
八、本篇总结
四层排查法:看状态(
kubectl describe+ Events)→ 看日志(kubectl logs)→ 看内部(exec+curl+nslookup)→ 看底层(节点 + 全局事件),逐层深入,不跳级。Pod 级故障:
Pending(资源不足/PVC 缺失/镜像拉取失败)、CrashLoopBackOff(探针过严/OOMKilled/应用自身错误),每个状态都有固定的排查路径。网络故障:Service 不通沿 DNS → Endpoints → Pod IP 逐层验证。Endpoints 为空检查 label selector 是否匹配,DNS 失败检查 CoreDNS 状态。
节点故障:
NotReady常见于资源压力(MemoryPressure/DiskPressure)或 kubelet 服务异常。可观测性联动:Prometheus + Grafana 快速发现异常,Loki 日志精确定位根因。监控告诉你“发生了什么”,日志告诉你“为什么发生”。
如果你把这篇文章中每个场景的排查路径都实际操作一遍,你就能自信地应对 K8s 中 80% 以上的日常故障。下一篇——第 49 篇:服务网格入门:Istio 简介,我们将站在 NetworkPolicy 和 Ingress 的肩膀上,向更高级的网络管理能力迈进。
想了解更多还可以去各个平台搜索「IT策士」,一起升级 IT 思维 !