速读#
容器镜像拉不下来,表面是 docker pull 失败,实际常常是镜像怎么跨过网络边界的问题。
- 能访问外网,只是上游慢:用可信的代理缓存或镜像仓库。
- 只有中转机能访问外网:用
skopeo、crane或 CI 把镜像同步到内网仓库。 - 环境完全离线:导出文件,校验后带进去,再导入或推到离线仓库。
临时换一个陌生镜像站也许能救急,但不能成为生产交付链。真正要稳定的是来源、版本、校验、同步和回滚这一整条路径。
先判断是哪一种网络#
我以前遇到拉取失败,第一反应是继续找新的镜像地址。看得多了才发现,这种处理把三类完全不同的环境混在了一起:
| 环境 | 真实问题 | 合适做法 |
|---|---|---|
| 可联网,但访问上游不稳定 | 路径慢或偶发失败 | 代理缓存、可信镜像仓库 |
| 生产网不能直连外网 | 网络边界受控 | 中转同步到内网 Registry |
| 完全离线 | 没有网络路径 | 文件导出、校验、导入 |
先把环境归类,后面才谈工具。否则今天改 daemon mirror,明天手工传 tar,后天又让每台节点各自找来源,最终谁也说不清线上镜像从哪里来。
路径一:代理缓存,适合“有网但不稳”#
如果机器可以访问外部仓库,只是链路慢,最省事的是让一个受控的 Registry 做 pull-through cache。第一次从上游拉取,之后由缓存直接提供。
它解决的是重复下载和链路抖动,不是供应链信任。配置前仍要回答几个问题:
- 缓存由谁维护,故障时是否有明确回退路径。
- 上游标签变化后,缓存多久刷新。
- 是否保留镜像 digest,能不能确认拿到的是同一个内容。
- 认证信息放在哪里,是否会写进脚本或日志。
我不会把网上搜到的一串公共镜像地址轮流塞进生产配置。免费节点可能限速、过期或改写内容;今天能拉,不代表下次部署仍能复现。生产需要的是一个自己能解释、能监控的入口。
路径二:同步到内网仓库#
只允许少数中转机访问外网时,应该把“拉镜像”和“部署应用”拆开。中转侧负责从可信来源获取镜像,生产侧只认识内网仓库。
skopeo 和 crane 都能在 Registry 之间复制镜像,不必先启动容器:
skopeo copy --all \ docker://docker.io/library/nginx:1.27 \ docker://harbor.example.com/base/nginx:1.27
crane copy \ docker.io/library/nginx:1.27 \ harbor.example.com/base/nginx:1.27--all 的意义是连同多架构 manifest 一起复制,避免在 x86 机器上同步成功,到了 ARM 节点才发现缺少对应镜像。
镜像固定、数量少时,手工同步能用;镜像一多,就该让 CI 根据一份清单执行复制,并记录源 digest、目标 digest 和同步时间。CI 的价值不是把命令藏起来,而是让每次交付都有相同入口和可追踪记录。
Harbor、Nexus 和 Artifactory 怎么选#
不用为了“正规”同时搭三套。
| 工具 | 更适合的情况 |
|---|---|
| Harbor | 主要管理容器镜像,需要复制、权限和镜像扫描 |
| Nexus | 已经在统一管理 Maven、npm 等制品,顺手纳管镜像 |
| Artifactory | 企业已有完整制品平台和配套流程 |
对一个小团队,最重要的不是功能表,而是所有部署只从一个受控仓库取镜像。已有 Nexus 就继续用;只管容器镜像,Harbor 更直接。为了一个临时交付搭完整平台,维护成本可能比手工文件还高。
路径三:完全离线时用 save/load#
镜像不多、目标环境完全离线时,docker save 和 docker load 是最短路径:
docker pull nginx:1.27docker save -o nginx-1.27.tar nginx:1.27sha256sum nginx-1.27.tar > nginx-1.27.tar.sha256把两个文件一起送入离线环境,先校验再导入:
sha256sum -c nginx-1.27.tar.sha256docker load -i nginx-1.27.tar校验的是传输文件有没有变化,不等于证明镜像来源可信。来源验证应该在联网侧完成,至少固定明确版本并记录 digest;有签名体系时,再验证签名和证明材料。
当镜像从几个增长到几十个,逐个 tar 会开始失控:重复层占空间、版本清单容易漏、每台节点都要重复导入。这时离线环境里也应该放一个 Registry,文件只负责跨边界,进入后统一推到仓库,再由节点正常拉取。
Kubernetes 最怕每个节点各自想办法#
单机执行一次 docker load 可能就结束了,Kubernetes 不一样。Pod 会被调度到任意节点,某个节点没有镜像就会出现 ImagePullBackOff。
稳定做法是:
- 把镜像同步进所有节点都能访问的内网 Registry。
- 部署清单中的
image明确指向这个仓库。 - 配好仓库证书、认证和节点信任。
- 用不可变版本或 digest,避免同名标签在节点间漂移。
紧急情况下可以给每个节点预加载镜像,但它很难长期维护。新增节点、重建节点和镜像升级都要重新补一遍;一旦漏了,故障只会在调度到那台节点时出现。
ImagePullBackOff 只是结果#
看到它不要只反复重启 Pod。先看事件:
kubectl describe pod <pod-name> -n <namespace>常见根因包括仓库地址写错、DNS 不通、证书不受信、认证失败、限流和镜像架构不匹配。它们都会落成“拉取失败”,处理方式却完全不同。
排查顺序可以固定下来:名字和标签是否存在,再看认证与证书,然后测 Registry 网络,最后确认节点架构。比不断更换镜像源更快,也不会把一个配置错误误判成网络问题。
我会保留的最小交付链#
对受限网络,我会把流程压成五步:
可信上游 -> 联网侧拉取与校验 -> 同步/导出 -> 内网仓库 -> 部署每一步只留下必要记录:镜像全名、源 digest、目标位置、同步时间和执行结果。镜像不是“能 pull 下来就行”的安装包,它是生产制品;既然要进生产,就应该像代码版本一样能回答从哪来、现在是什么、坏了退回哪一版。
临时环境用 save/load 没问题,长期环境用受控 Registry 更稳。工具选哪个是次要的,不要让生产机器在部署时临时去赌外部网络,才是这件事真正的边界。
