名字不变,只换拉取来源
一句话总结
向隔离网络中的 Kubernetes 放入镜像,有两种办法。在每个节点上直接放入归档文件,或者搭建内部注册表,并告诉运行时使用镜像站(mirror)。镜像站不会改动清单中的任何镜像名称。作为代价,每种运行时的配置文件各不相同(podman 是 registries.conf,containerd 是 hosts.toml,k3s 是 registries.yaml),而且都必须另外告知私有 CA。
为什么需要它
在有十个节点的隔离网络集群中,每次升级新版本,都不可能把 tar 复制到所有节点上。所以要设置内部注册表。但常见的第一次尝试,是把清单和 chart 中的镜像名称改成 registry.corp.internal/...。这样就与外部仓库的 chart 分道扬镳,上游每发布新版本都要重新改一遍名称,而用摘要固定的清单,一旦在搬运过程中摘要变了,就会失效。镜像站只告诉运行时“registry.k8s.io 的内容从这里下载”,从而避开了这个问题。
工作原理
内部注册表。 CNCF distribution(旧称 docker registry)把镜像放在配置文件的 storage.filesystem.rootdirectory 中,通过 http.addr 确定地址,通过 http.tls 的 certificate 和 key 确定 HTTPS。给出 proxy.remoteurl 就成了 pull-through 缓存,但在该模式下不能 push——隔离网络中没有上游,所以把它作为普通注册表,并把导入物 push 进去。在 Ubuntu noble 上,它以 docker-registry 软件包的形式存在(配置为 /etc/docker/registry/config.yml,服务为 docker-registry.service)。
保持摘要不变地搬运。 skopeo copy 可以直接从注册表搬到注册表。--all 会把多架构列表整体搬运,--preserve-digests 则在无法保持摘要时让它失败。如果只搬运一个架构,列表的摘要就会改变,用 image@sha256:... 固定的清单就找不到镜像站中的镜像。skopeo sync 可以用 YAML 列表一次搬运多个镜像。
podman:registries.conf。 containers-registries.conf(5) 的 [[registry]] 用 prefix 写明适用于哪个名称,用 location 写明原本的位置,而 [[registry.mirror]] 的 location 是先尝试的地方。通过 pull-from-mirror 可以选择标签和摘要中哪一种从镜像站下载。片段文件放在 /etc/containers/registries.conf.d/ 中,会按字母顺序接续读取。私有 CA 位于 /etc/containers/certs.d/<호스트:포트>/ca.crt(占位符为主机:端口)。
containerd:hosts.toml。 根据 containerd 文档,/etc/containerd/certs.d/<레지스트리>/hosts.toml(占位符为注册表名称)中,目录名称是原本的注册表,server 是原本的地址,[host."..."] 是先尝试的镜像站,并在其中写入 capabilities(pull、resolve、push)和 ca。它会依次尝试所列出的 host,全部失败后退回到 server。在隔离网络中,这个退回会走向被封闭的外部,所以报错信息可能会造成混淆。让 CRI 查看这个目录的设置(config_path)的位置,在 containerd 1.x 和 2.x 中不同,而 ctr images pull --hosts-dir 可以让你在一条命令中测试同样的格式。
k3s:registries.yaml。 根据 k3s 文档,在 /etc/rancher/k3s/registries.yaml 的 mirrors 中写原本的注册表和 endpoint,在 configs 中写镜像站地址的 tls(ca_file 等)和认证,修改之后必须在每个节点上重新启动 k3s。文档还写明,默认端点总是作为最后手段被尝试。本实验的 k3s(v1.33.3+k3s1)使用 containerd 2.0。
在现场相遇的样子
我们在这个实验 VM 中实测过。registry.k8s.io/e2e-test-images/busybox:1.36.1-1 的列表中包含五个 Linux 架构和两个 Windows 镜像,用 --all 搬运这两个镜像后,注册表存储变成了 893MB(Linux 层只有几 MB)。放在 VM 根磁盘(2.4GiB)上时,第一个镜像就把磁盘写满了——镜像站的存储空间要根据导入容量来确定。整体列表搬运的 pause 的摘要与外部相同,为 sha256:ee6521f2…,而不加 --all 再搬运一次的副本,则变成了 sha256:7c38f247…。k3s 读取 registries.yaml,并生成了 /var/lib/rancher/k3s/agent/etc/containerd/certs.d/registry.k8s.io/hosts.toml,文件头是 # File generated by k3s. DO NOT EDIT.——格式与手写的 hosts.toml 相同。把注册表密钥设为 root 所有后,docker-registry.service 以 status=1/FAILURE 崩溃了。
使用镜像站之后,在 fde-delivery 模块中失败的 imagePullPolicy: Always Pod 就能启动了。因为 Always 每次都会向注册表询问标签的摘要,而现在这个询问由内部镜像站接收了。反过来,如果请求镜像站从未拥有的镜像,运行时在镜像站找不到,就会退回原本的注册表,然后被封锁而失败——即使报错行中打印着原本注册表的地址,原因也是“不在导入清单上”。
下一项实验要做什么
在 k3s VM 上用私有 CA 以 HTTPS 搭建内部注册表,并在联网期间保持摘要不变地搬运两个镜像。封闭出站之后,通过 podman(registries.conf)、containerd(手写的 hosts.toml)、k3s(registries.yaml)三条路,按原本的名称下载。启动 Always Pod,并把导入记录与注册表、运行时的摘要核对后留存。
参考文档:distribution Configuration、Registry as a pull through cache、skopeo-copy(1)、containers-registries.conf(5)、containerd Registry Configuration(hosts.md)、K3s Private Registry Configuration