前段时间帮一个研发团队搭开发测试环境网络是全物理隔离的所有节点都不能访问外网。说实话在线环境下我用 kubeadm 部署 Kubernetes 已经很熟了半小时就能拉起一个集群但一换到离线场景很多“理所当然”的操作全部失效——镜像没法直接 pull系统依赖装不上连 pause 镜像漏了都能让节点反复 NotReady。这篇文章把我在开发测试环境下做 Kubernetes 离线部署的完整过程整理出来从物料准备、版本锁定、镜像搬运到集群初始化、网络插件、开发测试常用组件的落地再到排障和后面团队实际使用时的经验补充希望能给同样被困在隔离网络里的研发、运维和测试同学一条能直接照做的路线。需要先说清楚的是这里我谈的不是生产级别的离线高可用部署而是开发测试环境它对性能、容灾要求没那么高但对“能快速交付、稳定复现、团队上手成本低”有非常强的诉求。所以文章里很多选型都偏向务实和节省人力比如私有镜像仓库直接用 registry:2 而不是 Harbor 全家桶存储用 NFS 而不是去做 Ceph这些取舍在正式生产环境里可能不成立但在开发测试场景下是真的好用又省心。1. 开发测试环境为什么必须走离线部署这条路1.1 我在实际项目里遇到的几类网络受限场景做离线部署之前先得搞清楚自己到底面对的是哪种“离线”不同场景下的准备策略差别很大。物理隔离内网。这是我遇到最多的情况常见于安全要求严格的研发内网。机器之间能互相通信但整个网段没有任何公网出口连 DNS 解析都只认内网域名。要从外部拿数据只能通过光闸、U 盘、审批流程这种人工方式拷进去。这种环境下你基本上要假设“所有需要的文件都必须提前带进去”后续排障时只能靠内网已有的资源。云上 VPC 的私有子网。部分云环境或企业私有云会把开发测试集群放在无法访问公网的子网里但会给你留一台可以出网的跳板机。这种场景相对宽松可以通过跳板机搭个临时通道把镜像传输进去也可以在跳板机上部署一个私有镜像仓库让集群节点通过内网访问。相比物理隔离排障空间大了很多。临时交付和现场复现。还有一类是展会、客户现场、实验室这种临时环境。机器可能是崭新的也可能预装了一部分中间件但公网不稳定或者根本没有。这种场景时间紧、任务重通常不能让你从零开始折腾最好提前准备好一套完整的离线交付包到了现场直接导入运行。这三种场景的共性是你不能依赖“在线下载”这条路。但它们的区别会直接影响你选择镜像搬运方案——物理隔离只能用导出的 tar 包人工拷贝VPC 环境可以用仓库同步现场交付则要尽量自动化。1.2 离线部署真正的难点不是“装不上”而是“不知道缺什么”在线部署时报错了搜一下就能下载对应依赖重试一下也许就过去了离线环境不一样很多坑是到了实际安装那一刻才暴露出来的。比如 kubeadm init 提示拉取镜像失败你才发现镜像包没带全kubelet 启动失败排查半天发现系统缺了 conntrackPod 网络不通检查之后发现内核模块 br_netfilter 没加载。这些在联网环境里一条命令就能解决但在离线环境里每缺一个东西都意味着一次人肉搬运和漫长的审批等待。我自己的经验是离线部署的核心工作其实不在“部署”本身而在“前期物料盘点”。你得把安装的依赖闭环、镜像的依赖闭环、甚至配置文件的依赖闭环全部打通。下面要讲的整套流程本质上就是在做这件事。1.3 我建议的版本组合与物料闭环思路离线环境里最忌讳的就是“尝鲜”。选一个太新的 Kubernetes 版本很可能 kubeadm 的配置格式变了、容器运行时的适配还没跟上出了问题很难在隔离环境里快速找到答案。我推荐选一个较 mature 且社区资料丰富的版本比如 Kubernetes 1.28.x配套工具也用与之兼容的稳定版本。我这次使用的版本组合大致如下组件名 版本 用途 Kubernetes 1.28.2 集群核心 containerd 1.7.11 容器运行时 kubeadm / kubelet / kubectl v1.28.2 集群管理组件 Calico v3.27.0 Pod 网络插件 metrics-server v0.6.4 资源指标采集 Kubernetes Dashboard v7.1.1 Web 管理界面 nginx-ingress-controller v1.9.5 Ingress 控制器 pause 3.9 每个 Pod 都会用到的 sandbox 镜像版本组合确定后马上要建立“物料闭环”的概念把二进制包、镜像 tar、RPM 包、内核模块、配置模板全部对齐到这些版本在联网机器上准备成一套完整的 artifact 目录然后整体带到离线环境。2. 在联网机器上完成离线物料打包版本、镜像与系统依赖2.1 先花一小时做版本对齐能避免后面几天的折腾离线部署最痛苦的一件事就是版本不匹配导致的“连锁反应”。比如你在联网机器上下载了 Kubernetes 1.28.2 的二进制但 containerd 还是老版本cri 接口可能不兼容又比如你把 Calico 镜像换成了最新版但它要求的 kube-apiserver 特性开关在 1.28 里默认没开。这些兼容性问题在在线环境可以用“升级一下组件”糊弄过去离线环境里你手上没有新版本就只能干瞪眼。所以第一步一定要做版本对齐。你可以在联网机器上先用 kubeadm 生成一份镜像清单kubeadm config images list --kubernetes-versionv1.28.2这条命令会输出 kubeadm 初始化集群所需的全部镜像包括 kube-apiserver、kube-controller-manager、kube-scheduler、kube-proxy、etcd、pause 等。下载这些镜像时注意 kubeadm 默认的 imageRepository 是 registry.k8s.io如果网络访问不了可以从国内镜像站或代理下载后再重新打 tag。除了镜像之外还需要提前下载好 kubeadm、kubelet、kubectl 三个二进制文件并且版本要和镜像版本一致。一个小技巧是下载后用sha256sum校验一下文件完整性避免拷贝过程中文件损坏。2.2 系统依赖包与内核参数的离线准备很多人在离线部署时把所有精力都放在镜像上结果到了现场发现操作系统层面的包都缺。开发测试环境通常用的是 CentOS 7.9 或 Rocky Linux 9建议提前准备这些包tar、gzip解压二进制包用socatkubeadm 初始化时检查它是否存在conntrack-toolskubelet 依赖ebtables、ipset、ipvsadmiptables 和负载均衡相关nfs-utils后面挂 NFS 存储会用到你在联网的机器上准备这些包时可以用 yumdownloader 把所有依赖包下载到本地yumdownloader --resolve --destdir/tmp/k8s-rpms tar socat conntrack-tools ebtables ipset ipvsadm nfs-utils到了离线环境后把这些 RPM 包拷贝进去用rpm -ivh *.rpm或者yum localinstall安装即可。如果离线机器数量多建议在其中一台建一个本地 yum 源其他机器通过内网访问这样效率高很多。同样重要的还有内核模块和系统参数。Kubernetes 要求加载 br_netfilter 和 overlay 模块否则 Pod 网络和容器镜像挂载都会出问题。离线环境下把模块写进/etc/modules-load.d/k8s.conf并配置好/etc/sysctl.d/k8s.confnet.bridge.bridge-nf-call-iptables 1 net.bridge.bridge-nf-call-ip6tables 1 net.ipv4.ip_forward 1需要提醒的是有些内核模块在默认内核里可能没有编译进去比如 overlay。如果modprobe overlay失败说明内核不支持最稳妥的办法是换一个包含该模块的内核版本而不是硬着头皮继续。2.3 镜像搬运的三种方案以及怎么选镜像搬运是离线部署里最重的体力活。我在不同场景下用过三种方案这里分别说一下它们的适用条件。方案 A单机或小规模集群用 export import。如果只是搭建一个 3 节点左右的开发测试集群可以直接在联网机器上拉取镜像后导出 tar 包到离线节点上再导入。以 containerd 为例# 联网机器上拉取并导出镜像 docker pull registry.k8s.io/kube-apiserver:v1.28.2 docker save registry.k8s.io/kube-apiserver:v1.28.2 -o kube-apiserver.tar # 离线节点上导入镜像到 containerd 的 k8s.io 命名空间 ctr -n k8s.io images import kube-apiserver.tar注意这里一定要指定-n k8s.io因为 kubelet 和 kubeadm 默认从 containerd 的 k8s.io 命名空间读取镜像。如果导入到了默认命名空间crictl 是看不到的kubelet 依然会认为镜像缺失。方案 B多节点或后续需要持续交付搭建私有镜像仓库。如果集群节点多或者团队后面要在集群里频繁部署中间件那最简单的方式是搭一个私有镜像仓库。裸机环境直接跑一个 registry:2 容器就行docker run -d --name registry --restartalways \ -p 5000:5000 \ -v /data/registry:/var/lib/registry \ registry:2然后把下载好的镜像重新 tag 成内网仓库地址并 pushdocker tag registry.k8s.io/kube-apiserver:v1.28.2 192.168.1.10:5000/kube-apiserver:v1.28.2 docker push 192.168.1.10:5000/kube-apiserver:v1.28.2这种模式的好处是后续节点加入集群时不需要再拷贝 tar 包直接配置好 containerd mirror 就能从仓库拉取开发测试阶段往里塞中间件镜像也方便。方案 C超大镜像或跨机房传输直接拷贝 tar 到节点再导入。有些镜像是真的巨大比如只针对大模型推理的镜像可能有 5GB 甚至更大。如果所有节点都从私有仓库拉取可能把内网带宽打满。这种情况下我建议先在联网机器导出 tar然后 rsync 或 scp 到目标节点再用ctr -n k8s.io images import导入。缺点是占磁盘空间但胜在稳。三种方案不是互斥的实际项目中经常组合使用。控制面组件用私有仓库大体积中间件镜像用 tar 方式直传节点都是很常见的做法。2.4 内部镜像仓库搭建与 containerd 的 mirror 配置镜像仓库建好之后接下来要解决“kubelet 怎么知道去私有仓库拉镜像”的问题。如果你用的是 Docker 运行时只需要在/etc/docker/daemon.json里加insecure-registries即可。但既然我们选择 containerd就需要修改它的配置文件。containerd 的 CRI 插件配置文件一般在/etc/containerd/config.toml你需要找到[plugins.io.containerd.grpc.v1.cri.registry.mirrors]这一段按下面的方式配置[plugins.io.containerd.grpc.v1.cri.registry.mirrors] [plugins.io.containerd.grpc.v1.cri.registry.mirrors.192.168.1.10:5000] endpoint [http://192.168.1.10:5000] [plugins.io.containerd.grpc.v1.cri.registry.configs.192.168.1.10:5000.tls] insecure_skip_verify true如果镜像仓库启用了认证还需要配置 auth 字段。不过开发测试环境建议先以 http 方式跑起来后面有安全需求再加证书和认证。修改完配置后重启 containerdsystemctl restart containerd测试拉取是否正常crictl pull 192.168.1.10:5000/pause:3.9这里要特意提一下 pause 镜像。kubelet 创建每个 Pod 时都会先创建一个 sandbox 容器用的就是 pause 镜像。如果你的镜像仓库里没有提前导入 pause 镜像节点初始化后会出现kubelet is down或者 Pod 一直 ContainerCreating 的问题。很多人第一次离线部署就栽在这个上面。3. 离线部署 Kubernetes 集群的实操全流程3.1 containerd 离线安装与初始化环境准备好之后第一步是安装容器运行时。我推荐 containerd原因很简单Kubernetes 从 1.24 版本开始移除了 Dockershim如果你还坚持用 Docker就得多装一个 cri-dockerd 适配层这在离线环境里又增加了故障点。开发测试环境没必要给自己加戏。离线安装 containerd 有两种方式。一是直接下载官方编译好的二进制 tar 包解压后拷贝到/usr/local/bin然后生成配置二是把 containerd 的 rpm 包提前下载好离线机器上 rpm -ivh 安装。我更倾向二进制方式因为不依赖操作系统的包管理版本完全可控。安装完成后生成默认配置containerd config default | tee /etc/containerd/config.toml然后重点检查两个配置SystemdCgroup必须为 true否则 kubelet 和容器运行时在 cgroup 驱动上会不一致节点状态可能一直 NotReady。sandbox_image要修改成私有仓库里的 pause 镜像地址。这两个坑在离线环境特别常见建议配好后先重启 containerd再用ctr -n k8s.io images list确认 pause 镜像已经在本地。3.2 用 kubeadm 初始化控制平面节点安装完 containerd 之后把 kubeadm、kubelet、kubectl 三个二进制拷到节点上放到/usr/local/bin目录并加上执行权限。然后用配置文件的方式初始化控制面节点这样可以明确指定镜像仓库地址和网络网段。我准备的 kubeadm-config.yaml 大致如下apiVersion: kubeadm.k8s.io/v1beta3 kind: InitConfiguration localAPIEndpoint: advertiseAddress: 192.168.1.10 bindPort: 6443 --- apiVersion: kubeadm.k8s.io/v1beta3 kind: ClusterConfiguration kubernetesVersion: v1.28.2 imageRepository: 192.168.1.10:5000 networking: podSubnet: 10.244.0.0/16 serviceSubnet: 10.96.0.0/12 --- apiVersion: kubelet.config.k8s.io/v1beta1 kind: KubeletConfiguration cgroupDriver: systemd然后执行初始化kubeadm init --config kubeadm-config.yaml --upload-certs如果前面镜像导入完整这一步通常能顺利通过。初始化成功后会输出一段 join 命令先保存下来。然后配置 kubectlmkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config如果你在初始化过程中遇到拉取镜像超时或认证失败先别急着重试按第 4 节的排查链路走一遍往往都是 mirror 配置或者 imageRepository 地址写错导致的。3.3 工作节点加入与 Calico 网络插件离线配置控制平面初始化成功后工作节点的加入流程相对简单。先把 kubeadm、kubelet、containerd 这些组件全部装好确保镜像仓库里的镜像都能被 pull 到本地然后执行控制平面输出的 join 命令即可。如果 token 过期了可以在主节点重新生成kubeadm token create --print-join-command执行 join 之前建议在工作节点手动拉一次镜像比如crictl pull 192.168.1.10:5000/kube-proxy:v1.28.2确认网络和认证都通了再跑 join不然很容易被一堆无关报错淹没。网络插件我选了 Calico。离线安装 Calico 的核心是把它的镜像从 Docker Hub 改为内网仓库地址。具体做法是提前从 Calico 官方 GitHub 下载对应的 manifest 文件curl -O https://raw.githubusercontent.com/projectcalico/calico/v3.27.0/manifests/calico.yaml把这个 yaml 文件里的docker.io/calico/前缀全部替换成192.168.1.10:5000/然后 applykubectl apply -f calico.yaml验证网络是否正常kubectl get pods -n kube-system如果 calico-node 的 Pod 都在 Running并且kubectl get nodes显示节点状态 Ready说明网络插件已经生效。这时候可以随便创建一个部署测试一下跨节点 Pod 通信比如跑两个 nginx 副本分布在两个节点上互相 curl 一下。3.4 开发测试常用 Addons 的离线安装思路集群基础可用之后接下来要补几个开发测试环境必须的组件。这些组件在在线环境里直接kubectl apply就行但离线环境需要先把镜像同步到私有仓库或直接导入节点。第一个是 Kubernetes Dashboard。很多开发测试人员不习惯 kubectl更喜欢图形界面。Dashboard 的部署文件下载后同样要把镜像替换成内网仓库地址。为了方便访问我通常用 NodePort 方式暴露服务而不是去折腾 Ingress毕竟开发测试环境对安全域名的要求没那么高。第二个是 metrics-server。没有它kubectl top和 Dashboard 的资源图表都没法用。metrics-server 的镜像比较小替换成私有仓库后直接 apply 即可。如果发现 metrics-server 一直 CrashLoopBackOff大概率是 kubelet 的证书问题需要给 metrics-server 的启动参数里加--kubelet-insecure-tls。第三个是 Ingress Controller。开发测试环境一般有多个 Web 服务要暴露每个都用 NodePort 太浪费端口且管理混乱所以 nginx-ingress-controller 还是要装一个。离线部署时把镜像替换后 apply 就行。第四个是 StorageClass。开发测试环境跑 GitLab、Redis、MySQL 这些中间件时PVC 是绕不开的。我这里推荐两种最省事的方案一是用 NFS 动态供给在集群里部署一个 NFS Client Provisioner前提是内网有 NFS 服务器二是用开源项目提供的 local-path-provisioner它把节点本地目录映射成 PV适合不需要跨节点共享数据的场景。4. 开发测试阶段的常用服务落地与高频排障记录4.1 在离线 K8s 上部署 GitLab、Redis、OnlyOffice、Superset 这些开发依赖集群稳定跑起来之后开发测试团队真正关心的是GitLab 能不能用Redis 能不能连OnlyOffice 能不能在线预览文档Superset 能不能做数据看板这些服务在开发测试环境里几乎是刚需。它们的离线部署思路是相通的先在联网机器上把所有镜像拉下来然后通过私有仓库或 tar 包导入集群节点最后替换 manifest 里的 image 地址。例如一个常见的 GitLab 部署至少需要准备这些镜像gitlab/gitlab-cegitlab/gitlab-runnerredispostgresql更省事的方式是用 Helm 来管理这些中间件。你可以在联网机器上用helm pull把 chart 打包下载然后在离线环境里用helm install安装同时把 chart 中引用的镜像全部同步到私有仓库。需要注意的是Helm chart 中很多依赖子 chart比如 Redis 和 Postgresql这些子 chart 的镜像也得一起准备。如果你要部署 OnlyOffice Document Server有一点必须提前知道这个镜像非常大可能有几个 GB传输时间会比较长。而且它对 CPU 和内存都有要求建议单独调度到一台配置较高的节点而不是和其他高负载应用挤在一起。Superset 这种数据可视化工具也无非是 apache/superset 镜像加一个 redis、一个 postgres。开发测试环境不用追求特别高的性能简单安装即可。近几年还有一个趋势就是在开发测试环境里离线部署大模型相关服务比如把 DeepSeek 8B 这种大模型的推理镜像导入集群做本地测试。这类镜像的体积通常是几个 GB 甚至几十个 GB而且需要有 GPU 节点的支持。我的建议是大镜像不要走私有仓库中转直接导成 tar 后 scp 到节点再用ctr -n k8s.io images import导入同时确认 GPU 节点已经安装了匹配的驱动和 nvidia-device-plugin否则调度到 GPU 节点后 Pod 会一直处于 ContainerCreating。可以说私有仓库建立之后后续的中间件离线落地就变成了一件比较机械的事情。最关键的是提前把“镜像清单”梳理清楚不要到了现场才发现缺一个 tag 差一个版本。4.2 镜像拉取失败与 ImagePullBackOff 的完整排查链路ImagePullBackOff 是开发测试环境里出现频率最高的错误之一。离线环境里遇到它我建议按照下面的链路一步步排查而不是直接删 Pod 重来。第一步看 Pod 事件kubectl describe pod pod-name事件里通常会写清楚失败原因比如Failed to pull image、manifest unknown、connection refused、no such host等。这些错误信息已经能定位到大部分问题。第二步检查镜像是否存在。如果事件提示manifest unknown大概率是镜像名称或 tag 打错了。你可以在节点上手动执行crictl pull 192.168.1.10:5000/myimage:v1.0.0如果仍然报 manifest unknown说明仓库里没有这个镜像回联网机器重新打 tag 并 push。第三步检查 containerd 的 mirror 配置。如果手动 pull 时报connection refused或timeout多半是 containerd 的 mirror 没配对或者仓库地址不通。执行curl http://192.168.1.10:5000/v2/_catalog可以快速确认仓库是否可达。第四步看架构是否匹配。如果你把 amd64 的镜像推到了仓库但目标节点是 arm64 架构拉取时可能会报exec format error或者在启动时直接异常。这种问题在开发测试环境很常见因为团队的笔记本和服务器架构经常不统一。解决方法是重新拉取对应架构的镜像或者在构建镜像时用docker buildx构建多架构版本。第五步检查凭据和认证。如果仓库开了认证containerd 的 config.toml 里必须有对应的 auth 配置。否则 Pod 会报pull access denied。4.3 容易被忽略的隐藏坑时钟同步、DNS、代理残留和防火墙开发测试环境跑了一段时间之后经常会出现一些看起来很奇怪的问题比如节点时不时变成 NotReady或者 Pod 之间的网络时通时不通。这类问题的根因往往不在 Kubernetes 本身而是底层环境。时钟同步是最容易被忽视的一个。Kubernetes 组件之间大量依赖证书和 token 做认证而证书的有效期校验依赖系统时间。如果节点之间的时间差超过几分钟就会导致x509: certificate has expired or is not yet valid一类的问题。离线环境没有公网 NTP所以一定要在集群里找一台机器作为内部时间服务器其他节点通过 chrony 与它同步。DNS 也是个大坑。Kubernetes 集群内部的 CoreDNS 负责解析 Service 名称但它转发集群外部域名时默认会读取节点的/etc/resolv.conf。如果离线环境的内网 DNS 配置不对或者 CoreDNS 的上游地址没有指向内网 DNS那么 Pod 里访问集群外服务就会失败。遇到这种问题检查一下 CoreDNS 的 ConfigMap把 forward 指向内网 DNS 服务器。代理残留这个问题说实话在开发测试环境特别普遍。很多内网机器之前因为调试需要配置过 HTTP_PROXY 环境变量或者/etc/systemd/system/containerd.service.d/下存在带 proxy 的配置文件。这些代理配置在连外网时没问题但访问内网仓库时会先尝试走代理结果代理又不通导致镜像拉取一直超时。解决方式很简单把 containerd 服务里的 proxy 环境变量清理干净重启服务。防火墙和 SELinux 如果开着也可能导致 Calico 的 VXLAN 流量被拦截、Pod 之间无法互通。开发测试环境我通常会直接关掉防火墙并设置 SELinux 为 permissive 模式如果团队有安全规范必须开启那就要在防火墙上放行集群 Pod 网段和 VXLAN 端口。4.4 备份与集群升级离线环境必须提前想好的两件事很多团队在开发测试集群跑起来之后就不管了直到集群出问题才后悔没做备份。离线环境里一旦集群损坏修复成本比在线环境高得多因为很多排查工具和修复包都需要重新导入。所以我强烈建议从一开始就定期备份 etcd。etcd 是 Kubernetes 的“元数据数据库”备份它是整个集群备份的核心。最简单的做法是用 etcdctl 直接做快照ETCDCTL_API3 etcdctl --endpointshttps://127.0.0.1:2379 \ --cacert/etc/kubernetes/pki/etcd/ca.crt \ --cert/etc/kubernetes/pki/etcd/server.crt \ --key/etc/kubernetes/pki/etcd/server.key \ snapshot save /backup/etcd-snapshot-$(date %Y%m%d).db至于集群升级离线环境的正确态度是“能不升就不升要升也必须先在隔离的测试环境演练一遍”。开发测试集群一般不涉及生产数据升级诉求没那么强但如果确实需要升级小版本必须提前准备好新版本的 kubeadm、kubelet、kubectl 二进制和所有对应镜像把新镜像同步到私有仓库再按照“控制平面节点先升级、工作节点后升级”的顺序操作。跨大版本升级比如 1.28 升到 1.30就不要在离线环境里冒进了除非你有非常充分的理由和足够的时间。5. 团队真正跑起来之后我觉得值得补上的几个细节5.1 开发测试环境的资源规划与命名空间配额开发测试环境不像生产环境有严格的容量规划但也不能完全放任。团队一起用同一个集群时很容易出现“一个人把资源吃满其他人全部卡死”的情况。我的建议是按项目或业务线划分命名空间并通过 ResourceQuota 限制每个命名空间的资源总量。比如给测试项目组设置 limits 和 requestsapiVersion: v1 kind: ResourceQuota metadata: name: dev-quota namespace: dev-project spec: hard: requests.cpu: 8 requests.memory: 16Gi limits.cpu: 16 limits.memory: 32Gi persistentvolumeclaims: 10这样即使某个团队的某个应用出了内存泄漏也不会拖垮整个集群。另外建议给每个命名空间配置 LimitRange防止有人忘了写 resources 里 limits导致 Pod 被调度到节点后被 OOM Kill。5.2 离线环境下的 CI/CD 协同开发测试集群往往不只是用来部署应用还会承载一定量的 CI/CD 任务。离线环境下CI/CD 的难点在于构建镜像时需要基础镜像而基础镜像从哪来这里有两种思路。一种是把 gitlab-runner 或 jenkins agent 本身部署进 K8s 集群所有镜像从私有仓库拉取。构建时通过 Kaniko 或者 BuildKit 在集群内完成基础镜像也是从私有仓库拿。这种方式的好处是链路完整但前提是你提前把所有可能用到的基础镜像都同步到私有仓库。另一种是“构建机与集群分离”开发测试环境里有一台或多台可以访问内网仓库的构建机它们在本地构建镜像然后把构建产物 push 到私有仓库K8s 集群部署时只负责拉取。这种方式更灵活也更符合很多团队的现状。不管哪种方式都建议把“同步基础镜像”这个动作做成一个定时任务或脚本比如每天或每次版本发布后自动把从外部镜像源拉取的新版本镜像同步到私有仓库这样开发测试环境就不会因为缺镜像而阻塞。5.3 如果我再搭一次会提前做的几件事回过头看这次离线部署的整个过程如果再做一次我会在动手之前先做三件事。第一把所有节点的系统版本和内核版本统一。这次我就遇到一个节点是 CentOS 7.9、另一个是 Rocky Linux 9 的问题导致某些内核模块的加载方式不一样在网络插件排障时浪费了不少时间。第二写一个全局的镜像同步脚本。不要人工一个个去 docker pull 和 retag而是把镜像清单维护在一个文件里脚本自动拉取、导出、推送。这套脚本虽然前期要花一点时间写但后面每次补充镜像都会变得非常轻松。第三先在单节点上完整跑通一遍再扩展到多节点。离线环境最怕就是一开始就搞多节点结果报错了也不知道是网络问题还是镜像问题。单节点环境把所有组件跑到 Ready 以后再添加工作节点排障范围会小很多。离线部署 Kubernetes 这件事本质上是在和“不确定性”作斗争。你把所有可能用到的东西都提前锁死、安排好后面的部署就是水到渠成的事但只要有一样东西没准备到整个交付就可能陷入反复拷数据、反复等审批的泥潭。上面这些经验都是我在实际部署中一步步踩出来的。希望你看完之后能少走一些弯路尤其是那些花了很多时间才发现是系统基础依赖缺失的坑最好一次也不要踩。