第48篇 k8s之常见问题排查与排错指南

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 logsdocker 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 的 STATUSPending,且持续几分钟以上。

2.2 排查路径与解决

第一步:查看 Pod 的 Events,让 K8s 直接告诉你原因。

bash 复制代码
kubectl describe pod <pod-name>

查看输出末尾的 Events 部分,这里通常有最直接的线索。

原因一:资源不足

bash 复制代码
Events:
  Type     Reason            Age   From               Message
  ----     ------            ----  ----               -------
  Warning  FailedScheduling  30s   default-scheduler  0/1 nodes are available: 1 Insufficient cpu.

解决方案:降低 Pod 的 CPU/内存 Requests,或增加节点资源。

原因二:PVC 无法绑定

bash 复制代码
Events:
  Warning  FailedScheduling  30s   default-scheduler  0/1 nodes are available: persistentvolumeclaim "redis-pvc" not found.

这是因为 Pod 引用了不存在的 PVC。检查 PVC 是否已创建以及名称是否一致:

原因三:镜像拉取失败(ImagePullBackOff)

bash 复制代码
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 计数不断增长,STATUSCrashLoopBackOff

bash 复制代码
NAME                                READY   STATUS             RESTARTS   AGE
flask-deployment-xxxxxxxxx-xxxxx    0/1     CrashLoopBackOff   5          3m
3.2 排查路径与解决

CrashLoopBackOff 表示容器启动后立即退出,kubelet 根据重启策略不断重启,但因为连续失败,重启间隔被指数退避(10s → 20s → 40s → ...,最长 5 分钟)。首先要做的是查看容器日志,搞清楚它为什么退出。

bash 复制代码
kubectl logs <pod-name> --previous

--previous 参数查看上一次容器的日志,因为当前容器可能还没启动就退出了。

常见原因一:应用启动错误

以 Flask 应用为例,如果 app.py 中有语法错误,容器会启动后立即退出。日志中会直接显示 Python traceback,根据报错信息修改代码即可。

常见原因二:探针配置过严

bash 复制代码
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 不断重启容器。解决方案是调整探针参数------增加 initialDelaySecondsperiodSeconds,或增大 failureThreshold

常见原因三:OOMKilled(内存超限)

bash 复制代码
kubectl describe pod <pod-name> | grep -A5 "State"
bash 复制代码
State:       Running
  Started:   Mon, 27 May 2025 10:00:00 +0000
Last State:  Terminated
  Reason:    OOMKilled
  Exit Code: 137

OOMKilledExit Code: 137 表示容器因内存超限被杀。解决方案是增大 limits.memory,或排查应用是否有内存泄漏。


四、场景三:Service 访问不通

4.1 现象

在 Pod 内通过 Service 名称访问另一个服务,返回 Could not resolve hostConnection refused

bash 复制代码
kubectl exec deploy/flask-deployment -- curl http://redis-service:6379
# curl: (6) Could not resolve host: redis-service
4.2 排查路径与解决

Service 访问问题的排查遵循一条固定的验证链:DNS → Endpoints → Pod 连通性。任何一环断了,整个链路就不通。

第一步:检查 Service 是否存在

bash 复制代码
kubectl get svc redis-service

第二步:检查 Service 的 Endpoints 是否有值

bash 复制代码
kubectl get endpoints redis-service

如果 ENDPOINTS 列为空,说明 Service 没有匹配到任何健康的 Pod。检查 Service 的 selector 是否与 Pod 的 labels 完全一致:

bash 复制代码
kubectl get svc redis-service -o jsonpath='{.spec.selector}'
kubectl get pods -l <上面输出的标签>

第三步:检查 DNS 解析

bash 复制代码
kubectl run -it --rm debug --image=alpine -- nslookup redis-service

如果 DNS 解析失败,可能是 CoreDNS Pod 异常:

bash 复制代码
kubectl get pods -n kube-system -l k8s-app=kube-dns

第四步:直接 ping Pod IP

如果 DNS 正常但连接失败,用 Pod IP 直接测试网络层是否通:

bash 复制代码
kubectl get endpoints redis-service   # 记录 ENDPOINTS 列中的 IP
kubectl run -it --rm debug --image=alpine -- ping <Pod IP>

五、场景四:节点 NotReady

5.1 现象

kubectl get nodes 显示某个节点 STATUSNotReady

bash 复制代码
NAME       STATUS     ROLES           AGE   VERSION
minikube   NotReady   control-plane   1d    v1.31.0
5.2 排查路径与解决

节点 NotReady 意味着该节点上的所有 Pod 都面临被驱逐的风险。首先查看节点详情:

bash 复制代码
kubectl describe node <node-name>

在输出末尾的 Conditions 部分查看具体原因。

常见原因一:资源压力

bash 复制代码
Conditions:
  Type             Status    Reason
  MemoryPressure   True      KubeletHasInsufficientMemory
  DiskPressure     True      KubeletHasDiskPressure

节点内存或磁盘不足,kubelet 自动将节点标记为不健康,不再接受新 Pod 调度。解决方案是清理磁盘、释放内存,或增加节点。

常见原因二:kubelet 停止

在节点上检查 kubelet 服务状态:

bash 复制代码
minikube ssh
sudo systemctl status kubelet

如果 kubelet 服务停止了,重启它:

bash 复制代码
sudo systemctl 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 查询:

bash 复制代码
{app="flask-counter"} |= "ERROR"

Loki 返回的日志会显示具体的错误堆栈。如果错误集中在某个特定 Pod 实例,可以进一步缩小查询范围:

bash 复制代码
{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 思维 !

相关推荐
IT策士1 小时前
第 46 篇 k8s之CI/CD 集成:GitOps 理念与 ArgoCD
前端·容器·kubernetes
豆瓣鸡2 小时前
Docker快速入门
运维·docker·容器
VX_182 小时前
Docker镜像直接部署JumpServer
运维·docker·容器
努力搬砖的咸鱼2 小时前
容器编排底层原理:Kubernetes 网络模型与 CNI 插件
网络·微服务·云原生·容器·架构·kubernetes
Plastic garden2 小时前
K8s介绍(2)POD架构
云原生·容器·kubernetes
江湖有缘3 小时前
Docker部署Beaver Habit Tracker习惯追踪应用
运维·docker·容器
步步为营DotNet3 小时前
.NET Aspire 在云原生微服务架构中的深度实践与剖析
云原生·架构·.net
Plastic garden3 小时前
K8s介绍(1)
云原生·容器·kubernetes
江华森3 小时前
《网络架构实战:从单机到云原生的全栈思考》博客系列
网络·云原生·架构