
在一套使用 kubeadm 部署的生产集群里,我遭遇过一类看似常见却非常“缠人”的镜像拉取失败:ImagePullBackOff。
这个错误折腾得我够呛,所以把排查过程记录下来,方便以后再遇到类似问题,直接返回头来查看。
containerd 1.7.x(通过 systemd 管理)kubeadm 初始化的 Kubernetes 1.29.x,CoreDNS 默认为 1.10+Harbor(自签名 CA),同时透传外网 registry-1.docker.ioCalicocrictl 与 ctr,用于在节点上直接复现实验
业务侧反馈“新发版本的 Pod 起不来”。kubectl get pods 显示 ImagePullBackOff;kubectl describe pod 的 Events 里,有的节点报 x509 相关错误,有的纯超时。
在一台出问题节点上抓到的真实错误消息如下:
Warning Failed kubelet Failed to pull image `harbor.intra.example.com/proj/app:1.2.3`:
rpc error: code = Unknown desc = failed to pull and unpack image `harbor.intra.example.com/proj/app:1.2.3`:
failed to resolve reference `harbor.intra.example.com/proj/app:1.2.3`:
failed to do request: Head https://harbor.intra.example.com/v2/proj/app/manifests/1.2.3:
x509: certificate signed by unknown authority这个错误和 Kubernetes 社区中相同类型问题的报错基本一致(crictl pull 的一条典型输出见这里)(GitHub)。
图片1
为了确认不是镜像名写错或仓库 404,我在节点上直接复现:
# 以 root 在问题节点执行
crictl pull harbor.intra.example.com/proj/app:1.2.3
# 同样命中 x509 unknown authority这一步的动机很直接:把问题从 Kubernetes 面板拉回到节点与运行时层面,避免被 Pod 重试策略掩盖细节。
containerd 的调用栈要证明真的是拉取路径上的 TLS 校验问题,我给 containerd 发了 SIGUSR1,导出当时的 goroutine 栈(这一招可用于 Go 守护进程,触发位置与 dockerd 类似,发送信号后栈会被写进日志)(Docker Documentation):
# 导出 containerd 栈到 journal
kill -SIGUSR1 $(pidof containerd)
journalctl -u containerd --since "1 min ago" | less在另外一个 containerd 拉取异常的公开案例里,也能看到“栈里 goroutine 停在 I/O 等待”的描述(issue 中明确提示用 SIGUSR1 抓栈,并给出 goroutine 34 ... [IO wait] 的栈段落)(GitHub)。
我的现场日志里同样能看到拉取路径卡在 remotes/docker 解析与请求阶段的栈帧,这一证据与 x509 报错可以相互印证。
kubectl describe pod 的 Events,确认不是镜像名打错或没有权限(例如 pull access denied 这类会走另一个分支),而是 TLS 校验失败、或长时间无法连通。社群文章也建议把 describe 作为入口,这点很受用。crictl pull 或 ctr images pull 直接复现,既能避开 Pod 层面的重试,也能拿到更原始的错误串(Baeldung on Kotlin)。containerd 的 registry 配置:是否启用了 hosts 目录模式;是否为目标域名创建了 hosts.toml;是否放置、指向了正确的 CA 文件。官方文档对 hosts.toml 的字段有解释(server、capabilities、skip_verify、ca 等)(Fossies)。K3s / RKE2 / k0s 之类的发行版,registry 的配置文件路径可能不同,要按各自文档写 registries.yaml 或 drop-in,然后重启服务生效(K3s)。journalctl -u containerd 对齐时间窗口,结合 SIGUSR1 抓到的栈,确认拉取请求确实在 TLS 校验阶段失败或网络 I/O 超时(journalctl 的过滤姿势可以参考这几篇,用 --since、-u 过滤)(BetterStack)。我的修复目标是:让所有节点对 Harbor 的自签名 CA 达成一致信任,并避免临时 skip_verify 带来的合规风险。以下提供两条路径:一条是 containerd 官方 hosts.toml 路径;另一条是 K3s / RKE2 用户更常用的 registries.yaml。
containerd hosts 目录模式containerd 读取 certs.d 目录(config.toml 中的开关),官方建议的写法如下(注意这里的 config_path 指向目录):(Gardener)# /etc/containerd/config.toml
version = 2
[plugins.'io.containerd.grpc.v1.cri'.registry]
config_path = '/etc/containerd/certs.d'hosts.toml,并放入 CA:sudo mkdir -p /etc/containerd/certs.d/harbor.intra.example.com
sudo cp /root/ca/harbor-ca.crt /etc/containerd/certs.d/harbor.intra.example.com/harbor-ca.crt# /etc/containerd/certs.d/harbor.intra.example.com/hosts.toml
server = 'https://harbor.intra.example.com'
[host.'https://harbor.intra.example.com']
capabilities = ['pull', 'resolve', 'push']
ca = ['/etc/containerd/certs.d/harbor.intra.example.com/harbor-ca.crt']
skip_verify = false上面的字段与容器运行时文档一致,其中 skip_verify 只在临时应急时可考虑开启,长期应通过 CA 信任来解决。关于 hosts.toml 的字段含义与 hosts 目录模式,可以参考 upstream 文档与社区答复(Fossies)。
containerd。多数情况下需要重启服务才能生效(是否支持热加载与版本有关,实践上稳妥的做法是重启)(Stack Overflow):sudo systemctl restart containerdK3s / RKE2 的 registries.yaml如果你的发行版内置了更高层的封装(例如 K3s),可以直接写:(K3s)
# /etc/rancher/k3s/registries.yaml
mirrors:
docker.io:
endpoint:
- 'https://harbor.intra.example.com'
configs:
'harbor.intra.example.com':
tls:
ca_file: /etc/containerd/certs.d/harbor.intra.example.com/harbor-ca.crt
auth:
username: your-username
password: your-password保存后重启 k3s 服务即可使配置下发到 containerd:(K3s)
sudo systemctl restart k3s准备一个极简 Deployment,使用私有仓库镜像;如需认证,搭配 imagePullSecrets。下面全部为合法 YAML,且不依赖英文双引号。
# 如需认证,先创建拉取凭据
kubectl create secret docker-registry regcred \
--docker-server=harbor.intra.example.com \
--docker-username='your-username' \
--docker-password='your-password' \
-n demo# demo-ns.yaml
apiVersion: v1
kind: Namespace
metadata:
name: demo
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: whoami
namespace: demo
spec:
replicas: 1
selector:
matchLabels:
app: whoami
template:
metadata:
labels:
app: whoami
spec:
imagePullSecrets:
- name: regcred
containers:
- name: whoami
image: harbor.intra.example.com/proj/whoami:1.0.0
ports:
- containerPort: 80应用并观察:
kubectl apply -f demo-ns.yaml
kubectl -n demo get pods -w
kubectl -n demo describe pod deploy/whoami修复成功后,describe 的 Events 会显示 Pulled 与 Created,而非 ImagePullBackOff。这一步的思路与常见排障文章一致。
Secret、同样的 Pod,只有部分节点报错?这就是这类问题“复杂”的地方:镜像拉取最终发生在节点运行时。
Kubernetes 中的 imagePullSecrets 解决的是认证,而是否信任服务器证书由节点的 containerd 与宿主信任库决定。
只要不同节点对 CA 处理不一致,就会出现“有的节点能拉,有的节点报 x509”的情况。社区里关于 x509: certificate signed by unknown authority 的案例,也都有“把 CA 放到每个节点特定目录并重启运行时”的共识(DevOps Stack Exchange)。
certificate signed by unknown authority 或 x509 其它变体。要么让 containerd 信任代理的根证书,要么为目标域名直连绕过代理。Subject Alternative Name 不一致,x509 同样会失败。no matching manifest for linux/amd64 in the manifest list entries,那就不是证书问题,而是镜像没有你这台节点的架构清单;可以用 --platform 拉取或重建多架构镜像(这类报错也会把 Pod 打到 ImagePullBackOff,但事件内容不同)。containerd,更稳妥(Stack Overflow)。SIGUSR1 抓栈:Go 守护进程(如 dockerd)约定俗成地支持用信号导出栈;containerd 也在多个 issue 与工程实践中用相同手法抓过栈,有助于确认是否卡在 I/O(示例与方法参考)(Docker Documentation)。hosts 目录模式,/etc/containerd/certs.d/harbor.intra.example.com/hosts.toml 指向最新 harbor-ca.crt,禁用 skip_verify。配置语义与官方文档一致(Fossies)。K3s 与 RKE2 环境编写 registries.yaml 下发方案,避免每台机手动改 config.toml,并在运维手册中明确“证书更新需同步重启服务”(K3s)。Harbor 访问不被透明代理破坏。crictl pull harbor/... 验证证书信任与带宽连通,写进交付流水线。ImagePullBackOff 是表象,即时查看 describe 里的 Events 才能读到根因(超时、鉴权、证书、DNS、平台架构等路径不同,处理方式完全不一样)。imagePullSecrets 只负责账号口令,TLS 信任要交给节点运行时。当你看到 x509,就去看 containerd 的 certs.d 与宿主信任库。ImagePullBackOff。skip_verify:这是应急手段。长期要么配 CA,要么改用可信证书。社区答复强调了 hosts.toml 的 skip_verify 行为,但也有版本差异与坑(例如部分版本对某些情景忽略 skip_verify)(Stack Overflow)。containerd 发 SIGUSR1,配合 journalctl -u containerd 检索时间窗口,可以定位是否卡在解析与 I/O。相关方法与实践可参考这些资料(Docker Documentation)。crictl pull 典型的 x509 报错示例(与现场一致)(GitHub)kubectl describe 作为入口定位 ImagePullBackOff 的思路与图示containerd hosts.toml 与 hosts 目录模式的官方说明与社区答复(Fossies)K3s 私有仓库配置 registries.yaml(含 TLS 与认证样例)(K3s)SIGUSR1 导出堆栈的方法与 containerd 的相关 issue 说明(Docker Documentation)ImagePullBackOff 场景截图与说明(便于对照你自己的 Events)如果大家也在公司内网里用自建 Harbor,强烈建议把“节点信任仓库 CA 的一致性检查”加入日常巡检脚本,并在证书续期流水线上触发全节点的配置刷新与运行时重启。这不是 Kubernetes 的“基础语法问题”,而是节点运行时配置一致性与证书生命周期管理的工程问题。只要这个工程问题处理好了,ImagePullBackOff 这类事故的概率会显著下降。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。