























在 Kubernetes(k8s)日常运维中,镜像拉取失败是最常见的故障之一。尤其是当 Deployment 的镜像拉取策略设置为 Always 时,一旦镜像仓库不可用、镜像标签不存在或网络不通,Pod 就会陷入 ImagePullBackOff 或 ErrImagePull 状态,反复重试拉取却始终无法恢复。
很多运维同学会尝试一个“常规操作”:在本地通过docker save -o 镜像名.tar 镜像地址导出镜像,然后拷贝到Pod所在的宿主机,用docker load -i 镜像名.tar导入镜像,以为这样就能让Pod正常拉取。但往往事与愿违——Pod依然会报错,无法恢复。
这时候,一个简单却有效的“救命操作”出现了:执行kubectl edit pods/[pod名称] -n [命名空间],手动修改 Pod 的 image 字段为宿主机已导入的镜像地址(比如本地镜像名:标签,或私有仓库可访问的镜像),保存后不久,Pod就会自动重启并恢复正常。
为什么导入宿主机的镜像没用?为什么修改 Pod 的 image 地址就能解决问题?今天我们就从K8s的核心原理出发,把这个现象讲透,同时附上完整实操步骤,帮你彻底搞定这类镜像拉取故障。
先明确一下故障的前提条件和现象,确保我们讨论的是同一个问题:
要理解这个现象,核心是搞懂两个关键点:k8s 的镜像拉取逻辑和 Always 拉取策略的真正含义,以及 docker load 导入镜像的作用范围。
k8s 本身不负责镜像拉取和容器运行,它会调用底层的容器运行时(比如Docker、containerd、CRI-O)来完成这些操作。我们平时用的 Docker,就是其中一种容器运行时。
当 k8s 需要启动一个 Pod 时,会向Pod所在宿主机的 kubelet 发送指令,kubelet 再调用容器运行时(比如Docker),由容器运行时负责拉取镜像、创建容器。
这里有一个关键:容器运行时拉取镜像,是“按镜像地址(image字段)拉取”,而非“按镜像ID拉取”。即使宿主机本地有一个镜像,其镜像ID和Pod要拉取的镜像ID完全一致,但如果镜像地址(image字段)不一样,容器运行时依然会尝试从镜像地址对应的仓库拉取,而不是直接使用本地镜像。
k8s 的 imagePullPolicy 有三个可选值,我们重点说 Always(也是故障场景的前提):
回到故障场景:当 imagePullPolicy=Always 时,即使你通过 docker load 把镜像导入到了宿主机本地,只要 Pod 的 image 字段还是原来的仓库地址(比如harbor.example.com/my-image:v1),kubelet 调用 Docker 拉取时,依然会尝试访问 harbor.example.com 这个仓库拉取镜像——而此时仓库不可用/网络不通,所以拉取依然失败,Pod 无法恢复。
简单说:docker load只是把镜像放到了宿主机的本地镜像仓库,但Always策略让容器运行时“无视”本地镜像,非要去远程仓库拉取,自然失败。
有同学可能会问:我导入的本地镜像,和远程仓库里的镜像,镜像ID是一样的,为什么 k8s 还是要拉取?
因为 k8s 判断“镜像是否可用”,优先依据 image 字段的“镜像地址+标签”,而非镜像ID。容器运行时的拉取逻辑是:
这就是为什么“导入本地镜像”没用——即使镜像ID一致,只要远程仓库不可用,Always策略下,拉取依然会失败。
当我们执行 kubectl edit pods 修改 image 字段后,本质上是改变了“容器运行时要拉取的镜像地址”,同时触发了Pod的重启,这两个动作结合起来,解决了故障,我们拆解来看:
我们把image字段从原来的“远程仓库地址”(比如harbor.example.com/my-image:v1),修改为“宿主机本地镜像地址”(比如my-image:v1)。
此时,容器运行时(Docker)收到的拉取指令,就变成了“拉取my-image:v1”——这个地址对应的是宿主机本地已经通过docker load导入的镜像,没有远程仓库的依赖。
k8s 的 Pod 是“不可变基础设施”——一旦 Pod 创建成功,其大部分配置(包括 image 字段)是不能直接修改的。而 kubectl edit pods 本质上是:
新 Pod 启动时,会执行新的 image 字段对应的拉取操作——此时拉取的是宿主机本地的 my-image:v1,而 imagePullPolicy=Always 虽然要求“每次拉取”,但因为镜像地址是本地的,容器运行时会直接读取本地镜像(无需访问远程仓库),拉取成功。
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。