TT Lab
开始
学习 学习路径 课程

隔离网络镜像与私有 CA

按原名从内部镜像站拉取镜像

在 TT Lab 中继续学习

本实验在真正的 k3s 中运行

VM 中运行着一台 k3s(v1.33.3+k3s1),并安装了 skopeo、podman 和 Ubuntu 的 docker-registry 软件包(CNCF distribution 注册表)。首次启动需要几分钟。VM 起初可以访问互联网(80/443)——到第 3 步为止是联网状态下的导入准备,在第 4 步中由你封闭出站,把它变成隔离网络。评分由从外部连入 VM 的 8899 端口的代理完成,所以如果封闭了已建立的连接和回环,评分就会中断。

目标

用私有 CA 以 HTTPS 搭建内部注册表,原样搬运镜像及其摘要,然后在外部被封闭的状态下,让 podman、containerd 和 Kubernetes 在不改变镜像名称的情况下,从内部 mirror 下载。

为什么重要

在隔离网络中,如果把清单中的镜像名称全部改成内部注册表地址,chart 和清单就会与外部分道扬镳,每次获取下一个版本都要重新修改。mirror 保持名称不变,只告诉运行时“那个注册表的内容从这里下载”。陷阱在于每种运行时的配置文件不同——podman 是 registries.conf,containerd 是 hosts.toml,k3s 是 registries.yaml。而且都必须另外告知私有 CA。

预计 60 分钟。会话结束后 VM 会消失,想保留的文件请在结束前另行保存。

步骤

  1. 在 /root/registry/pki 中创建根证书(ca.crt、ca.key,CN 为 Airgap Internal Root CA)和 registry.airgap.internal 证书(registry.crt、registry.key,带 SAN),并在 /etc/hosts 中加入 127.0.0.1 registry.airgap.internal。
  2. 通过 /etc/docker/registry/config.yml 让内部注册表在 127.0.0.1:5000 上用该证书提供 HTTPS,并启用服务,使其开机时也会启动。注册表服务账号必须能够读取密钥。
  3. 联网期间,把 registry.k8s.io/e2e-test-images/busybox:1.36.1-1 和 registry.k8s.io/pause:3.10,以保持摘要不变的方式搬运到内部注册表的相同路径(registry.airgap.internal:5000/e2e-test-images/busybox:1.36.1-1、.../pause:3.10),并把 원래이름 다이제스트(占位符依次为原始名称和摘要)逐行写入 /root/registry/digests.txt。
  4. 用重复运行也只会留下一套规则的 /root/registry/egress.sh 封闭出站(链 AIRGAP-EGRESS,在 OUTPUT 和 FORWARD 中各引用一次,放行回环、已建立的连接、10.42.0.0/16 和 10.43.0.0/16)。
  5. podman:通过 /etc/containers/registries.conf.d/50-airgap-mirror.conf 把 registry.k8s.io 的 mirror 设为内部注册表,告知私有 CA 之后,以原本的名称 registry.k8s.io/e2e-test-images/busybox:1.36.1-1 执行 pull。
  6. containerd:手写 /root/registry/certs.d/registry.k8s.io/hosts.toml,并用 k3s ctr -n airgap-ctr images pull --hosts-dir /root/registry/certs.d registry.k8s.io/pause:3.10 下载。
  7. Kubernetes:通过 /etc/rancher/k3s/registries.yaml 让 k3s 使用内部 mirror 并重新启动 k3s,然后在命名空间 airgap 中,让使用原本名称、imagePullPolicy: Always 的 Pod mirror-busybox(清单为 /root/registry/mirror-busybox.yaml)进入 Running。
  8. 在 /root/registry/mirror-record.json 中留下导入记录——images 数组的每一项包含 ref、mirror_ref、digest、pulled_by(podman、ctr、kubelet 中实际完成下载的那些),最上层包含 egress_blocked。

参考

内部注册表证书

在 /root/registry/pki 中创建根证书(ca.crt、ca.key)和 registry.airgap.internal 证书(registry.crt、registry.key,带 SAN),并把该名称加入 /etc/hosts。

根证书是 CA:TRUE 的自签名,服务器证书则用根证书签名,同时通过扩展文件给出 SAN。流程与私有 CA 模块相同。

用 HTTPS 提供内部注册表

通过 /etc/docker/registry/config.yml 让注册表在 127.0.0.1:5000 上用该证书提供 HTTPS,并启用服务,使其开机时也会启动。

在配置的 http 部分给出地址和 tls(证书和密钥路径)。服务以专用账号运行,所以必须把密钥放在该账号能够读取的位置并设置相应权限。如果没有启动,请先看 journalctl -u docker-registry。

保持摘要不变地搬运

把两个镜像以保持摘要不变的方式搬运到内部注册表的相同路径,并把 원래이름 다이제스트(占位符依次为原始名称和摘要)逐行写入 /root/registry/digests.txt。

skopeo copy 可以直接从注册表搬运到注册表。必须整体搬运多架构列表,摘要才会与外部相同。把内部注册表的 CA 放在 /etc/containers/certs.d/<主机:端口>/ca.crt 中,skopeo 和 podman 就会共同使用。

封闭出站,变成隔离网络

用重复运行也只会留下一套规则的 /root/registry/egress.sh 封闭出站。

新建一条链,并在 OUTPUT 和 FORWARD 的最前面转到这条链,采用清空再重新填充的方式,无论运行几次,结果都是同样的形态。请先放行评分代理和 Kubernetes 网段。

podman:registries.conf 中的 mirror

通过 /etc/containers/registries.conf.d/50-airgap-mirror.conf 把 registry.k8s.io 的 mirror 设为内部注册表,并以原本的名称 pull busybox。

registries.conf 的 [[registry]] 用 prefix 写明适用于哪个名称,[[registry.mirror]] 写明先尝试的位置。不改变名称。私有 CA 直接使用第 3 步中放置的 certs.d。

containerd:手写 hosts.toml

编写 /root/registry/certs.d/registry.k8s.io/hosts.toml,并用 k3s ctr -n airgap-ctr images pull --hosts-dir /root/registry/certs.d registry.k8s.io/pause:3.10 下载。

目录名称是原本的注册表,文件中的 server 是原本的地址,[host."..."] 是先尝试的 mirror。在 mirror 中写入 pull、resolve 权限和 CA 路径。

Kubernetes:registries.yaml 与 Always Pod

通过 /etc/rancher/k3s/registries.yaml 让 k3s 使用内部 mirror 并重新启动,然后在命名空间 airgap 中,让 imagePullPolicy: Always 的 Pod mirror-busybox(清单为 /root/registry/mirror-busybox.yaml)进入 Running。

在 mirrors 中写原本的注册表和 endpoint,在 configs 中写 mirror 地址的 tls ca_file。如 k3s 文档所述,修改之后必须重新启动 k3s。镜像名称保持原样——即使是 Always,只要能在 mirror 中解析标签,就能启动。

把导入记录与注册表、运行时核对

在 /root/registry/mirror-record.json 中,为每个镜像留下 ref、mirror_ref、digest、pulled_by,并在最上层留下 egress_blocked。

摘要向内部注册表查询后提取,pulled_by 写 podman、ctr、kubelet 中实际下载了该镜像的那一方。值不要手抄,请从命令输出中收集。