适用场景

这篇文章适用于 Pod 一直停留在 PendingErrImagePullImagePullBackOff,业务发布后没有新实例可用的场景。常见环境包括自建 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 的根因一般集中在以下几类:

  1. 镜像名或 tag 写错,仓库中根本不存在该镜像。
  2. 私有仓库需要登录,但 Pod 没有配置 imagePullSecrets,或 Secret 已过期。
  3. 节点无法访问镜像仓库,例如 DNS 解析失败、防火墙拦截、代理配置缺失、证书不被信任。
  4. 镜像架构与节点架构不匹配,例如 arm64 节点拉取了只有 amd64 的镜像。
  5. 节点磁盘空间不足,容器运行时无法解压镜像层。
  6. CI/CD 推送镜像失败,但后续部署步骤仍然更新了工作负载。

排查思路

1. 先看 Pod 事件,不要只看 STATUS

kubectl get pod 只能告诉我们正在退避重试,真正原因在事件里:

kubectl describe pod -n prod api-7d6d9f8f6b-xm2pt

重点看 Events 中的 FailedPullingBackOff 三类记录:

  • not foundmanifest unknown:优先检查镜像 tag 是否存在。
  • unauthorizeddenied:优先检查仓库账号和 imagePullSecrets
  • i/o timeoutno 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 会显示 ErrImagePullImagePullBackOff
  • .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}

只有 pushmanifest 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. 在问题节点上验证网络和容器运行时

事件中如果出现 timeoutno such hostcertificate 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

预防措施

  1. 发布使用不可变 tag,例如 Git commit SHA、构建号或日期时间,不依赖 latest
  2. CI/CD 在部署前校验镜像已经成功推送到仓库。
  3. 私有仓库账号使用专用机器人账号,并记录 token 过期时间。
  4. 给关键命名空间统一配置 imagePullSecrets 或专用 ServiceAccount。
  5. 监控 kube_pod_container_status_waiting_reason{reason="ImagePullBackOff"},出现后及时告警。
  6. 节点磁盘设置合理的镜像 GC 策略,避免镜像层占满磁盘。
  7. 多架构集群构建镜像时明确支持 linux/amd64linux/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 工作负载更新到一个不可拉取的版本。