我们日常玩 K8s,每天必敲的命令估计就是 kubectl get pod了!
但90%的人看这个命令的输出,就只认识 Running、Pending 这两个状态,剩下的像 ImagePullBackOff、CrashLoopBackOff 一出现,瞬间就懵了——不知道为啥会这样,更不知道该从哪下手排查。
其实Pod的状态,就是K8s在给我们“报信”,每一个状态背后,都藏着明确的问题原因,只要看懂了,排查问题就能少走80%的弯路。
今天直接把K8s里所有常见的Pod状态,一个个讲透、讲明白,包括每个状态的含义、常见原因,还有直接能上手用的排查命令。
Pending
首先第一个,也是我们最常遇到的状态之一——Pending。
我们先搞懂它的核心含义:K8s的API服务器已经接收到了你的Pod创建请求,也已经把这个请求记录下来了,但调度器还没给这个Pod分配具体的节点。简单说就是,Pod已经“挂号”了,但还没分到“病床”(节点)。
那什么时候会出现这个状态呢?我总结了3个最常见的情况,都是我们日常工作中大概率会碰到的:
- • 节点资源不足:这是最最常见的原因。比如你给Pod设置的Request(资源请求)是1核CPU、2G内存,但集群里所有节点的剩余CPU都不到1核,或者内存不够,调度器就没法给它分配节点,只能一直Pending。这里要注意,Request是Pod必须要有的资源,节点不够就肯定调度不了。
- • 节点有污点(Taint),而Pod没有对应的容忍(Toleration):我们有些节点会设置污点,比如把节点标记为“不可调度”,或者“只能运行特定Pod”,如果你的Pod没有配置对应的容忍规则,调度器就不会把它调度到这个节点上,如果集群里所有节点都有这样的污点,Pod就会一直Pending。
- • PV/PVC绑定失败:如果你的Pod需要挂载存储,也就是配置了PVC,但对应的PV不存在,或者PV的规格、访问模式和PVC不匹配,Pod就会一直处于Pending状态,直到PVC和PV成功绑定。
排查这个状态的命令,就一个,百试百灵
kubectl describe pod <pod-name> -n <namespace>
执行这个命令后,拉到最下面的“Events”(事件)部分,里面会明确告诉你,为什么Pod调度失败,是资源不足,还是PVC绑定失败,一看就懂。比如会显示“0/3 nodes are available: 3 Insufficient cpu.”,就是说3个节点都因为CPU不足,无法调度。
ContainerCreating
接下来这个状态,ContainerCreating,比Pending更进一步——调度器已经给Pod分配好节点了,现在节点上的kubelet正在执行“创建容器”的操作。
正常情况下,这个状态只会持续几十秒,最多一两分钟,如果这个状态卡了很久(比如超过5分钟),就说明有问题了,得排查。
常见的卡壳原因,主要有2个:
- • 镜像太大,拉取速度太慢:比如你用的镜像体积有几个G,而节点的网络不好,拉取镜像就会特别慢,kubelet一直在等镜像拉完,就会一直显示ContainerCreating。这种情况,我们可以先查看镜像拉取的进度。
- • Pod配置错误:比如你写错了容器的command(启动命令)、args(启动参数),或者挂载volume的时候,路径写错了、挂载的文件不存在,kubelet在创建容器的时候,发现配置有问题,就会卡住,一直处于这个状态。
排查方法还是用describe命令,重点看Events里的信息,比如如果是镜像拉取慢,会显示“Pulling image xxx”,一直没有后续;如果是配置错误,会显示“Error: invalid command”之类的提示。
另外,也可以用这个命令查看容器创建的详细日志,更精准定位问题:
kubectl logs <pod-name> -n <namespace> --previous
Running
这个状态大家最熟悉——Running,表面意思就是Pod正在正常运行。但我必须提醒大家一句:Running不代表你的业务正常,只代表Pod里的容器已经成功启动了。
很多新手会踩一个坑:看到Pod是Running状态,就以为万事大吉了,结果业务访问不了,还不知道问题出在哪。其实原因很简单:K8s判断Pod是否Running,只看容器是否启动,不看业务是否正常。
这里就要提到两个关键的健康检查:就绪探针(Readiness Probe)和存活探针(Liveness Probe)。
如果你的Pod配置了就绪探针,就算容器启动了(Running状态),但就绪探针检测失败,Pod也不会被纳入Service的负载均衡,业务就访问不到;而存活探针检测失败,K8s会重启容器,直到探针检测成功。
所以,看到Running状态,我们最好再额外检查一下探针状态和业务日志,确认业务真的正常:
# 查看Pod的详细状态,包括探针信息
kubectl describe pod <pod-name> -n <namespace>
# 查看业务日志,确认业务正常运行
kubectl logs -f <pod-name> -n <namespace>
Succeeded
Succeeded这个状态,大家可能不常遇到,但只要遇到,就说明这个Pod是“正常完成任务”了。
它的核心含义是:Pod里的所有容器都已经正常退出,并且退出码是0(退出码0代表正常退出)。这种Pod一般是一次性任务,比如Job、CronJob创建的Pod——执行完指定的任务,就自动退出,状态变成Succeeded。
举个例子:你用Job创建一个Pod,让它执行一个脚本,脚本执行完成后,容器正常退出,Pod状态就会变成Succeeded。这种状态下,Pod不会再重启,除非你重新创建Job。
如果想查看这个Pod执行任务的日志,可以用:
kubectl logs <pod-name> -n <namespace>
另外,Succeeded状态的Pod,默认会保留在集群中,如果你想自动清理,可以在Job配置里设置ttlSecondsAfterFinished,比如设置为3600,就是任务完成后1小时,自动删除这个Pod。
Failed
和Succeeded相对的,就是Failed状态——Pod里的至少一个容器,异常退出了,而且退出码不是0(非0退出码代表异常)。
简单说就是,Pod的任务没完成,还出错了。比如你写的脚本有语法错误,执行的时候报错,容器退出,Pod状态就会变成Failed;或者应用程序崩溃,导致容器异常退出,也会显示Failed。
遇到Failed状态,重点要排查“为什么容器会异常退出”,核心排查步骤就是两步:
- 1. 查看容器日志,看具体的报错信息:
kubectl logs <pod-name> -n <namespace>
- 2. 查看Pod事件,看是否有其他异常(比如资源不足导致容器被kill):
kubectl describe pod <pod-name> -n <namespace>
这里要注意,如果Pod配置了重启策略(RestartPolicy)为Always,那么Failed状态的Pod会被自动重启,重启后状态会变成Running,但如果应用本身有问题,会一直循环“Running→Failed→重启”。
CrashLoopBackOff
这个状态,绝对是我们运维日常最头疼的状态之一——CrashLoopBackOff,翻译过来就是“崩溃-循环-退避”,简单说就是:Pod启动后,很快就崩溃退出,K8s尝试重启它,结果重启后又崩溃,就这样一直循环,而且每次重启的间隔时间会越来越长(退避机制)。
比如:Pod启动后10秒崩溃,K8s隔10秒重启,重启后又10秒崩溃,下次就隔20秒重启,再崩溃就隔40秒,以此类推,直到达到最大重启次数。
出现这个状态,核心原因只有一个:Pod里的应用启动失败,或者启动后很快就异常退出。我总结了4个最常见的场景,大家可以对号入座:
- • 应用本身有问题:比如Java应用的jar包损坏、配置文件错误,启动的时候报错,直接退出;或者应用依赖的服务(比如数据库、Redis)没起来,应用连接失败,启动失败。
- • 容器启动命令错误:比如command写错了,或者args参数传递错误,导致容器启动后无法正常运行,直接退出。
- • 端口冲突:Pod里的容器要监听的端口,被节点上的其他进程占用了,容器启动失败,就会崩溃。这种情况在单机部署K8s或者节点上运行了其他服务时,很容易出现。
- • 资源不足:虽然Pod被调度到了节点上,但节点的剩余资源刚好够Pod启动,启动后应用运行起来,占用的资源超过了节点剩余资源,被节点的OOM Killer(内存溢出杀手) kill掉,导致Pod崩溃。
排查这个状态,方法很固定,就两个命令,结合起来用,基本能定位所有问题:
# 查看容器日志,看应用启动失败的具体报错(最关键)
kubectl logs -f <pod-name> -n <namespace>
# 查看Pod事件,看是否有资源不足、端口冲突等异常
kubectl describe pod <pod-name> -n <namespace>
举个例子,如果你在日志里看到“Could not connect to MySQL”,就知道是应用依赖的数据库没起来;如果看到“Address already in use”,就是端口冲突了。
ImagePullBackOff / ErrImagePull
这两个状态经常一起出现,都是和“镜像拉取”有关,我们放在一起讲,区别不大,核心都是“镜像拉取失败”。
先区分一下两者的细微差别:ErrImagePull是“拉取镜像时直接出错”,比如镜像名写错、镜像不存在;而ImagePullBackOff是“拉取失败后,K8s会尝试重新拉取,每次拉取失败后,间隔时间会越来越长(退避)”。简单说,ErrImagePull是“单次拉取失败”,ImagePullBackOff是“多次拉取失败,正在重试”。
出现这两个状态,原因非常明确,就4种,我们一个个说,排查起来很简单:
- • 镜像名写错了:这是最基础的错误,比如把镜像名写成“nginx:1.23”,结果实际镜像名是“nginx:1.24”,或者镜像仓库地址写错了,比如把“docker.io”写成了“docker.ioo”,都会导致拉取失败。
- • 私有镜像没配置Secret:如果你的镜像存放在私有仓库(比如阿里云ACR、Docker Hub私有仓库),没有在Pod所在的命名空间创建对应的Secret,也没有在Pod配置里指定这个Secret,K8s拉取镜像时没有权限,就会失败。
- • 节点网络不通,无法访问镜像仓库:比如节点没有联网,或者节点的防火墙、网络策略限制了访问镜像仓库(比如docker.io、阿里云仓库),导致镜像拉取不了。可以在节点上手动执行docker pull 镜像名,测试一下是否能拉取成功。
- • 镜像tag不存在:比如你指定的镜像tag是“latest”,但仓库里没有这个tag,或者tag拼写错误(比如把“v1.0”写成了“v10”),也会拉取失败。
排查方法:先检查Pod配置里的镜像名和tag是否正确,然后用describe命令查看事件,里面会明确提示拉取失败的原因:
kubectl describe pod <pod-name> -n <namespace>
如果是私有镜像权限问题,就创建Secret,然后在Pod配置里添加imagePullSecrets:
imagePullSecrets:
- name: 私有仓库secret名称
Terminating
Terminating状态很简单,就是“Pod正在被删除”。比如你执行了kubectl delete pod命令,或者Pod的生命周期结束了(比如Job完成后),K8s就会开始删除Pod,这个过程中,Pod状态就会显示为Terminating。
正常情况下,这个状态只会持续几十秒,Pod会被顺利删除,但有时候会出现“Terminating卡住”的情况——Pod一直显示Terminating,删不掉,这时候就需要排查原因了。
常见的卡住原因,主要有3个:
- • preStop钩子卡死:如果你的Pod配置了preStop钩子(容器停止前执行的命令),比如preStop里执行了一个长时间运行的脚本,或者脚本出错了,导致钩子一直不结束,Pod就会一直处于Terminating状态,删不掉。
- • 容器进程无法被kill:容器里的应用进程变成了僵尸进程,或者进程本身拒绝被终止,K8s无法正常停止容器,就会导致Pod删除卡住。
- • 挂载盘卡住:Pod挂载了存储卷(比如NFS、PVC),删除Pod时,存储卷无法正常卸载,导致Pod删除卡住。
如果遇到Terminating卡住,先尝试强制删除:
kubectl delete pod <pod-name> -n <namespace> --force --grace-period=0
如果强制删除也不行,就需要登录到Pod所在的节点,手动杀死容器进程,或者排查存储卷挂载问题。
Unknown
Unknown状态,相对来说比较少见,但出现了就说明“节点失联了”。
它的核心含义是:K8s的API服务器,已经无法和Pod所在节点的kubelet通信了,不知道这个Pod现在是什么状态,所以标记为Unknown。
常见原因只有2个,都和节点有关:
- • 节点的kubelet服务挂了:kubelet是节点上负责管理容器的组件,如果kubelet停止运行了,就无法向API服务器上报Pod的状态,API服务器就会把Pod状态标记为Unknown。
- • 节点网络中断:节点和API服务器之间的网络断了,比如节点网卡故障、网络路由异常,导致API服务器无法和kubelet通信,也会显示Unknown。
排查方法:先检查Pod所在的节点状态,看节点是否正常:
kubectl get nodes
# 查看节点详细状态,重点看kubelet是否正常
kubectl describe node <node-name>
如果节点状态是NotReady,就登录到节点上,检查kubelet服务是否运行,以及节点网络是否正常:
# 检查kubelet服务状态
systemctl status kubelet
# 测试节点和API服务器的连通性(替换为你的API服务器地址)
ping apiserver.kubernetes.default.svc
Init:Error / Init:CrashLoopBackOff
这两个状态,和前面的Error、CrashLoopBackOff类似,但有一个关键区别——它们是针对Init容器的,不是主容器。
先给新手科普一下:Init容器是Pod启动前执行的容器,用来做一些初始化操作,比如下载配置文件、等待依赖服务启动、创建目录等,只有所有Init容器都正常完成(退出码0),主容器才会启动。
所以,Init:Error就是“Init容器执行失败,异常退出”;Init:CrashLoopBackOff就是“Init容器反复崩溃、重启,循环失败”。
常见原因,和主容器的Error、CrashLoopBackOff差不多,主要是:
- • Init容器的启动命令错误,或者脚本出错;
- • Init容器依赖的服务(比如数据库、配置中心)没起来,导致Init容器执行失败;
- • Init容器需要的资源不足,被节点kill掉。
排查方法,和主容器类似,重点查看Init容器的日志:
# 查看Init容器的日志,替换为你的Init容器名称
kubectl logs <pod-name> -n <namespace> -c <init-container-name>
比如你的Init容器叫“init-db”,就用上面的命令,查看这个容器的日志,就能知道初始化操作哪里出错了。
PodHasNetwork
这个状态比较特殊,很多人可能没见过,它的含义是:Pod已经成功分配了网络(比如通过CNI插件分配了IP地址),但容器还没开始启动。
简单说就是,Pod的“网络部分”已经准备好了,但“容器部分”还没开始创建。这个状态正常情况下只会持续很短的时间,很快就会过渡到ContainerCreating状态。
如果这个状态卡了很久,大概率是节点的CNI插件有问题,或者网络配置异常,导致Pod虽然分配了IP,但无法正常启动容器。
排查方法:查看节点上的CNI插件状态(比如calico、flannel),看是否运行正常:
# 查看calico插件状态(如果用的是calico)
kubectl get pods -n kube-system | grep calico
# 查看CNI相关日志
kubectl logs <calico-pod-name> -n kube-system
NotReady
注意了,这里的NotReady,是Pod的状态,不是节点的状态!很多人会把它和节点的NotReady搞混,一定要区分开。
Pod的NotReady状态,含义是:Pod里的容器已经正常启动了,但就绪探针(Readiness Probe)检测失败。
前面我们讲Running状态的时候提到过,就绪探针是用来判断Pod是否“就绪”,是否能接收请求的。如果就绪探针检测失败,Pod就会处于NotReady状态,并且不会被Service转发请求,避免把请求发送到不健康的Pod上。
常见原因,主要和就绪探针的配置以及业务状态有关:
- • 就绪探针配置错误:比如探针的检测路径写错了(比如把“/health”写成了“/healthz”),或者检测端口写错了,导致探针一直检测失败;
- • 业务虽然启动了,但还没准备好接收请求:比如应用启动后,需要加载大量数据,加载完成前,就绪探针检测失败,Pod就会处于NotReady状态;
- • 业务运行异常:比如应用启动后,因为配置错误,无法正常提供服务,就绪探针检测不到健康状态,就会显示NotReady。
排查方法:查看Pod的探针配置和探针检测日志:
# 查看Pod的探针配置
kubectl describe pod <pod-name> -n <namespace>
# 查看探针检测日志(部分K8s版本支持)
kubectl logs <pod-name> -n <namespace> --log-dir=/var/log/kubelet
解决方法也很简单:要么修正就绪探针的配置,要么排查业务问题,确保业务能正常响应探针检测。
ContainerStatusUnknown
这个状态和Unknown状态类似,都是“状态未知”,但它更具体——是Pod里的某个容器的状态未知,而不是整个Pod的状态。
核心原因:Pod所在的节点失联了(kubelet挂了或网络断了),API服务器无法获取到容器的具体状态,所以把容器的状态标记为ContainerStatusUnknown,进而导致整个Pod的状态可能变成Unknown。
排查方法和Unknown状态完全一样:先检查Pod所在的节点状态,查看kubelet服务和节点网络是否正常,修复节点问题后,容器状态就会恢复正常。
Evicted
最后一个状态,Evicted,翻译成中文就是“被驱逐”。这个状态我上一篇文章提到过,今天再详细说一下,因为它也是日常工作中很常见的状态。
它的含义是:Pod因为节点的资源压力(磁盘、内存、CPU),被K8s的驱逐器(kube-evictioner)强制驱逐出节点了。简单说就是,节点资源不够用了,K8s会优先驱逐那些资源占用高、或者优先级低的Pod,腾出资源给更重要的Pod。
常见的驱逐原因,主要有3种:
- • 节点磁盘空间不足:节点的根目录或者容器存储目录(比如/var/lib/docker)空间满了,K8s会驱逐Pod,释放磁盘空间;
- • 节点内存不足:节点的剩余内存低于阈值,K8s会驱逐部分Pod,避免出现OOM(内存溢出);
- • 节点CPU不足:节点的CPU使用率持续过高,K8s会驱逐部分Pod,缓解CPU压力。
排查方法:查看节点的资源使用情况,确认是否有资源不足的问题:
# 查看节点资源使用情况
kubectl top node
# 查看节点磁盘使用情况(登录节点执行)
df -h
# 查看Pod被驱逐的具体原因
kubectl describe pod <pod-name> -n <namespace>
解决方法:要么清理节点的资源(比如删除无用的容器、镜像,释放磁盘空间),要么给节点扩容(增加内存、CPU、磁盘),然后重新创建被驱逐的Pod。
补充:
Init:0/1,
UnexpectedAdmissionError,
ImageInspectError,
CreateContainerError,
NodeSelectError,
OOMKilled,
Unschedulable
其实Pod的状态,本质上就是K8s给我们的“故障提示”,每一个状态都对应着明确的问题方向,不用害怕,也不用瞎排查。
记住一个核心排查逻辑:先看状态,再用kubectl describe pod看事件,最后用kubectl logs看日志,90%的问题都能解决。