在 kind 集群里复现镜像拉取失败,练习区分 ErrImagePull、ImagePullBackOff 与私有仓库鉴权

演练目标

环境准备

隔离边界:使用独立的 kind 集群和一个临时 registry:2 容器,端口只映射到本机回环地址;不要在企业 registry 上推测试镜像,也不要修改节点 Docker/containerd 全局配置。确保 kind、kubectl、docker 可用,预留约 1 GB 内存和 2 GB 磁盘。

docker info --format '{{.ServerVersion}}' && kind version
mkdir -p /tmp/lab-pull/auth
printf 'labuser:%s\n' "$(openssl passwd -apr1 labpass)" > /tmp/lab-pull/auth/htpasswd

启动需要认证的 registry,并接入 kind 网络:

docker run -d --name lab-registry -p 127.0.0.1:5001:5000 -v /tmp/lab-pull/auth:/auth \
  -e REGISTRY_AUTH=htpasswd -e REGISTRY_AUTH_HTPASSWD_REALM=lab \
  -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd registry:2

kind create cluster --name lab-pull && docker network connect kind lab-registry
docker login 127.0.0.1:5001 -u labuser -p labpass

接入 kind 网络后,节点可以用容器名 lab-registry 解析到 registry。

注入故障

先推送一个测试镜像,然后创建两个 Pod:一个使用不存在的 tag,一个指向私有仓库但没有 imagePullSecrets。

docker pull nginx:1.27-alpine
docker tag nginx:1.27-alpine 127.0.0.1:5001/private/app:1.0 && docker push 127.0.0.1:5001/private/app:1.0

kubectl create namespace lab-pull && kubectl -n lab-pull run bad-tag --image=nginx:1.27-no-such-tag
kubectl -n lab-pull run private-no-secret --image=lab-registry:5000/private/app:1.0

等待约 30 秒,让 kubelet 进入退避:

sleep 30
kubectl -n lab-pull get pods

观察现象

kubectl -n lab-pull describe pod bad-tag | sed -n '/Events:/,$p'
kubectl -n lab-pull describe pod private-no-secret | sed -n '/Events:/,$p'

预期 bad-tag 的 Events 先出现 ErrImagePull,失败原因是 manifest unknown 或 not found,随后变成 ImagePullBackOff;private-no-secret 出现 401 Unauthorized,同样从 ErrImagePull 进入 ImagePullBackOff。ImagePullBackOff 是 kubelet 在多次失败后按指数退避重试的状态,不代表错误类型变了。

在 kind 节点上验证 DNS 和端口可达性:

docker exec lab-pull-control-plane sh -c \
  'getent hosts lab-registry && timeout 5 bash -c "echo > /dev/tcp/lab-registry/5000" && echo tcp-ok'

输出 registry IP 和 tcp-ok,说明节点到 registry 的 DNS 和端口是通的,失败点在认证而不是网络。若解析失败或连接超时,问题在节点网络,不在镜像 tag。

判断与定位

  1. 先写假设:tag 不存在、仓库需要认证、节点无法解析仓库域名,还是节点到仓库的网络不通。每种假设对应不同验证命令。
  2. 看 Events 的 Reason 和 Message。manifest unknown 指向 tag/仓库路径;401 Unauthorized 指向 secret;no such host、i/o timeout 指向 DNS 或网络。
  3. 检查 Pod 的 imagePullSecrets 是否真的存在且 namespace 一致。Secret 必须和 Pod 同 namespace,名字写错时事件会显示 FailedToRetrieveImagePullSecret。节点侧可用 crictl images 和 crictl pull 复现拉取错误。
  4. 容易误判:看到 ImagePullBackOff 就以为 tag 写错。它只是“拉取失败并退避”,认证失败、网络超时、仓库限流都会进入同一状态。另一个误判是把 ErrImagePull 当成另一种故障;它通常只是首次失败,退避后就显示为 ImagePullBackOff。只有部分节点失败时,优先比较节点网络和镜像缓存。

修复

为私有仓库创建 docker-registry 类型的 Secret,并在 Pod 中引用:

kubectl -n lab-pull create secret docker-registry regcred \
  --docker-server=lab-registry:5000 --docker-username=labuser --docker-password=labpass

kubectl -n lab-pull delete pod private-no-secret
kubectl -n lab-pull run private-with-secret \
  --image=lab-registry:5000/private/app:1.0 \
  --overrides='{"spec":{"imagePullSecrets":[{"name":"regcred"}]}}'

把 bad-tag 改成真实存在的 tag:

kubectl -n lab-pull set image pod/bad-tag bad-tag=nginx:1.27-alpine
kubectl -n lab-pull wait --for=condition=Ready pod/bad-tag pod/private-with-secret --timeout=120s

两个 Pod 都变为 Running 且 READY 1/1 即修复。生产中还应把 imagePullSecrets 挂到 ServiceAccount,避免每个 Pod 手写。

清理与回滚

kubectl -n lab-pull delete pod bad-tag private-no-secret private-with-secret --ignore-not-found
kubectl delete namespace lab-pull --ignore-not-found
kind delete cluster --name lab-pull
docker rm -f lab-registry && docker logout 127.0.0.1:5001
rm -rf /tmp/lab-pull

只删除本实验创建的 kind 集群和 registry 容器;不要执行 docker system prune。

复盘

延伸阅读

读完自测

  1. ErrImagePull 和 ImagePullBackOff 是同一种错误吗?
  2. Pod 引用了不存在的 imagePullSecret,Events 会给出什么线索?
  3. 节点能解析仓库域名但返回 401,应该先改 tag 还是检查凭据?
答案
  1. 不是;ErrImagePull 是某次拉取失败,ImagePullBackOff 是失败后进入指数退避重试的状态,根因要看事件 Message。
  2. 会出现 FailedToRetrieveImagePullSecret 或无法拉取 secret 的事件,先确认 secret 名称和 namespace。
  3. 先检查凭据和 imagePullSecrets;401 是认证问题,不是 tag 不存在的问题。