运维笔记

Kubernetes ImagePullBackOff 根因排查与修复:从 kubectl describe 到私有仓库认证的完整实战指南

Cloud & DevOps 技术可视化

这破事儿到底啥意思?

ImagePullBackOff —— 你要是跑过 Kubernetes 生产环境,大概率见过这玩意儿。Pod 状态卡在 ImagePullBackOff,然后 kubelet 会不断重试拉取镜像,每次重试间隔会指数级增长(backoff)。10秒、20秒、40秒……直到你疯掉或者找到问题。

我见过不少团队在这上面翻车。有个哥们儿在 Reddit 上吐槽,说他们的 CI 突然全挂了,所有新部署的 Pod 全部 ImagePullBackOff,排查了半天发现是镜像仓库的 token 过期了。这种事儿太常见了。

症状描述

Pod 状态长这样:

$ kubectl get pods
NAME                     READY   STATUS              RESTARTS   AGE
my-app-7d4f8b6c9-x2k9j   0/1     ImagePullBackOff     0          5m

kubectl describe pod 能看到更详细的信息:

$ kubectl describe pod my-app-7d4f8b6c9-x2k9j
...
Events:
  Type     Reason     Age                   From               Message
  ----     ------     ----                  ----               -------
  Normal   Scheduled  6m10s                 default-scheduler  Successfully assigned...
  Normal   Pulling    5m50s (x4 over 6m10s)  kubelet            Pulling image "myregistry.io/my-app:v1.0.0"
  Warning  Failed     5m50s (x4 over 6m10s)  kubelet            Failed to pull image "myregistry.io/my-app:v1.0.0": rpc error: code = Unknown desc = Error response from daemon: manifest for myregistry.io/my-app:v1.0.0 not found: manifest unknown: manifest unknown
  Warning  BackOff    5m35s (x7 over 6m5s)   kubelet            Back-off pulling image "myregistry.io/my-app:v1.0.0"

注意那个 Back-off pulling image —— 这就是 kubelet 在告诉你:“我试了,没成功,我先歇会儿再试。”

根因分析:到底是谁的锅?

我根据过去几年在生产环境踩过的坑,把 ImagePullBackOff 的根因分了四大类:

根因分类典型错误信息发生频率
镜像名称/标签错误manifest unknownnot found极高
私有仓库认证失败authentication requireddenied
网络/仓库不可达dial tcp: lookupconnection refused
镜像拉取策略/配额ImagePullBackOff 无具体错误

1. 镜像名称或标签写错了

这是最常见的坑。我见过有人把 nginx:latest 写成 nginix:lates,然后盯着屏幕看了十分钟。

# 错误示例
containers:
- name: my-app
  image: myregistry.io/my-app:v1.0.0  # 这个 tag 不存在!

排查命令:

# 先手动拉一下试试
$ docker pull myregistry.io/my-app:v1.0.0
Error response from daemon: manifest for myregistry.io/my-app:v1.0.0 not found: manifest unknown

# 看看仓库里到底有啥 tag
$ skopeo list-tags docker://myregistry.io/my-app
{
    "Repository": "myregistry.io/my-app",
    "Tags": [
        "v1.0.1",
        "v1.1.0",
        "latest"
    ]
}

看到了吧?v1.0.0 根本不存在。这种问题在 CI/CD 流程里特别容易出 —— 镜像还没 push 完,kubelet 就开始拉了。

2. 私有仓库认证炸了

这玩意儿坑过我们团队两次。一次是 Harbor 的 robot account 过期了,另一次是 ECR 的 token 刷新逻辑写错了。

$ kubectl describe pod my-app
...
Failed to pull image "my-private-registry.io/my-app:latest": 
rpc error: code = Unknown desc = Error response from daemon: pull access denied for my-private-registry.io/my-app, repository does not exist or may require 'docker login': denied

排查步骤:

# 检查当前使用的 imagePullSecrets
$ kubectl get pod my-app -o yaml | grep -A 5 imagePullSecrets
imagePullSecrets:
- name: my-registry-cred

# 看看这个 secret 是不是还活着
$ kubectl get secret my-registry-cred -o yaml
# 检查 dockerconfigjson 里的 creds 是否已过期

# 重新创建 secret(以 Docker Hub 为例)
$ kubectl create secret docker-registry my-registry-cred \
  --docker-server=https://index.docker.io/v1/ \
  --docker-username=myuser \
  --docker-password=mypassword \
  --docker-email=myemail@example.com

一个更隐蔽的坑: 如果你用 ECR,token 每 12 小时过期。很多团队忘了写 token 刷新逻辑,结果半夜 Pod 全挂了。我们后来用 k8s-at-home/ecr-token-refresh 这个 sidecar 解决了这个问题。

3. 网络问题

这个比较少见,但一旦出现就是大问题。比如集群节点没法访问镜像仓库。

$ kubectl describe pod my-app
...
Failed to pull image "myregistry.io/my-app:latest": 
rpc error: code = Unknown desc = Error response from daemon: Get "https://myregistry.io/v2/": dial tcp: lookup myregistry.io on 10.0.0.10:53: no such host

排查命令:

# 进到节点上测试 DNS
$ kubectl debug node/worker-node-1 -it --image=busybox
# 在 debug pod 里
/ # nslookup myregistry.io
Server:    10.0.0.10
Address:   10.0.0.10:53
** server can't find myregistry.io: NXDOMAIN

# 测试网络连通性
/ # wget -O- https://myregistry.io/v2/
wget: bad address 'myregistry.io'

