1. 为什么开发测试环境也要认真对待离线部署
上个月同事在群里求助:机房里的开发测试集群要整体重建,新机器放在一个不能访问外网的网段,里面还要跑一套数据看板 Superset 和一套 ONLYOFFICE 文档预览。往常装 Kubernetes 都是对着公网仓库一路 apt、docker pull 就完事,真到了离线环境,才发现第一步找包就能把人卡住。
后来我把整套流程走了一遍,从二进制、镜像、Helm Chart 到应用部署全部离线打通。这次选的版本是 Kubernetes v1.28.2,配合 containerd 1.7.13。开发测试环境没有生产环境那么高的可用性要求,但它的坑在于:机器换了、网段变了、开发要用的中间件又多,如果离线介质准备不齐,后面每装一个应用都要重新折腾一次。
1.1 你以为的开发测试环境,可能比生产还难搞
很多人默认"开发测试环境 = 能联网的宽松环境",实际情况恰恰相反。不少公司和机房的开发测试网段是独立隔离的,为了省事或者合规,直接不给外网路由。你在办公室开发时能随便拉镜像,可机房里的机器根本访问不了 Docker Hub 或 Kubernetes 官方镜像仓库。
还有一类场景是项目验收、漏洞扫描、等保测评,要求所有组件必须用内部介质安装,不能出现运行时从公网拉取的行为。这时候哪怕机器能访问外网,也不能拉,只能靠离线介质。
所以给开发测试环境做离线部署,不是自找麻烦,而是提前把"换机器重来"的成本打下来。一次离线介质准备好,后续扩容节点、重建环境、交付给其他团队,都是一套固定动作。
1.2 离线部署的本质是降级为供应链管理
离线部署说白了就是把原来分散在公网各个仓库里的东西,提前搬到一台有网的"下载工作站"上,整理成标准介质包,再运到内网目标机器上安装。
Kubernetes 离线部署要管的包,大体是三类:
- 二进制包:kubeadm、kubelet、kubectl、containerd、runc、CNI 插件。
- 镜像包:Kubernetes 核心组件镜像、DNS、网络插件、以及开发测试要用的中间件和应用镜像。
- 配置与编排文件:kubeadm 配置、Helm Chart、Manifest YAML、本地镜像仓库和离线 Helm 仓库的初始化配置。
把这三类盘清楚,离线部署就成功了一大半。别上来就到处找"一键离线安装脚本",脚本多的是,但脚本背后的版本匹配、镜像对应关系、私有仓库地址,才是真正决定你能不能一次跑通的地方。
2. 离线部署前必须盘清楚的版本、镜像与网络台账
离线环境下改版本是件很痛苦的事。在线环境想升级就改一下镜像 tag,离线环境每次改动都意味着重新做介质、重新拷盘、重新验包。所以动手之前,花半小时把台账建好,比装到一半再返工划算得多。
2.1 版本选型:从操作系统到容器运行时的匹配关系
我这次统一采用 Ubuntu 22.04 + containerd 1.7.13 + Kubernetes v1.28.2。选这套组合的原因很直接:内核较新,对 cgroup v2 和 iptables 的支持都省心;containerd 1.7 是长时间维护版本,cri 集成成熟;Kubernetes 1.28 不是最新的,但功能特性稳定,团队后续接中间件也不会碰到兼容性红线。
各节点操作系统版本尽量一致,不要在一个集群里混用 Ubuntu 和 CentOS。如果实在避免不了,也要保证内核版本接近,并且对 containerd 的 overlay 存储驱动做一次实际验证。
一个容易忽略的坑是内核模块。Kubernetes 正常运行时依赖overlay和br_netfilter,如果目标机器内核模块没加载,网络转发和 Pod 通信都会出问题。这个我们在后面初始化步骤里会专门处理。
2.2 把"网络拓扑 + 仓库地址"先画出来
离线不等于节点之间没有网络。开发测试环境里,控制节点、工作节点、镜像仓库、应用服务之间通常还是同一个内网。我的建议是固定一张表:
| 角色 | 地址示例 | 说明 |
|---|---|---|
| 控制节点/工作节点 | 192.168.10.11 ~ 10.13 | Kubernetes 集群节点,双网卡或单网卡均可 |
| 私有镜像仓库 | 192.168.10.10:5000 | 用 registry:2 容器跑,承载所有 K8s 和应用镜像 |
| 离线下载工作站 | 10.0.0.8 | 能访问公网的机器,负责下载和打包,不在集群内 |
| 目标网段入口机器 | 192.168.10.10 | 接收外部介质,启动 registry,同时可以作为运维跳板 |
镜像仓库地址要早定。后面 kubeadm 配置、containerd 配置、应用 values 里全都要写这个地址,如果装到一半再改,所有节点都要跟着改一遍。开发测试环境我用192.168.10.10:5000这种 IP 加端口的方式,简单直接;如果公司有自己的域名解析,用registry.internal:5000更规范。
2.3 依赖清单:镜像只是其中一部分
很多人以为离线部署就是拷镜像,真正装的时候才发现还缺系统包、缺 CNI 二进制、缺 crictl。我按依赖类型整理了一个检查清单,照着准备就行:
- Kubernetes 官方镜像:kube-apiserver、kube-controller-manager、kube-scheduler、kube-proxy、etcd、coredns、pause。
- 容器运行时:containerd、runc、CNI 插件二进制包。
- 网络插件镜像:flannel 或 calico 相关的镜像。
- 集群附加组件:metrics-server、ingress-nginx、dashboard 等,按需准备。
- 开发测试应用镜像:Superset、ONLYOFFICE Document Server、PostgreSQL、Redis、vLLM/Ollama 等。
- 系统依赖:conntrack、ebtables、ethtool、socat、iproute2、iptables。
- 离线仓库组件:registry:2 镜像,或者 Harbor 离线安装包。
- Helm 及 Chart 包:Helm 二进制、需要离线安装的 Chart 压缩包。
系统依赖很容易被漏掉。如果目标机器有内网 apt/yum 源,直接装;如果没有,最省事的办法是拿操作系统安装 ISO 挂载成本地源。千万别默认 kubeadm 二进制拷上去就能跑,它依赖的这些用户态工具在干净系统上经常是缺的。
3. 在有网机器上把离线介质一次性准备到位
这部分在能上网的下载工作站上完成。准备介质不是把文件下载下来就完事,还要校验、分类、做成可复用的目录结构。我的建议是打造一个/data/k8s-offline目录,下面分成bin、images、charts、manifests四个子目录。
/data/k8s-offline/ ├── bin/ # kubeadm、kubelet、kubectl、containerd、cni ├── images/ # 所有镜像 tar 包 ├── charts/ # Helm Chart 压缩包 └── manifests/ # 手工整理的 YAML、kubeadm 配置3.1 下载二进制包:能下载到官方 tar 包就不要只拿 deb/rpm
kubeadm、kubelet、kubectl 这三个可以直接从 Kubernetes release 页面下载裸二进制文件,不用依赖包管理器。这样省掉了在不同发行版上处理依赖的问题。
mkdir -p /data/k8s-offline/bin cd /data/k8s-offline/bin VERSION=v1.28.2 curl -L -O https://dl.k8s.io/release/$VERSION/bin/linux/amd64/kubeadm curl -L -O https://dl.k8s.io/release/$VERSION/bin/linux/amd64/kubelet curl -L -O https://dl.k8s.io/release/$VERSION/bin/linux/amd64/kubectl chmod +x kubeadm kubelet kubectlcontainerd、runc、CNI 插件也一样,直接从对应 GitHub Release 下载 Linux amd64 二进制压缩包:
CONTAINERD_VERSION=1.7.13 RUNC_VERSION=1.1.11 CNI_VERSION=v1.4.0 curl -L -O https://github.com/containerd/containerd/releases/download/v${CONTAINERD_VERSION}/containerd-${CONTAINERD_VERSION}-linux-amd64.tar.gz curl -L -O https://github.com/opencontainers/runc/releases/download/v${RUNC_VERSION}/runc.amd64 curl -L -O https://github.com/containernetworking/plugins/releases/download/${CNI_VERSION}/cni-plugins-linux-amd64-${CNI_VERSION}.tgz下载完不要急着拷贝,先在下载机器上把版本号记下来。后面排查问题的时候,第一件事就是确认这些二进制版本和 kubeadm init 配置里的kubernetesVersion一致,不一致会出现很多莫名其妙的问题。
3.2 拉取并导出 Kubernetes 核心镜像
我建议先写一个kubeadm-init.yaml,把镜像仓库地址定成最终要用的内网地址,然后用 kubeadm 从配置里生成镜像清单。这样导出的镜像名直接就是内网仓库地址,省去后期二次 tag。
cat > /data/k8s-offline/manifests/kubeadm-init.yaml <<'EOF' apiVersion: kubeadm.k8s.io/v1beta3 kind: InitConfiguration localAPIEndpoint: advertiseAddress: 192.168.10.11 nodeRegistration: criSocket: unix:///run/containerd/containerd.sock --- apiVersion: kubeadm.k8s.io/v1beta3 kind: ClusterConfiguration kubernetesVersion: v1.28.2 imageRepository: 192.168.10.10:5000 controlPlaneEndpoint: 192.168.10.11:6443 networking: podSubnet: 10.244.0.0/16 serviceSubnet: 10.96.0.0/12 EOF然后生成镜像列表。注意,这个列表是最终集群要从内网仓库拉取的镜像名,但在下载机器上直接 pull 会失败,因为内网仓库还没有这些镜像。所以正确顺序是:先让 kubeadm 输出一份默认地址的清单,把默认地址替换成本地临时 tag,pull 完成后再重新 tag 回内网仓库地址。
具体的脚本可以这样写:
cd /data/k8s-offline/images # 生成默认仓库的镜像清单,用于 pull kubeadm config images list --kubernetes-version v1.28.2 > upstream-images.list # 逐个 pull、tag、导出 REGISTRY=192.168.10.10:5000 while read -r img; do name=$(echo "$img" | awk -F/ '{print $NF}') docker pull "$img" docker tag "$img" "${REGISTRY}/${name}" docker save "${REGISTRY}/${name}" | gzip > "k8s-${name}.tar.gz" done < upstream-images.list核心镜像清单中一定包含 pause。pause 镜像也叫 sandbox_image,是 containerd 启动每个 Pod 前先拉取的基础镜像,很多离线环境第一次 init 失败就是因为它没准备到。p8s 这个坑我们在排障部分再展开。
3.3 下载网络插件和开发测试应用镜像
Kubernetes 集群没有网络插件之前,节点会一直处于 NotReady 状态。我这次用 flannel,原因是开发测试环境网络简单,flannel 的 VXLAN 后端够用,镜像数量也比 calico 少。
cd /data/k8s-offline/manifests curl -L -O https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml cd /data/k8s-offline/images grep -E 'image:' /data/k8s-offline/manifests/kube-flannel.yml | awk '{print $2}' | sort -u > flannel-images.list把这些网络插件镜像也按同样方式 pull、tag、导出。用 grep 从 YAML 里提取镜像列表,比手写更不容易漏。
开发测试应用镜像,比如 Superset、ONLYOFFICE Document Server、PostgreSQL、Redis、vLLM 等,下载思路完全一样。我的经验是不要一上来就下载一大批应用镜像,先把 Kubernetes 基础设施镜像搞定,集群能正常跑起来,再按应用逐个补镜像。一次准备太多,后面出问题不好定位。
4. 目标节点初始化到集群拉起:一条可复现的完整链路
介质准备好之后,剩下的就是在目标节点上执行安装。这一节我把每个步骤的命令和验证方法写清楚,照着走一遍就能有一个最小可用集群。
4.1 节点基础配置:内核模块、系统参数和依赖包
所有目标节点先做同一套基础初始化。
# 加载内核模块 cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf overlay br_netfilter EOF sudo modprobe overlay sudo modprobe br_netfilter # 配置网络转发参数 cat <<EOF | sudo tee /etc/sysctl.d/99-kubernetes.conf net.bridge.bridge-nf-call-iptables = 1 net.bridge.bridge-nf-call-ip6tables = 1 net.ipv4.ip_forward = 1 EOF sudo sysctl --system然后关闭 swap。Kubernetes 的 kubelet 默认对 swap 很敏感,虽然新版有 NodeSwap 特性,但开发测试环境没必要开。关闭后记得注释/etc/fstab里的 swap 行,否则重启后又自动挂载回来,kubelet 状态会反复异常。
sudo swapoff -a接下来安装系统依赖。如果目标机器没有内网 apt 源,可以用操作系统安装 ISO 挂载成本地源;如果连 ISO 也拿不到,就从下载工作站上把这些 deb 包连同依赖一起用外置介质带过去。
sudo apt update sudo apt install -y conntrack ebtables ethtool socat iproute2 iptables4.2 安装 containerd 并处理 sandbox_image
containerd 是所有容器实例的直接管理进程,kubelet 通过 CRI 协议和它通信。把下载好的 containerd 压缩包解压安装到/usr/local,并把 runc 和 CNI 插件放到对应位置。
cd /data/k8s-offline/bin sudo tar -C /usr/local -xzf containerd-1.7.13-linux-amd64.tar.gz sudo install -m 755 runc.amd64 /usr/local/bin/runc sudo mkdir -p /opt/cni/bin sudo tar -C /opt/cni/bin -xzf cni-plugins-linux-amd64-v1.4.0.tgz # 生成默认配置 sudo mkdir -p /etc/containerd sudo sh -c 'containerd config default > /etc/containerd/config.toml'修改config.toml两处关键内容。第一处是让 containerd 使用 systemd 作为 cgroup 驱动,和 kubelet 的 cgroupfs 配置保持一致;第二处是把 pause 镜像指向内网仓库。
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml sudo sed -i 's#sandbox_image = .*#sandbox_image = "192.168.10.10:5000/pause:3.9"#' /etc/containerd/config.toml如果私有仓库是 HTTP,还要在config.toml里给仓库地址单独声明 endpoint,否则 containerd 默认按 HTTPS 去连,会直接 timeout。最简单的方式是追加这样一段:
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."192.168.10.10:5000"] endpoint = ["http://192.168.10.10:5000"]启动 containerd 后,用ctr验证一下 pause 镜像是否已经被导入。等会儿 kubeadm init 会立刻去拉这个镜像,这里多花一分钟检查,后面能少折腾一小时。
sudo systemctl daemon-reload sudo systemctl enable --now containerd sudo ctr -n k8s.io images list | grep pause4.3 导入镜像并执行 kubeadm init
把上一阶段打包的所有镜像 tar 包拷贝到目标节点,逐个用ctr导入。这里必须带-n k8s.io命名空间,因为 containerd 的 CRI 插件只认k8s.io这个命名空间。很多人习惯用ctr images import导入,发现 kubectl 里还是 ImagePullBackOff,就是因为导到了默认命名空间,kubelet 根本看不到。
sudo ctr -n k8s.io images import /data/k8s-offline/images/k8s-kube-apiserver.tar.gz sudo ctr -n k8s.io images import /data/k8s-offline/images/k8s-kube-controller-manager.tar.gz sudo ctr -n k8s.io images import /data/k8s-offline/images/k8s-kube-scheduler.tar.gz sudo ctr -n k8s.io images import /data/k8s-offline/images/k8s-kube-proxy.tar.gz sudo ctr -n k8s.io images import /data/k8s-offline/images/k8s-etcd.tar.gz sudo ctr -n k8s.io images import /data/k8s-offline/images/k8s-coredns.tar.gz sudo ctr -n k8s.io images import /data/k8s-offline/images/k8s-pause.tar.gz然后执行 init:
sudo kubeadm init --config /data/k8s-offline/manifests/kubeadm-init.yaml初始化成功后,配置 kubectl:
mkdir -p $HOME/.kube sudo cp /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config工作节点加入集群需要 join 命令。kubeadm init 成功后会直接输出,最好用kubeadm token create --print-join-command再生成一次,保存到本地文件。token 的有效期默认是 24 小时,离线搭建如果中间隔了几天,记得重新生成。
4.4 安装网络插件,让节点状态转为 Ready
没有 CNI 插件时,kubectl get nodes看到控制节点是 NotReady,coredns 也起不来。把 flannel 的镜像导入到所有节点,或者推送到私有仓库,然后修改kube-flannel.yml里的镜像地址为192.168.10.10:5000/flannel/flannel:v0.24.2之类,最后 apply。
sudo ctr -n k8s.io images import /data/k8s-offline/images/flannel.tar.gz kubectl apply -f /data/k8s-offline/manifests/kube-flannel.yml等一两分钟,再看节点状态:
kubectl get nodes kubectl get pods -A如果节点状态是 Ready,集群基础环境就算通了。到这一步,Kubernetes 本身的离线部署已经完成,接下来要解决的是"开发测试环境里常用服务怎么离线跑起来"。
5. 开发测试应用的离线补给:镜像仓库、Helm 源与常见应用落地
集群基础通了,开发测试工作才刚开始。你要在集群里装数据库、数据看板、文档服务,甚至跑离线大模型推理验证。这些应用的镜像和 Chart 同样需要离线化。
5.1 私有镜像仓库的两种搭法
开发测试环境我强烈建议先部署一个私有镜像仓库,而不是把所有应用镜像直接ctr import到节点上。原因很直接:导入到本机的镜像只对当前节点有效,Pod 调度到另一个节点时还是会去拉镜像,一旦节点上没有,就等着 ImagePullBackOff。
最简单的方式是用 registry:2 起一个单容器仓库:
sudo docker load < /data/k8s-offline/images/registry-2.tar.gz sudo docker run -d --name registry --restart=always \ -p 5000:5000 \ -v /data/registry:/var/lib/registry \ registry:2如果团队内需要镜像权限控制和 Web 管理界面,用 Harbor。但 Harbor 的离线安装包本身也不小,而且要依赖 Docker Compose,开发测试环境如果就二三十个镜像,registry:2 完全够用。
把下载工作站上 tag 好的镜像推到私有仓库:
REGISTRY=192.168.10.10:5000 while read -r img; do docker tag "${img##*/}" "${REGISTRY}/${img##*/}" docker push "${REGISTRY}/${img##*/}" done < /data/k8s-offline/images/upstream-images.list推完之后,在任意一个集群节点上用 crictl 验证:
sudo crictl pull 192.168.10.10:5000/nginx:latest能拉下来,说明 containerd 到私有仓库的通路没问题。后面应用镜像都能走这条链路。
5.2 离线 Helm 源:不求人也能装 Chart
开发测试环境里很多应用都用 Helm 部署,比如 Superset 有自己的官方 Chart,ONLYOFFICE 也有 Chart。离线环境不能直接helm repo add公网仓库,但在下载工作站上把 Chart 先 pull 下来,再拷贝到内网机器上,完全可行。
helm repo add superset https://apache.github.io/superset helm repo add onlyoffice https://onlyoffice.github.io/charts helm repo add bitnami https://charts.bitnami.com/bitnami helm pull superset/superset --version 0.12.0 helm pull onlyoffice/document-server --version 0.1.0 helm pull bitnami/postgresql --version 12.5.4 helm pull bitnami/redis --version 18.0.1如果团队里有多个人都要用,可以在内网起一个 ChartMuseum 作为离线 Helm 仓库:
sudo docker run -d --name chartmuseum --restart=always \ -p 8080:8080 \ -v /data/chartmuseum:/charts \ chartmuseum/chartmuseum:latest然后在开发机上配置 Helm 源:
helm repo add internal http://192.168.10.10:8080 helm cm-push superset-0.12.0.tgz internal5.3 三个常见的开发测试离线应用落地方式
Superset 离线部署
Superset 依赖 PostgreSQL 和 Redis。离线环境下,先把这三个镜像准备好,再通过 Helm values 把镜像地址指到私有仓库:
helm install superset ./superset-0.12.0.tgz \ --set image.repository=192.168.10.10:5000/superset \ --set image.tag=latest \ --set postgresql.image.registry=192.168.10.10:5000 \ --set redis.image.registry=192.168.10.10:5000如果 Chart 内部还有依赖子 Chart,比如postgresql-ha,它可能默认从docker.io拉镜像。这时候最省事的办法是在 values 里统一覆盖global.imageRegistry=192.168.10.10:5000,前提是你已经把相关的镜像都推到了私有仓库。
ONLYOFFICE Document Server 离线部署
文档预览服务在开发测试环境很常见。ONLYOFFICE 的镜像比较大,依赖 PostgreSQL、RabbitMQ、Redis,离线部署时同样需要注意这些子 Chart 的镜像地址。
helm install onlyoffice ./document-server-0.1.0.tgz \ --set image.repository=192.168.10.10:5000/onlyoffice/documentserver \ --set image.tag=latest \ --set postgresql.image.registry=192.168.10.10:5000 \ --set rabbitmq.image.registry=192.168.10.10:5000 \ --set redis.image.registry=192.168.10.10:5000有一点容易被忽略:ONLYOFFICE 要正常预览中文文档,容器里得有中文字体。在线环境可以直接在 Pod 里 apt 装字体,离线环境不行。所以离线部署时,最好把fonts-noto-cjk字体包放到私有镜像里,或者用 initContainer 挂载宿主机字体目录。开发测试环境我不建议做得太复杂,直接在镜像制作阶段把字体打进去最省心。
离线大模型推理验证
开发测试环境现在还有个很常见的需求:在内网验证大模型推理效果,比如用 8B 参数的 DeepSeek 模型跑业务问答。这类模型权重动辄十几 GB 到几十 GB,让每台节点都在线拉权重根本不现实。常规做法是:在有网机器上提前拉好 vLLM 或 Ollama 的推理镜像并推送到私有仓库,模型权重文件通过外置硬盘拷到内网服务器,再挂载到 Pod 里。
apiVersion: apps/v1 kind: Deployment metadata: name: vllm-deepseek-8b spec: replicas: 1 selector: matchLabels: app: vllm template: metadata: labels: app: vllm spec: containers: - name: vllm image: 192.168.10.10:5000/vllm/vllm-openai:latest command: ["python3", "-m", "vllm.entrypoints.openai.api_server"] args: ["--model", "/models/deepseek-8b", "--port", "8000"] resources: limits: nvidia.com/gpu: "1" volumeMounts: - name: models mountPath: /models volumes: - name: models hostPath: path: /data/models/deepseek-8b这个 YAML 的镜像地址、模型路径、GPU 资源限制都需要根据实际环境调整。重点在于:推理镜像走私有仓库,模型权重走外置介质,两者分开,不要全部塞进镜像 tar 包,否则一个模型就要重新打包一次。
6. 开发测试集群日常排障:我踩过的坑和检查顺序
离线集群排障和在线集群排障最大的区别是:在线环境可以直接docker pull试一下,离线环境不行,每次验证都要看介质和仓库。我把自己踩过的高频问题整理成一个排查顺序,遇到问题按这个顺序过一遍,大部分都能解决。
6.1 kubeadm init 卡在 kubelet-start:先看 pause 镜像
第一次 init 时,最经典的失败是日志一直在等 kubelet 启动。打开另一个终端看 kubelet 日志:
journalctl -u kubelet -f --no-pager如果日志里反复出现container image "192.168.10.10:5000/pause:3.9" for sandbox image相关的记录,大概率是 containerd 里根本没有这个镜像,或者 containerd 访问不到私有仓库。先用 crictl 手动拉一次:
sudo crictl pull 192.168.10.10:5000/pause:3.9如果拉取超时,检查 containerd config 里的 endpoint 是不是 HTTP。很多人在这里卡半天,其实就是少写了http://前缀。
6.2 镜像已经导入,但 Pod 还是 ImagePullBackOff
这个场景十有八九是命名空间导错了。containerd 有两个"世界":一个是底层 containerd 的默认命名空间,一个是 Kubernetes 通过 CRI 使用的k8s.io命名空间。你用ctr images import不带-n k8s.io,镜像确实导入成功了,但 kubelet 完全看不到。
检查命令是:
sudo ctr -n k8s.io images list | grep pause如果不带-n k8s.io能看到,带了却看不到,重新导入一遍即可。
6.3 节点状态 Ready,但 Pod 之间网络不通
开发测试环境里最常见的问题是 Pod 网段和物理机网段冲突。我在 kubeadm 配置里用10.244.0.0/16,如果目标机器所在的内网段也是10.244.0.0/16,路由就会打架,表现为某些 Pod 能通,某些 Pod 间歇性超时。
排查第一步不是看 flannel 配置,而是先看主机路由表:
ip route | grep 10.244如果发现本机存在物理网卡占用了这个网段,优先把 kubeadm 里的podSubnet改成一个内网不会用到的地址段,比如172.20.0.0/16。开发测试环境改起来成本低,趁早改。
6.4 开发测试环境的常见坑位速查
| 问题现象 | 大概率原因 | 排查命令 |
|---|---|---|
| kubeadm init 一直卡住 | kubelet 服务未启动或 pause 镜像缺失 | journalctl -u kubelet -f |
| Pod 一直是 ContainerCreating | CNI 插件没装或镜像没导入 | kubectl describe pod |
| 节点 NotReady | flannel/calico 没起来或内核模块没加载 | kubectl get pods -A |
| containerd 拉私有仓库超时 | 没写 http endpoint 或证书不对 | crictl pull |
| 镜像导入成功但 Pod 仍拉取失败 | ctr 导入到默认 namespace | ctr -n k8s.io images list |
| Helm 安装报 registry 错误 | values 里没有把镜像地址全局覆盖 | helm get values |
| GPU 资源 unavailable | 节点没有安装 NVIDIA 驱动/device plugin | kubectl describe node |
6.5 最后一条经验:把离线介质目录当作代码仓库管理
整个离线部署做完之后,一定不要把/data/k8s-offline随便扔在那里。建议把里面的images.list、charts/、manifests/全部纳入版本管理,至少在一个共享目录里长期保存。开发测试环境的节点会换,系统会重装,你不可能每次都重新去公网下载一遍介质。
我自己的习惯是每次新增应用镜像或升级组件后,都在下载工作站上重新生成一次镜像清单,并写一个简单的README.md记录版本号和验证时间。后面再有人接手机房环境,直接照着 README 操作,不用再从头趟一遍坑。