适用场景
这篇文章适用于 Pod 一直停留在 Pending、ErrImagePull 或 ImagePullBackOff,业务发布后没有新实例可用的场景。常见环境包括自建 Kubernetes、云厂商托管集群、私有镜像仓库 Harbor、阿里云/腾讯云镜像仓库,以及通过 CI/CD 自动更新镜像 tag 的发布链路。
ImagePullBackOff 不是应用进程启动失败,而是 kubelet 在节点上拉取镜像失败后进入退避重试。排查时不要先看应用日志,因为容器通常还没有真正启动,应优先看 Pod 事件、镜像地址、仓库鉴权、节点到仓库的网络连通性和容器运行时状态。
现象描述
发布后 Deployment 的副本数迟迟不 Ready:
kubectl get deploy -n prod api
kubectl get pod -n prod -l app=api -o wide
可能看到类似输出:
NAME READY STATUS RESTARTS AGE IP NODE
api-7d6d9f8f6b-xm2pt 0/1 ImagePullBackOff 0 6m <none> node-03
查看 Pod 详情时,事件里通常会出现更有价值的错误:
kubectl describe pod -n prod api-7d6d9f8f6b-xm2pt
典型事件包括:
Failed to pull image "registry.example.com/prod/api:20260724-1530":
rpc error: code = NotFound desc = failed to pull and unpack image
Failed to pull image "registry.example.com/prod/api:latest":
unauthorized: authentication required
Failed to pull image "registry.example.com/prod/api:v1":
dial tcp 10.20.30.40:443: i/o timeout
Back-off pulling image "registry.example.com/prod/api:v1"
可能原因
ImagePullBackOff 的根因一般集中在以下几类:
- 镜像名或 tag 写错,仓库中根本不存在该镜像。
- 私有仓库需要登录,但 Pod 没有配置
imagePullSecrets,或 Secret 已过期。 - 节点无法访问镜像仓库,例如 DNS 解析失败、防火墙拦截、代理配置缺失、证书不被信任。
- 镜像架构与节点架构不匹配,例如 arm64 节点拉取了只有 amd64 的镜像。
- 节点磁盘空间不足,容器运行时无法解压镜像层。
- CI/CD 推送镜像失败,但后续部署步骤仍然更新了工作负载。
排查思路
1. 先看 Pod 事件,不要只看 STATUS
kubectl get pod 只能告诉我们正在退避重试,真正原因在事件里:
kubectl describe pod -n prod api-7d6d9f8f6b-xm2pt
重点看 Events 中的 Failed、Pulling、BackOff 三类记录:
not found、manifest unknown:优先检查镜像 tag 是否存在。unauthorized、denied:优先检查仓库账号和imagePullSecrets。i/o timeout、no such host:优先检查节点网络和 DNS。no space left on device:优先检查节点磁盘和镜像缓存。
如果事件很多,可以按时间排序查看:
kubectl get event -n prod \
--field-selector involvedObject.name=api-7d6d9f8f6b-xm2pt \
--sort-by=.lastTimestamp
2. 确认工作负载实际使用的镜像
不要只看 CI/CD 页面上的变量,应以集群中实际生效的配置为准:
kubectl get deploy -n prod api \
-o jsonpath='{range .spec.template.spec.containers[*]}{.name}{" => "}{.image}{"\n"}{end}'
如果是多容器 Pod,也要定位是哪个容器拉取失败:
kubectl get pod -n prod api-7d6d9f8f6b-xm2pt \
-o jsonpath='{range .status.containerStatuses[*]}{.name}{" => "}{.state.waiting.reason}{" / "}{.state.waiting.message}{"\n"}{end}'
关键字段说明:
.spec.template.spec.containers[*].image是 Deployment 模板中配置的镜像。.status.containerStatuses[*].state.waiting.reason会显示ErrImagePull或ImagePullBackOff。.status.containerStatuses[*].state.waiting.message通常包含仓库返回的原始错误。
3. 检查镜像 tag 是否真的存在
如果公司使用 Harbor 或云镜像仓库,先在仓库页面确认 tag 是否存在。命令行可以在能访问仓库的机器上执行:
docker manifest inspect registry.example.com/prod/api:20260724-1530
返回非 0 通常说明 tag 不存在、没有权限,或客户端无法访问仓库。生产发布建议避免长期使用 latest,因为它无法从 Kubernetes 事件中直接判断本次发布究竟对应哪一次构建。
CI/CD 中也应明确校验镜像推送结果,例如:
docker build -t registry.example.com/prod/api:${BUILD_ID} .
docker push registry.example.com/prod/api:${BUILD_ID}
docker manifest inspect registry.example.com/prod/api:${BUILD_ID}
只有 push 和 manifest inspect 都成功后,才允许执行 kubectl set image 或 Helm 发布。
4. 检查 imagePullSecrets
查看 Pod 是否带上了拉取凭据:
kubectl get pod -n prod api-7d6d9f8f6b-xm2pt \
-o jsonpath='{.spec.imagePullSecrets}{"\n"}'
查看命名空间内 Secret 是否存在:
kubectl get secret -n prod
kubectl describe secret -n prod harbor-registry
如果 Secret 缺失,可以重新创建:
kubectl create secret docker-registry harbor-registry \
-n prod \
--docker-server=registry.example.com \
--docker-username='deploy-user' \
--docker-password='your-password-or-token' \
--docker-email='ops@example.com'
然后在 Deployment 中引用:
kubectl patch deploy -n prod api \
-p '{"spec":{"template":{"spec":{"imagePullSecrets":[{"name":"harbor-registry"}]}}}}'
如果多个工作负载都需要同一个仓库凭据,也可以挂到 ServiceAccount:
kubectl patch serviceaccount -n prod default \
-p '{"imagePullSecrets":[{"name":"harbor-registry"}]}'
注意:Secret 必须和 Pod 在同一个命名空间。把 Secret 创建在 default 命名空间,prod 命名空间中的 Pod 不会自动使用它。
5. 在问题节点上验证网络和容器运行时
事件中如果出现 timeout、no such host、certificate signed by unknown authority,需要到 Pod 被调度的节点上排查。先确认节点名:
kubectl get pod -n prod api-7d6d9f8f6b-xm2pt -o wide
在该节点上检查 DNS、端口和证书链:
getent hosts registry.example.com
curl -vk https://registry.example.com/v2/
如果集群使用 containerd,可以直接用 crictl 验证拉取:
sudo crictl pull registry.example.com/prod/api:20260724-1530
sudo crictl images | grep 'registry.example.com/prod/api'
如果使用 Docker 作为运行时:
sudo docker pull registry.example.com/prod/api:20260724-1530
关键判断:
- 节点无法解析域名:检查节点
/etc/resolv.conf、CoreDNS 上游、云 VPC DNS。 - 节点能解析但连接超时:检查安全组、防火墙、路由、代理。
- 提示证书不可信:检查私有 CA 是否安装到节点和容器运行时信任目录。
- 手工 pull 成功但 kubelet 失败:检查 kubelet/containerd 配置、凭据和镜像地址是否完全一致。
6. 检查节点磁盘和镜像缓存
镜像层下载成功但解压失败时,事件可能出现 no space left on device。在节点上检查:
df -h
df -ih
sudo du -sh /var/lib/containerd /var/lib/docker 2>/dev/null
containerd 环境可以查看镜像:
sudo crictl images
sudo crictl rmi --prune
Docker 环境可以清理无用镜像:
sudo docker system df
sudo docker image prune -a
生产节点清理前要确认是否会影响需要快速回滚的镜像。更稳妥的方式是先扩容磁盘或迁移部分工作负载,再规划镜像 GC 策略。
定位示例
一次生产发布中,api 服务新版本长时间没有 Ready:
kubectl rollout status deploy/api -n prod
输出显示发布超时:
error: deployment "api" exceeded its progress deadline
查看 Pod:
kubectl get pod -n prod -l app=api
状态为:
api-7d6d9f8f6b-xm2pt 0/1 ImagePullBackOff 0 8m
继续看事件:
kubectl describe pod -n prod api-7d6d9f8f6b-xm2pt
关键错误:
Failed to pull image "registry.example.com/prod/api:20260724-1530":
manifest for registry.example.com/prod/api:20260724-1530 not found
再查 Deployment:
kubectl get deploy -n prod api \
-o jsonpath='{.spec.template.spec.containers[0].image}{"\n"}'
确认集群正在使用 20260724-1530 这个 tag。仓库里检查发现 CI 构建阶段成功,但推送镜像阶段因为临时网络错误失败,部署脚本没有拦截 docker push 的失败状态,仍继续执行了 Helm upgrade。
修复方案
临时恢复服务
如果旧版本镜像仍然存在,最快恢复方式是回滚 Deployment:
kubectl rollout undo deploy/api -n prod
kubectl rollout status deploy/api -n prod
也可以显式设置为上一个可用 tag:
kubectl set image deploy/api api=registry.example.com/prod/api:20260723-1800 -n prod
kubectl rollout status deploy/api -n prod
修复发布流水线
CI/CD 中应把镜像推送作为强校验步骤:
set -euo pipefail
IMAGE="registry.example.com/prod/api:${BUILD_ID}"
docker build -t "$IMAGE" .
docker push "$IMAGE"
docker manifest inspect "$IMAGE" >/dev/null
helm upgrade --install api ./charts/api \
-n prod \
--set image.repository=registry.example.com/prod/api \
--set image.tag="${BUILD_ID}" \
--wait \
--timeout 5m
关键点:
set -euo pipefail可以避免前面命令失败后继续发布。docker manifest inspect用来确认仓库中能查到该 tag。- Helm 使用
--wait和--timeout,让发布系统能感知部署是否真正成功。
修复私有仓库凭据
如果错误是 unauthorized,应重新创建或轮换 imagePullSecrets,并触发 Pod 重建:
kubectl delete secret -n prod harbor-registry
kubectl create secret docker-registry harbor-registry \
-n prod \
--docker-server=registry.example.com \
--docker-username='deploy-user' \
--docker-password='new-token' \
--docker-email='ops@example.com'
kubectl rollout restart deploy/api -n prod
kubectl rollout status deploy/api -n prod
如果使用 ServiceAccount 挂载凭据,确认工作负载使用的是同一个 ServiceAccount:
kubectl get deploy -n prod api \
-o jsonpath='{.spec.template.spec.serviceAccountName}{"\n"}'
kubectl get serviceaccount -n prod default -o yaml
预防措施
- 发布使用不可变 tag,例如 Git commit SHA、构建号或日期时间,不依赖
latest。 - CI/CD 在部署前校验镜像已经成功推送到仓库。
- 私有仓库账号使用专用机器人账号,并记录 token 过期时间。
- 给关键命名空间统一配置
imagePullSecrets或专用 ServiceAccount。 - 监控
kube_pod_container_status_waiting_reason{reason="ImagePullBackOff"},出现后及时告警。 - 节点磁盘设置合理的镜像 GC 策略,避免镜像层占满磁盘。
- 多架构集群构建镜像时明确支持
linux/amd64、linux/arm64等目标架构。
Prometheus 告警示例:
groups:
- name: kubernetes-workload
rules:
- alert: KubernetesImagePullBackOff
expr: kube_pod_container_status_waiting_reason{reason="ImagePullBackOff"} == 1
for: 5m
labels:
severity: warning
annotations:
summary: "Pod 镜像拉取失败"
description: "namespace={{ $labels.namespace }}, pod={{ $labels.pod }}, container={{ $labels.container }}"
总结
ImagePullBackOff 的排查入口是 Pod 事件,而不是应用日志。实践中可以按“镜像是否存在、凭据是否有效、节点是否能访问仓库、运行时是否正常、节点磁盘是否可用”这条线逐步收敛。发布系统也要把镜像推送和部署结果做成硬校验,避免镜像没有进入仓库时仍然把 Kubernetes 工作负载更新到一个不可拉取的版本。
Discussion
评论