网络问题常见于:

  • 自建仓库没有配置正确的 DNS
  • 私有网络没有打通(VPC peering / NAT 网关配置错误)
  • 镜像仓库做了 IP 白名单,但集群节点 IP 不在白名单里

4. 镜像拉取策略

imagePullPolicy 这个字段很多人不注意。默认情况下:

  • 如果 tag 是 latest,策略是 Always
  • 如果 tag 是具体的版本号,策略是 IfNotPresent

但如果你改了策略,可能会导致奇怪的问题。比如你设了 imagePullPolicy: Always,但仓库里其实没有这个 tag,kubelet 就会一直尝试拉取。

containers:
- name: my-app
  image: my-app:latest
  imagePullPolicy: Always  # 每次都会尝试拉取

修复步骤:一套标准的排查流程

这是我个人的排查流程,踩过无数次坑后总结出来的:

Step 1: 查看 Pod 状态和事件

$ kubectl get pods --all-namespaces | grep -E "ImagePullBackOff|ErrImagePull"
$ kubectl describe pod <pod-name> -n <namespace>

重点关注 Events 部分的 Failed 消息。这里会直接告诉你拉取失败的原因。

Step 2: 手动拉取镜像验证

# 在任意一个集群节点上
$ docker pull <your-image>:<tag>

如果 docker 能拉下来,但 kubelet 拉不下来,那问题大概率在认证或网络层面。

Step 3: 检查 imagePullSecrets

$ kubectl get pod <pod-name> -o yaml | grep -A 10 imagePullSecrets
$ kubectl get secret <secret-name> -o yaml | head -20

确认 secret 存在且内容正确。可以用 base64 解码看看:

$ kubectl get secret <secret-name> -o jsonpath="{.data.\.dockerconfigjson}" | base64 -d

Step 4: 检查镜像仓库是否可达

# 创建一个 debug pod
$ kubectl run debug --image=busybox -it --rm --restart=Never -- wget -O- https://your-registry.com/v2/

Step 5: 检查镜像是否存在

# 用 skopeo 或 crane 检查
$ crane manifest <your-image>:<tag>
# 或者
$ skopeo inspect docker://<your-image>:<tag>

社区里大家都在吐槽什么

最近在 Reddit 和 Hacker News 上看到不少关于 ImagePullBackOff 的讨论。

有个帖子讲的是他们团队迁移到新的镜像仓库后,CI 一直报 ImagePullBackOff。排查了两天,发现是 kubelet 的 --pod-infra-container-image 参数指向了旧仓库。这个问题在升级 Kubernetes 版本时特别常见。

另一个哥们儿吐槽说他们的 EKS 集群突然大面积出现 ImagePullBackOff,原因是 AWS ECR 的认证 token 过期了,但他们用的 kubelet 版本不支持自动刷新。最后他们写了个 cronjob 每 6 小时刷新一次 token。

还有人提到 Docker Hub 的拉取限制 —— 匿名用户每 6 小时只能拉 100 次。如果你用 imagePullPolicy: Always,Pod 多了很容易触发这个限制。解决方案是换成 IfNotPresent 或者配一个 Docker Hub 的 pull secret。

进阶:自动化排查脚本

我写了个小脚本,一键排查集群里所有 ImagePullBackOff 的 Pod:

#!/bin/bash
# k8s-imagepullbackoff-checker.sh

echo "=== 检查 ImagePullBackOff Pod ==="
kubectl get pods --all-namespaces | grep -E "ImagePullBackOff|ErrImagePull" | while read ns name rest; do
    echo "Pod: $ns/$name"
    echo "--- 事件 ---"
    kubectl describe pod $name -n $ns | grep -A 10 "Events:" | tail -20
    echo ""
done

FAQ

Q: ImagePullBackOffErrImagePull 有什么区别?

A: ErrImagePull 是第一次拉取失败时的状态,ImagePullBackOff 是失败后 kubelet 进入退避重试状态。前者是尝试拉取但失败了,后者是"我先歇会儿再试"。

Q: kubelet 重试 ImagePullBackOff 的间隔是多少?

A: 默认从 10 秒开始,每次翻倍,上限 5 分钟。计算公式是 min(5min, 10s * 2^(retry_count-1))。如果你删掉 Pod 重新创建,计数器会重置。

Q: 为什么我的 imagePullSecrets 配置了但还是认证失败?

A: 常见原因:1) Secret 所在的 namespace 不对(secret 必须和 Pod 在同一个 namespace);2) Secret 内容已过期(尤其是 ECR token);3) Pod 的 spec 里没有引用这个 secret。

Q: 镜像拉取失败会影响集群的其他 Pod 吗?

A: 不会。ImagePullBackOff 只影响出问题的那个 Pod。但如果你用 DaemonSet 或 StatefulSet,可能会影响整个应用的可用性。

Q: 如何避免 ImagePullBackOff

A: 1) CI/CD 流程里加镜像验证步骤;2) 配置 imagePullSecrets 自动刷新;3) 用 imagePullPolicy: IfNotPresent 减少拉取次数;4) 监控镜像仓库的可用性和认证状态。

参考与社区洞察

本文的技术观点综合自以下平台的真实工程讨论:Hacker News、Reddit 的 r/kubernetes 和 r/devops 社区。特别感谢那些在生产环境踩过坑还愿意分享的工程师们。

Elvin Hui

关于作者:Elvin Hui

Elvin 拥有 10+ 年企业级数据中心、云原生架构和网络安全经验。持有 CCNA、AWS 解决方案架构师认证。我致力于将一线的“踩坑”经验沉淀为真实、硬核的技术指南,拒绝空洞理论。