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

FDE综合实战:仓库收到了三次相同订单

离线集群里的 ImagePullBackOff

在 TT Lab 中继续学习

本实验在真正的 k3s 中运行

VM 里运行着一台 k3s(v1.33.3+k3s1),containerd 和 kubelet 会真实地拉取镜像。第一次启动需要几分钟。VM 起初能连上互联网(80/443)——第 1 步充当联网的导入准备机,第 2 步由你亲手阻断出站,把它变成客户隔离网络。评分由从外部进入 VM 的 8899 端口的代理执行,所以如果阻断了已建立的连接和回环,评分就会中断。

目标

在联网的地方把镜像下载为归档并记录哈希,在出站被阻断的 k3s 中把 ImagePullBackOff 留作证据,然后通过 k3s ctr images import 和 k3s 镜像目录两条路径导入,再确认导入之后仍会失败的拉取策略,并把导入记录与运行时核对后留下。

为什么重要

在隔离网络的客户公司,“Pod 起不来”有一半是镜像问题。连不上注册表的集群自己拿不到镜像,必须有人把它放进运行时存储。而且放进去也不算结束。把搬运途中被改动的文件放进去,就没人知道运行的是什么;如果清单是 imagePullPolicy: Always 或者标签是 latest,即使镜像已经放好,也会去向注册表询问并失败。导入不是“把文件复制了”,而是证明“运行时持有哈希一致的镜像,并且 Pod 在使用它”。

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

步骤

  1. 把两个镜像 registry.k8s.io/e2e-test-images/busybox:1.36.1-1 和 registry.k8s.io/e2e-test-images/nginx:1.15-4 下载为镜像归档,放在 /root/airgap/bundle/busybox.tar 和 /root/airgap/bundle/nginx.tar,并在 /root/airgap/manifest.txt 中每行写一条 sha256 파일이름 이미지(占位符依次为文件名和镜像)。归档内的镜像名称必须与上面的名称完全一致。
  2. 编写 /root/airgap/egress.sh,使重复运行时规则也只留下一份,并以此阻断出站。iptables 链名为 AIRGAP-EGRESS,在 OUTPUT 和 FORWARD 中各转入一次。保留回环、已建立的连接、k3s Pod 网段(10.42.0.0/16)和服务网段(10.43.0.0/16),其余全部阻断。在阻断之前和之后,用 curl -s -o /dev/null -w '%{http_code}' https://registry.k8s.io/v2/ 测得的值,用 before=、after= 写入 /root/airgap/egress-proof.txt。
  3. 在命名空间 airgap 中,用 busybox 镜像创建运行 sleep 3600 的 Pod shop-api(清单 /root/airgap/shop-api.yaml)。拉取失败后,把 uid=<파드 UID>(占位符为 Pod UID)以及状态和事件保存到 /root/airgap/pull-failure.txt。
  4. 核对哈希后,用 k3s ctr images import 导入 busybox.tar,并把输出保存到 /root/airgap/import-busybox.txt。让 shop-api 变为 Running。
  5. 把 nginx.tar 放进 k3s agent 镜像目录(/var/lib/rancher/k3s/agent/images/)完成导入,并用该镜像让 Pod shop-web(清单 /root/airgap/shop-web.yaml)变为 Running。
  6. 用已导入的 busybox 镜像创建 imagePullPolicy 为 imagePullPolicy: Always 的 Pod shop-always(清单 /root/airgap/shop-always.yaml),观察失败,并把 uid=、policy= 和状态保存到 /root/airgap/always.txt。
  7. 在保持出站阻断的情况下,把 shop-always 用 imagePullPolicy: IfNotPresent 重新创建,并让它变为 Running。
  8. 在 /root/airgap/import-record.json 中留下导入记录。images 数组的每一项写入 ref、tar、tar_sha256、imported_via(ctr 或 images-dir)、runtime_image_id(k3s crictl inspecti 报告的 id)、pods(当前使用该镜像且处于 Running 的 airgap Pod 名称列表),并在最顶层用 true/false 写入 egress_blocked。

参考

在联网一侧制作导入物

把两个镜像下载为镜像归档,放在 /root/airgap/bundle/busybox.tar 和 /root/airgap/bundle/nginx.tar,并在 /root/airgap/manifest.txt 中每行写一条 sha256 파일이름 이미지(占位符依次为文件名和镜像)。

给 skopeo copy 的目标写成 docker-archive:路径:名称,就能不经注册表、直接下载成一个文件。要把名称一并写上,导入之后 Pod 才能用同样的名称找到它。哈希要在搬运之前测好,接收一侧才能核对。

阻断出站,造出隔离网络

用重复运行规则也只留一份的 /root/airgap/egress.sh 阻断出站,并把阻断前后的 HTTP 状态码用 before=、after= 写入 /root/airgap/egress-proof.txt。

新建一条链,在 OUTPUT 和 FORWARD 的最前面转入它,用先清空再重新填充的方式,无论运行多少次都是同样的样子。先用 RETURN 写下要保留的,最后再阻断。这台 VM 的评分是从外部通过 8899 进来的连接,所以如果阻断已建立的连接,评分就会中断。

被阻断的集群里出现 ImagePullBackOff

在命名空间 airgap 中用 busybox 镜像创建 Pod shop-api,观察拉取失败,并把 uid=<파드 UID>(占位符为 Pod UID)以及状态和事件保存到 /root/airgap/pull-failure.txt。

固定了标签的镜像,如果不写 imagePullPolicy,就是 IfNotPresent。运行时里没有,就会出去拉取而被挡住。Pod 很快会被重新创建,所以请现在就把 UID 和事件留下来——kubelet 的记录在 k3s 日志中也有。

用 ctr 直接导入运行时

用 k3s ctr images import 导入 busybox.tar,把输出保存到 /root/airgap/import-busybox.txt,并让 shop-api 变为 Running。

Kubernetes 使用的镜像必须在 containerd 的 k8s.io 命名空间里才看得见。即使导入了,kubelet 在失败不断重复时,到下一次尝试之前也要等得越来越久(最长 5 分钟),如果不想等,就删除 Pod 并用同一份清单重新创建。

通过 k3s 镜像目录导入

把 nginx.tar 放进 k3s agent 镜像目录完成导入,并用该镜像让 Pod shop-web 变为 Running。

k3s 会把放在 agent 的 images 目录里的 tar 导入 containerd。请在文档的版本表中确认,运行中放入的文件是否也会被导入。等到运行时里能看见之后,再创建 Pod。

导入了却依然失败的 Pod

用已导入的 busybox 镜像创建 imagePullPolicy 为 imagePullPolicy: Always 的 Pod shop-always,观察失败,并把 uid=、policy= 和状态保存到 /root/airgap/always.txt。

Always 即使镜像在本地,也每次都会向注册表询问,试图把标签解析成摘要。如果连不上注册表,就会在这一步失败。也请想起,标签是 latest 或没有标签时,默认策略是 Always。

阻断保持不变,修正策略

在保持出站阻断的情况下,把 shop-always 用 imagePullPolicy: IfNotPresent 重新创建,并让它变为 Running。

Pod 容器的 imagePullPolicy 是创建之后不能修改的字段。解除阻断来蒙混过关,是客户隔离网络里做不到的解决办法,所以请修改清单后重新创建。

把导入记录与运行时核对后留下

在 /root/airgap/import-record.json 中留下所导入的两个镜像的记录以及 egress_blocked。

记录里的值不要用手抄,要从命令输出里提取。运行时镜像 ID 由 crictl inspecti 告知,使用该镜像的 Running Pod 从 kubectl 的 JSON 里汇总。导入路径是你实际使用的方法。