registry.k8s.io 镜像拉不下来怎么办:kubeadm 初始化、ImagePullBackOff 与 containerd 镜像配置全解
在国内搭 Kubernetes 集群,最先遇到的不是网络插件也不是证书,而是镜像。kubeadm init 在预检之后会拉取 kube-apiserver、kube-controller-manager、kube-scheduler、kube-proxy、etcd、coredns 和 pause 一共七个镜像,默认地址都在 registry.k8s.io。这个域名背后会按请求来源重定向到不同云厂商的存储,从国内访问经常超时,终端里一行「pulling images」能停十几分钟,最后报「failed to pull image」和「context deadline exceeded」。
更麻烦的是,镜像问题不止出现一次。初始化时改了 kubeadm 的镜像仓库,容器运行时 containerd 却还有自己的一份 pause 镜像配置;控制面起来了,部署 ingress-nginx、metrics-server 这些组件时又会去拉 registry.k8s.io 上的其他镜像;新加入的工作节点又是一台没有缓存的干净机器。下面先讲清镜像的来源与几种获取方式,再按部署顺序给出步骤和报错对照。旧地址 k8s.gcr.io 已经冻结不再更新,新版本组件只发布在新地址上,以官方最新说明为准。
Kubernetes 镜像来源与几种获取方式
单机实验环境用「替换镜像仓库」最快;生产环境更推荐运行时代理或内部镜像仓库,因为它们不需要改动任何清单文件里的镜像地址,升级组件时也不用担心同步仓库缺版本。几种方式可以组合,比如控制面镜像走同步仓库、业务镜像走内部仓库。
| 方式 | 原理 | 优点 | 局限 |
|---|---|---|---|
| 直连 registry.k8s.io | 通过线路访问官方仓库 | 版本最全,与文档一致 | 依赖线路质量 |
| 替换为国内同步仓库 | kubeadm 指定镜像仓库地址 | 初始化快 | 同步可能滞后,只覆盖常用镜像 |
| containerd 镜像加速 | 运行时层面把仓库映射到镜像站 | 对所有 Pod 生效 | 需要逐节点配置 |
| 运行时代理 | 给 containerd 服务设置代理变量 | 镜像地址不用改 | 要处理好集群内网段排除 |
| 离线导入 | 提前导出镜像包再导入节点 | 不依赖外网 | 版本升级要重新导出 |
| 内部镜像仓库 | Harbor 等仓库统一代理缓存 | 团队共享、可审计 | 需要维护一套服务 |
从初始化到部署组件:分步操作步骤
- 第一步:先把网络线路准备好。在一台能连上刺猬VPN的机器上确认 registry.k8s.io 可以访问,客户端开全局模式时用 curl -I https://registry.k8s.io/v2/ 验证返回状态;服务器上如果不走全局线路,就给容器运行时单独配置代理,见第四步;
- 第二步:列出要拉的镜像。执行 kubeadm config images list,加上 --kubernetes-version 指定版本,记下全部镜像名与标签,后面离线导入或核对同步仓库都要用到;
- 第三步:用同步仓库初始化。在 kubeadm 的配置文件里写 imageRepository,或者在命令行加 --image-repository,常见的国内同步仓库是云厂商提供的 google_containers 命名空间,地址以其官方说明为准,然后执行 kubeadm config images pull 预拉取;
- 第四步:给 containerd 配代理。在 systemd 的服务配置目录下为 containerd 新建一个代理配置片段,写入 HTTPS_PROXY 与 NO_PROXY,NO_PROXY 必须包含 localhost、节点网段、Pod 网段和 Service 网段,保存后执行 systemctl daemon-reload 并重启 containerd;
- 第五步:修正 pause 镜像。打开 containerd 的配置文件,找到 sandbox_image 一项,把地址改成与 kubeadm 一致的仓库和版本号,重启 containerd,这一步漏掉是 Pod 卡在 ContainerCreating 的头号原因;
- 第六步:配置镜像加速。在 containerd 配置里指定 config_path,然后在对应目录下为 registry.k8s.io、docker.io 分别建立 hosts.toml,写入镜像站地址,并保留官方地址作为后备;
- 第七步:执行 kubeadm init,完成后用 crictl images 核对七个镜像都已就位,再安装网络插件;
- 第八步:工作节点加入前,在每个节点上重复第四到第六步,或者把镜像离线导入,否则 kube-proxy 与网络插件的 Pod 会在新节点上拉取失败。
按现象排查:拉取、启动与组件部署
Kubernetes 的镜像报错会层层包装,先用 kubectl describe pod 看事件,再到节点上用 crictl 复现。
- kubeadm init 报 failed to pull image 并带有 i/o timeout:控制面镜像拉不下来,确认线路或改用同步仓库;
- Pod 一直 ContainerCreating,事件里有 failed to get sandbox image:containerd 的 pause 镜像地址没改,按第五步修正;
- Pod 状态 ImagePullBackOff,事件里是 not found:同步仓库没有这个版本,换官方地址拉取或降低版本号;
- Pod 状态 ErrImagePull,事件里是 x509 错误:镜像站证书不受信任,或公司网络替换了证书,在 hosts.toml 里配置证书或换镜像站;
- 配置代理后集群内部通信失败,节点之间 Pod 无法访问:NO_PROXY 漏了 Pod 网段或 Service 网段,补齐后重启 containerd;
- crictl pull 能成功但 Pod 仍然拉取失败:Pod 清单里的镜像地址与你拉的不完全一致,核对仓库前缀和标签;
- Helm 装 ingress-nginx 时控制器镜像拉不下:图表里默认引用官方仓库,在 values 文件里覆盖镜像仓库地址,或走运行时代理。
几种方案怎么选
选择的核心是「以后怎么升级」。同步仓库省事,但新版本发布后可能要等一段时间才同步;运行时代理配置一次就和官方保持一致;离线导入适合完全隔离的环境,但每次升级都要重新打包。实际可用性取决于当前网络环境。
团队规模稍大时,比较稳妥的架构是在内网部署一套镜像仓库,开启对官方仓库的代理缓存功能,只有这台仓库服务器需要访问境外地址,所有节点的 containerd 都把镜像加速指向它。这样一来,同一个镜像无论多少台节点需要,都只从境外下载一次;仓库还会记录谁在什么时间拉了什么镜像,方便安全审计。仓库服务器本身的代理配置只需要维护一份,节点上不再需要任何代理变量,也就不存在网段排除写漏的问题。
| 对比项 | 同步仓库 | 运行时代理 | 离线导入 |
|---|---|---|---|
| 配置成本 | 低,一个参数 | 中,逐节点配置 | 高,每次升级重做 |
| 版本完整度 | 常用版本齐全 | 与官方一致 | 只有导出的版本 |
| 对清单改动 | 要改部分镜像地址 | 无需改动 | 无需改动 |
| 适合场景 | 学习与实验集群 | 长期运行的集群 | 无外网的内网环境 |
| 线路需求 | 不需要 | 会员节点有效期内不限流量、不限速 | 导出时需要一次 |
| 免费节点 | 不涉及 | 永久免费、不限流量、不限速,适合先验证 | 可用于导出 |
本地开发集群的差别
minikube、kind、k3s 这些本地集群各有自己的镜像获取方式。minikube 启动时可以指定镜像仓库地址,节点镜像本身也要下载;kind 的节点是一个包含全部组件的容器镜像,第一次创建集群时要先拉下这个较大的节点镜像,之后的 Pod 镜像可以用 kind load 命令从本机直接导入集群;k3s 自带 containerd,镜像加速写在它自己的仓库配置文件里,而不是系统的 containerd 配置。
Docker Desktop 自带的 Kubernetes 则通过 Docker Desktop 的设置来配置代理,开启 Kubernetes 时会一次性拉取全部组件镜像,失败后界面会一直显示「Starting」。这种情况下先在 Docker Desktop 的「Resources」→「Proxies」里配置好代理,再重置 Kubernetes 集群,比反复点重试有效得多。
升级集群时的镜像检查顺序
这套顺序的意义在于把「拉镜像」从升级过程中剥离出来。升级本身只需要几分钟,真正耗时且容易失败的是镜像拉取,提前做完就能把风险集中在可控的时间段处理。生产集群建议先在测试集群完整演练一遍,记录每一步用到的镜像与命令,再照着清单操作。
- 先查清单:用新版本号执行一次镜像列表命令,与当前版本对比,找出这次升级新增或变更的镜像,通常是控制面四个组件、coredns 和 pause;
- 再查来源:逐个确认这些镜像在你的来源里能拉到,同步仓库要特别留意新版本是否已经同步,可以先在一台节点上用 crictl pull 试拉;
- 然后预拉:在每个节点上提前把新版本镜像拉好,控制面节点执行预拉命令,工作节点至少把 kube-proxy 与 pause 拉好,升级时就不会因为拉取超时卡住;
- 接着改配置:如果 pause 镜像的版本号变了,同步修改 containerd 的 sandbox_image 并重启,否则升级完成后新建的 Pod 会卡在创建阶段;
- 最后升级:按先控制面、后工作节点的顺序逐台升级,每台完成后用 kubectl get nodes 确认状态为 Ready,再处理下一台;
- 顺手清理:升级稳定运行几天后,用 crictl rmi 清掉旧版本镜像,避免节点磁盘被历史镜像占满。
与容器工具的分工
Kubernetes 只解决 registry.k8s.io 上的组件镜像,业务镜像多数还在 Docker Hub,匿名拉取的频率限制、镜像加速器失效等问题见 Docker Hub 访问与拉取限制。本地构建和调试镜像时选 Docker 还是 Podman,二者对代理和镜像仓库配置文件的写法不同,见 Podman 与 Docker 怎么选。
层次是这样的:构建工具负责把镜像做出来,镜像仓库负责存放与分发,containerd 负责在节点上拉取与运行。配置代理和镜像站时,三层各有各的配置文件,改哪一层要先想清楚。
常见报错速查表
| 现象 | 原因 | 解决办法 |
|---|---|---|
| failed to pull image 加 i/o timeout | 官方仓库不可达 | 走线路或改用同步仓库 |
| failed to get sandbox image | pause 镜像地址未改 | 修改 sandbox_image 后重启 |
| ImagePullBackOff 加 not found | 同步仓库缺版本 | 换官方地址或调整版本 |
| ErrImagePull 加 x509 | 镜像站证书不受信任 | 配置证书或更换镜像站 |
| Pod 跨节点不通 | NO_PROXY 漏了网段 | 补齐 Pod 与 Service 网段 |
| context deadline exceeded | 拉取超时 | 检查线路后重试 |
| Docker Desktop 一直 Starting | 组件镜像拉取失败 | 配置代理后重置集群 |
高频问答
- 问:k8s.gcr.io 还能用吗?答:旧地址已冻结,不再发布新版本,新部署统一用 registry.k8s.io,以官方最新说明为准;
- 问:只改 kubeadm 的镜像仓库够吗?答:不够,containerd 的 pause 镜像和后续组件的镜像都要单独处理;
- 问:同步仓库安全吗?答:镜像带有摘要,可以和官方摘要核对,生产环境建议用内部仓库统一代理;
- 问:代理会不会影响集群内部通信?答:NO_PROXY 写全节点、Pod 与 Service 网段就不会;
- 问:离线导入用什么命令?答:在有网络的机器上导出镜像包,在节点上用 ctr 指定 k8s.io 命名空间导入;
- 问:升级集群要注意什么?答:先确认新版本的镜像在你的镜像来源里都能拉到,再执行升级,并且提前在每个节点上预拉好镜像,避免升级过程中因为拉取超时而中断,具体顺序见上文的检查清单。
让集群镜像来源长期稳定
registry.k8s.io 镜像拉不下来,本质是集群里有三四个地方各自在拉镜像:kubeadm 的控制面、containerd 的 pause、组件清单与业务 Pod。把这几处统一指向同一个来源,要么同步仓库,要么运行时代理加镜像加速,要么内部仓库,ImagePullBackOff 和 ContainerCreating 就不会反复出现。新节点加入前用脚本统一下发配置,比事后逐台排查省力得多。
刺猬VPN 支持自定义用户名和密码匿名注册,不需要邮箱、也不需要手机号,采用 HTTPS+AES 加密传输;免费节点永久免费、不限流量、不限速,适合先验证 registry.k8s.io 是否可达;时长套餐有效期内不限流量、不限速,适合经常拉取镜像、导出离线包的运维与开发人员,一个账号支持多设备同时在线。客户端带断线保护和按域名、IP 的智能分流,国内镜像站照常直连。实际可用性取决于当前网络环境。
立即下载刺猬VPN:从注册到连接,三分钟完成
刺猬VPN提供永久免费节点,不限流量;无需邮箱,自定义用户名即可匿名注册。一个账号支持多设备同时在线,实际可用性取决于当前网络环境。