拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Kubernetes离线部署实战:从镜像同步到集群初始化全指南

1. 为什么开发测试环境一定要把离线部署纳入规划先说说我自己的经历。前几年负责一套内部开发测试环境集群建在内网物理上与公网隔离没有任何临时开个网的余地。第一次做 Kubernetes 初始化时我按在线文档的默认路径走kubeadm init 直接卡在拉取 registry.k8s.io 镜像这一步超时重试三次全部失败整个下午就耗在等一个根本不可能到达的仓库上。后来我把整个过程重做了一遍从二进制包到镜像文件全部离线搬运问题才彻底消失。那次之后我养成了一个习惯任何集群环境第一件事先问清楚有没有外网而不是默认它有。开发测试环境和生产环境有一个本质区别生产环境通常有成熟的变更流程和网络策略而开发测试环境往往边界模糊今天这台机器能联网明天加一个安全策略就封了今天拉镜像飞起明天源站维护就全卡住。如果你一开始就采用离线部署的思路把这些环境当作随时可能断网来处理后面遇到网络波动、源站变更、机房策略收紧都不会影响你的交付节奏。还有一个容易被忽视的点开发测试环境本身就承担着模拟生产的职责。生产环境因为合规要求根本无法联外网那么你的测试环境就必须在与生产一致的网络条件下进行验证否则很多问题在测试阶段根本暴露不了。比如离线场景下镜像怎么分发、节点重启后从哪拉取基础组件、证书怎么维护这些问题只有在真正的离线环境里跑过才算验证到位。离线部署并不是什么高深技术它本质上就是三件事提前准备好所有需要的软件包和镜像在内网建立自己的分发通道然后按正确的顺序完成初始化。但这三件事里每一件都藏着大量细节稍不注意就会在某个环节卡住。这篇文章我会把这些细节和踩过的坑完整过一遍。2. 离线物料准备先把货备齐再动手离线部署最忌讳的事就是装到一半发现缺东西。在线环境缺什么可以现下离线环境缺什么就只能从头再来。所以物料准备这一步我建议宁可多备不要少备。2.1 二进制安装包清单Kubernetes 集群的核心组件包括 kubelet、kube-apiserver、kube-controller-manager、kube-scheduler、kube-proxy、etcd 等。这里有一个关键决策是用发行版自带的包管理工具安装还是直接下载官方二进制文件我的建议是在离线环境里直接用官方编译好的二进制包不要依赖发行版的软件源。原因有两个第一发行版的 Kubernetes 包版本通常落后而且不同发行版打包方式不同容易引入不可控的差异第二二进制包拷贝即用不依赖包管理器的依赖解析对离线环境最友好。具体需要准备的内容如下kubeadm、kubelet、kubectl这三个通常一起下载注意版本必须一致推荐在官方 GitHub Releases 页面下载对应版本的二进制文件etcd控制面的关键组件建议单独下载对应架构的二进制包CNI 插件比如 calico、flannel、cilium 的二进制和配套 manifestcrictl用于和 containerd 交互的调试工具架构问题要特别留意。生产环境大多用 x86_64但开发测试环境里经常混着 arm64 的机器比如一些 ARM 开发板、国产化服务器。下载时一定要确认uname -m的输出x86_64 和 aarch64 的二进制是不通用的这个问题我在实际项目中见到过不止一次。2.2 容器镜像清单与版本锁定二进制包只是控制面的一部分真正占据离线部署绝大部分工作量的是容器镜像。一个标准的单节点开发测试集群需要拉取的镜像包括这些组件kube-apiserver、kube-controller-manager、kube-scheduler、kube-proxyetcdcoreDNSpause所有 Pod 都会用到的基础容器CNI 相关镜像比如 calico 的 calico-node、calico-cni这些镜像的数量看起来不多但有一个很麻烦的细节它们的镜像名都带仓库地址前缀比如registry.k8s.io/kube-apiserver、quay.io/calico/node。在离线环境里节点上的容器运行时根本不认识内网仓库必须先把这些镜像从原始仓库拉下来重新打标签指向你的内网仓库地址再推送上去。版本锁定是另一个关键点。开发测试环境往往不是只部署一套集群而是隔几个月就新开一套。如果不锁定版本等下一次部署时原来的镜像 tag 可能已经在公网仓库被覆盖或删除你要想复现当时的部署就会变得非常困难。我现在的做法是在项目的根目录维护一个images.list文件每一行一个镜像格式是镜像名:tag每次部署前先 git diff 看版本变化确认无误再开始拉取。2.3 Helm Charts 与离线插件包开发测试环境通常还会装一些附加组件比如 Ingress Controller、监控套件、日志采集等。如果你用 Helm 管理这些组件离线部署还需要额外准备 Helm Charts 包。这里有一个常见误区以为有 Helm Chart 就行了运行时镜像还是要从仓库拉。Helm Chart 里面只有模板和 values 文件真正的容器镜像还是在部署时从镜像仓库拉取。所以准备工作要做两遍一遍是helm pull把 Chart 包下载到本地另一遍是把 Chart 里涉及的镜像同步到内网仓库。更省事的办法是使用 Helm 的oci://协议。很多组件现在都支持把 Chart 打包成 OCI 镜像这样你可以用同一套镜像同步工具把 Chart 和业务镜像一起推送到 Harbor实现镜像即 Chart、Chart 即镜像的统一管理。不过要注意这种方式要求你的 Harbor 版本在 2.2 以上否则 OCI 存储支持不完整。3. 内网基础设施搭建软件源、镜像仓库与 DNS 规划物料准备完成后接下来要做的不是急着部署 Kubernetes而是先把内网的物流体系建立起来。这个体系包含三部分操作系统软件源、容器镜像仓库、以及域名解析。这三者缺一个后续步骤都会卡壳。3.1 本地 Yum/Apt 源很多人做离线部署时会直接跳过这一步觉得操作系统上的软件包安装时再说。但实际上Kubernetes 依赖很多系统包比如conntrack-tools、socat、ipvsadm、ebtables这些在最小化安装的操作系统上很可能没有。如果没有本地软件源node 初始化时就会卡在依赖检查上。搭建本地源的办法并不复杂。在一台有公网访问的中转机上把所需的 rpm 或 deb 包全部下载下来然后通过 HTTP 服务共享给内网机器。以 CentOS/RHEL 系为例# 中转机上下载依赖包createrepo 生成仓库元数据 yum install -y createrepo yum install --downloadonly --downloaddir/repo/k8s-deps \ conntrack-tools socat ipvsadm ebtables # 生成仓库元数据 createrepo /repo/k8s-deps # 用 nginx 或 python 简单起一个 HTTP 服务 cd /repo nohup python3 -m http.server 8080 内网机器上配置本地源cat /etc/yum.repos.d/local.repo EOF [local-k8s] nameLocal K8s Dependencies baseurlhttp://mirror.internal:8080/k8s-deps enabled1 gpgcheck0 EOF yum clean all yum makecache我做这件事的时候总结了一个教训不要只下载当前需要的依赖把一台全新最小化安装的系统所需要的全部基础依赖都下载下来会稳妥得多。比如装完系统后执行yum update可能需要几百个包虽然 Kubernetes 本身不依赖这些但后续装其他组件时系统依赖不一致会带来各种奇怪问题。3.2 Harbor 镜像仓库部署与配置内网镜像仓库的选择目前主流就是 Harbor它自带 Web UI、项目隔离、镜像复制、RBAC功能完善且部署简单。Harbor 本身也是容器化的所以需要一个可用的容器运行时。在离线环境下你面临一个先有鸡还是先有蛋的问题需要 Harbor 来存镜像但 Harbor 自己也需要镜像才能跑起来。解决思路是先在一台有外网的机器上把 Harbor 的离线安装包下载下来官方提供harbor-offline-installer包里面包含了所有必要镜像拷入内网后用docker load或ctr -n harbor images import导入再执行./install.sh启动。Harbor 启动后有几个配置必须提前想清楚项目策略建议至少建两个项目一个library放 Kubernetes 系统镜像一个dev放开发测试业务镜像权限分离避免误操作复制规则如果有多个集群建议配置 Harbor 到其他节点的镜像复制这样节点本地就不需要单独缓存镜像了仓库容量开发测试环境的镜像迭代频繁Harbor 所在磁盘很可能被撑爆建议按项目设置配额比如每个项目限 50GB同时在 Harbor 里开启动态清理策略Harbor 的访问方式也值得斟酌。开发测试环境内网一般没有正式域名直接用 IP 访问即可。但如果后续要做多集群、多环境建议还是提前规划好统一的内部域名比如harbor.internal避免以后迁移时到处改 IP 地址。3.3 内网 DNS 规划这一步经常被忽略但它直接决定你后面是否要改一堆配置文件。Kubernetes 集群内部组件之间通信大量依赖域名解析特别是 apiserver 的地址。如果你用 IP 部署后面节点 IP 变了整个集群的证书和配置全部要重新做如果你用域名部署则只需要改 DNS 记录。开发测试环境建议这么规划给每台机器规划固定的主机名和域名比如node01.k8s.internal并统一写入/etc/hosts或内网 DNSapiserver 的地址使用一个负载均衡域名比如apiserver.k8s.internal即使单节点也可以这样设置为将来扩容留余地镜像仓库域名harbor.internal必须解析到 Harbor 所在节点内网纯/etc/hosts的方式在小规模集群3-5 台完全够用但节点多了以后维护成本上升建议直接用 CoreDNS 作为内网 DNS 服务器或者用云原生存储的方案。这里不展开但记住一个原则先定域名再定 IP域名是永远不会变的IP 会变。4. 从零到集群etcd、控制面、工作节点的完整安装流程这部分是整篇的核心。我按照实际安装的顺序来写并且会把每个步骤背后的逻辑解释清楚这样你知道每一步为什么这么做遇到问题也能自己判断。4.1 etcd 集群初始化etcd 是 Kubernetes 的存储后端所有集群状态都保存在这里。kubeadm 默认会自动部署 etcd但对开发测试环境来说我建议分开部署。原因很简单分开部署你可以控制 etcd 的存储路径、定期备份、资源上限而 kubeadm 内置的 etcd 比较难做这些定制。单节点的开发测试环境etcd 其实只需要一个实例但配置上建议按三节点的模式来写这样以后扩容不至于推倒重来。# 解压 etcd 二进制 tar -xzf etcd-v3.5.13-linux-amd64.tar.gz cp etcd-v3.5.13-linux-amd64/{etcd,etcdctl} /usr/local/bin/ # 创建数据目录和配置目录 mkdir -p /var/lib/etcd mkdir -p /etc/etcd这一步我认为最容易出错的地方是 etcd 的启动参数。至少要把以下几项配置正确--name节点名对应主机名--data-dir数据目录确保磁盘空间充足--listen-client-urls和--advertise-client-urls给 kube-apiserver 连接的地址建议用内网域名而不是 IP--listen-peer-urls和--initial-advertise-peer-urlsetcd 节点之间通信的地址--initial-cluster集群成员列表启动 etcd 后用一个命令验证etcdctl endpoint health --cluster --endpointshttps://node01.k8s.internal:2379 --cacert/etc/etcd/ca.pem --cert/etc/etcd/server.pem --key/etc/etcd/server-key.pem如果返回healthy说明 etcd 这一层没问题。这里要提醒一点etcd 的证书配置非常容易出错三个证书文件CA、服务端证书、服务端私钥缺一不可路径写错或者权限不对都会导致 kube-apiserver 连接失败。我给 etcd 和 kube-apiserver 通信开的是同一个 CA但不同组件用不同证书这样权限可控。4.2 kube-apiserver 与控制面组件控制器平面包含三个组件kube-apiserver、kube-controller-manager、kube-scheduler。kubeadm 可以一键初始化但如果你已经决定了自定义控制面的方式也可以逐个安装。两种方式我都试过。如果纯粹做开发测试验证用 kubeadm 最省事但如果你的环境有特殊安全要求比如自定义证书颁发机构、自定义审计日志策略那建议走手动部署路线。kubeadm 初始化的关键配置在kubeadm-config.yaml里离线环境最核心的两个配置就是镜像仓库地址和 pod 网段apiVersion: kubeadm.k8s.io/v1beta3 kind: ClusterConfiguration kubernetesVersion: v1.29.2 controlPlaneEndpoint: apiserver.k8s.internal:6443 imageRepository: harbor.internal/library networking: podSubnet: 10.244.0.0/16 serviceSubnet: 10.96.0.0/12imageRepository指向你 Harbor 里的library项目这样 kubeadm 会从内网仓库而不是registry.k8s.io拉取控制面镜像。controlPlaneEndpoint用之前规划的域名这样即使以后节点 IP 变了证书里的 SAN 和客户端连接都不用动。执行初始化时强烈建议加上--v5参数kubeadm init --configkubeadm-config.yaml --v5这个参数会打印详细日志一旦失败你能看到具体卡在哪个请求上。我见过很多人初始化失败后一脸懵就是因为没开详细日志看不到错误原因。初始化完成后记得把 kubeconfig 拷贝到当前用户目录mkdir -p $HOME/.kube cp -i /etc/kubernetes/admin.conf $HOME/.kube/config chown $(id -u):$(id -g) $HOME/.kube/config4.3 kubelet 与 kube-proxy 配置控制面初始化完成后工作节点需要做两件事安装 kubelet、kube-proxy然后加入集群。kubelet 是每个节点上最核心的代理进程它负责管理本节点的 Pod。它本身不是容器而是以系统服务方式运行的二进制进程所以它的启动配置分散在多个配置文件中。kubelet 的关键配置文件是/etc/kubernetes/kubelet.conf这个文件由 kubeadm 自动生成。离线环境下kubelet 启动后会从镜像仓库拉取 pause 容器如果你在初始化时把imageRepository指向了内网 Harbor那这一步会直接成功。但如果你用的是手动方式部署一定要确认 containerd 的sandbox_image配置指向了内网仓库的 pause 镜像# /etc/containerd/config.toml 关键配置 [plugins.io.containerd.grpc.v1.cri] sandbox_image harbor.internal/library/pause:3.9这个配置写错了最典型的表现是所有 Pod 一直处于ContainerCreating状态描述信息里显示拉取 pause 镜像超时。我自己就踩过这个坑排查了半天才发现不是业务镜像的问题而是基础配置里的 sandbox 镜像没改。kube-proxy 现在默认以 DaemonSet 方式运行由 kubeadm 直接处理不需要额外手工安装确保它的镜像已经同步到 Harbor 就可以了。4.4 CNI 网络插件的离线安装网络插件是 Kubernetes 集群能跑业务的前提。开发测试环境最常用的 CNI 是 flannel 和 calico前者配置简单后者功能全面支持网络策略。离线安装 CNI 的关键点在于两件事CNI 二进制文件要放到节点指定目录、CNI 镜像要提前推送到仓库。以 flannel 为例先下载 flannel 的 manifest 文件kube-flannel.yml把里面的quay.io/coreos/flannel镜像地址替换为内网 Harbor 地址然后kubectl apply -f kube-flannel.ymlCalico 也是类似的流程但 calico 的 manifest 比较大里面包含的镜像不止一个替换地址时要格外小心建议先用grep image把镜像列表拉出来逐一确认。这里还有一个容易踩的坑CNI manifest 里可能引用了配置字典或自定义资源这些资源定义文件CRD也要先安装。执行kubectl apply时如果报资源不存在的错误通常是 CRD 没有先创建需要检查 manifest 文件的顺序或者直接使用官方提供的完整部署包。5. 镜像离线搬运的最佳实践跳过 docker save直接上 skopeo镜像搬运是离线部署里最耗时、最容易出错的一步。很多人第一反应是用docker save和docker load来导出导入我强烈建议不要用这套方案原因后面会说。镜像搬运工具有很多我实际使用下来最顺手的就是 skopeo。5.1 为什么不推荐 docker save/loaddocker save会输出一个 tar 包里面包含镜像所有层和元数据适合完整导出。但它在面对多架构镜像、OCI 镜像格式、以及跨仓库复制这几个场景时存在明显弱点它需要先把镜像 pull 到本地占用磁盘空间而且 pull 过程中如果镜像比较大很容易超时它不支持从 registry 到 registry 的直接复制你必须先 save再拷文件再 load最后再 push中间每一步都产生冗余数据如果镜像本身就是多架构的docker save 通常只导出当前平台的镜像其他架构的副本会丢失skopeo 可以直接在 registry 之间复制镜像中间的层数据不需要落到本地磁盘速度快得多占用的临时空间也小得多。5.2 镜像同步脚本我的做法是在中转机上写一个批量同步脚本从images.list文件读取镜像列表用 skopeo 把它们从源仓库同步到 Harbor#!/bin/bash # sync-images.sh set -e SRC_LIST${1:-images.list} DEST_REGISTRYharbor.internal/library while IFS read -r img; do [ -z $img ] continue echo Syncing $img skopeo copy --all \ docker://${img} \ docker://${DEST_REGISTRY}/${img##*/} \ --src-tls-verifyfalse --dest-tls-verifyfalse \ --dest-creds admin:${HARBOR_PASSWORD} done $SRC_LIST--all参数很关键它保留源镜像的所有架构副本这样以后在 ARM 节点上部署时也能直接拉取对应架构的镜像不需要重新同步。--src-tls-verifyfalse和--dest-tls-verifyfalse是在内网使用自签名证书时的常规做法如果你们的证书是由内网 CA 签发的可以去掉这两个参数只配置系统信任即可。这个脚本看起来简单但实际运行中我发现几个需要处理的细节镜像名中可能带路径比如quay.io/calico/node${img##*/}处理后会变成node如果同一镜像名在多个 namespace 下都有要注意重名覆盖问题镜像总数多时建议加set -x打印执行过程方便看到哪一步失败了如果遇到同步失败脚本不会自动重试建议在循环里加一个重试逻辑比如失败后等 5 秒重试 3 次5.3 crictl 与 containerd 的对接镜像推送到 Harbor 之后节点侧的容器运行时 need 配置。当前主流容器运行时是 containerd不再使用 Docker 作为运行时。containerd 的交互工具是 crictl可以用来查看镜像、容器、Pod 的状态。离线环境下调试时我经常用 crictl 确认节点上是否已经有对应镜像crictl images crictl pull harbor.internal/library/nginx:1.25 crictl ps -acrictl 的配置文件在/etc/crictl.yaml一般内容如下runtime-endpoint: unix:///run/containerd/containerd.sock image-endpoint: unix:///run/containerd/containerd.sock timeout: 10 debug: falseruntime-endpoint写错是新手常见的坑写错了 crictl 会报连接失败。在 containerd 环境下默认 socket 路径就是/run/containerd/containerd.sock如果用了其他运行时例如 cri-o路径完全不同要对应修改。另外containerd 自身需要配置 registry 的访问方式。如果 Harbor 用了 HTTPS 且证书是自签的需要在/etc/containerd/certs.d/harbor.internal下放 CA 证书并在config.toml的[plugins.io.containerd.grpc.v1.cri.registry]里配置 hosts。这里改动之后必须重启 containerd 才能生效systemctl restart containerd很多人在节点上配置完 registry 镜像加速或者内网仓库地址后发现仍然拉取失败往往是忘记重启 containerd。这个操作重启不算危险但建议在业务部署之前完成避免影响已有的 Pod。6. 开发测试环境的专属优化资源配额、镜像预热与升级回滚离线部署不光是把集群装起来对开发测试环境来说装起来只是开始。开发测试环境的使用模式和生产差别很大需要针对性地做一些优化。6.1 多环境多租户的资源规划开发测试环境通常不止一套集群可能有 dev、test、staging 等多套。每套集群里还有不同团队在共用。这种场景下如果你不做资源隔离和配额管理很容易出现一个团队的测试任务把整个集群资源消耗光的局面。我建议在命名空间层面做两层控制。第一层是命名空间级别的ResourceQuota限制 CPU、内存、存储的总额第二层是每个工作负载的LimitRange限制单个 Pod 的资源上下限。apiVersion: v1 kind: ResourceQuota metadata: name: dev-quota namespace: dev spec: hard: requests.cpu: 16 requests.memory: 32Gi limits.cpu: 24 limits.memory: 48Gi persistentvolumeclaims: 10离线环境下这部分配置经常被忽略因为大家觉得反正是测试环境随便用。但事实证明开发测试环境的资源浪费往往比生产还严重。配额一定要在集群初始化之后立刻配置否则等业务都跑起来再补配额就会面临大量驱逐和调整比一开始就配好麻烦得多。6.2 镜像预热与本地缓存离线环境下镜像分发速度直接影响开发测试的效率。想象一个场景开发人员提交代码CI 构建出新的业务镜像推送到了 Harbor然后测试环境的 Pod 需要把它拉下来。如果节点第一次拉取这个镜像Harbor 在内网速度尚可但反复拉取同一镜像的多个 tag也会占用大量网络和磁盘 IO。解决方案是节点层面的镜像预热。有两个方向一个是 Harbor 项目里配置镜像复制把业务的常用镜像复制到集群所在网段的边缘仓库减少跨机房带宽消耗。另一个是使用 containerd 的镜像缓存能力在关键节点上预先用ctr images pull把常用的基础镜像全部拉取到本地这样后续创建任意 Pod 时基础层已经存在只需要拉取业务镜像速度会快一个量级。还有一个细节是镜像垃圾回收策略。开发测试环境里每个人可能不停构建镜像节点本地磁盘很容易被 containerd 的镜像缓存填满。containerd 有自己的垃圾回收机制但默认的阈值可能不满足你的需求。建议在部署时修改/etc/containerd/config.toml[plugins.io.containerd.grpc.v1.cri] max_container_log_line_size -1 [plugins.io.containerd.grpc.v1.cri.containerd] default_runtime_name runc discard_unpacked_layers truediscard_unpacked_layers设为 true 可以在镜像解包后删除中间层减少磁盘占用。但要注意这个设置会让每次拉取新镜像时多一次解包操作所以适合镜像迭代频繁、磁盘压力大的开发测试环境。6.3 离线升级与回滚策略开发测试环境也不能一直停留在一个版本Kubernetes 版本升级是迟早要面对的事。离线环境下升级的核心逻辑是先准备好新版本的二进制和镜像再按照节点逐个升级而不是在线升级。这里有一个我自己摸索出来的稳妥流程在准备阶段把新版本的 kubeadm、kubelet、kubectl 二进制和所有需要的镜像全部下载并推送到 Harbor在控制面节点上先升级 kubeadmkubeadm upgrade plan查看可升级版本执行kubeadm upgrade apply v1.29.2这一步会更新控制面组件逐个节点升级 kubelet先kubeadm upgrade node再替换 kubelet 二进制每个节点升级完成后再执行kubectl drain和kubectl uncordon升级过程中最大的风险是镜像版本不匹配。比如 kube-apiserver 已经升级到 v1.29但 etcd 镜像还停留在 v3.4虽然 kubeadm 会尽量兼容但如果你手动改了镜像 tag就会导致控制面组件之间版本不一致产生各种奇怪错误。回滚策略同样重要。我的建议是升级前一定先把 etcd 做一次快照备份。etcd 里保存了集群的所有状态只要 etcd 快照是完整的即使升级失败也能通过恢复快照回到升级前状态ETCDCTL_API3 etcdctl snapshot save /backup/etcd-snapshot-$(date %Y%m%d).db \ --endpointshttps://node01.k8s.internal:2379 \ --cacert/etc/etcd/ca.pem \ --cert/etc/etcd/server.pem \ --key/etc/etcd/server-key.pem平时也要养成定期备份的习惯。开发测试环境虽然不要求像生产那样严格但至少每次大的变更前做一次备份这能让你在踩坑之后快速恢复节省大把排查时间。7. 踩坑实录离线部署最常见的问题与排查思路最后这部分我把这几年实际遇到的高频问题整理一遍重点讲排查思路而不是只给结论。因为我觉得真正有价值的不是答案是什么而是你怎么一步步找到答案。7.1 镜像拉取超时的根因 registry 配置与证书没对上现象节点上 Pod 一直ImagePullBackOffkubectl describe pod显示拉取镜像超时。很多人的第一反应是网络问题于是在节点上测试到 Harbor 的连通性结果 ping 也通、curl 也能访问但容器运行时就是拉不下来。排查链路crictl pull手动拉一个测试镜像看报什么错如果报failed to resolve reference说明 containerd 的 registry hosts 配置没生效检查/etc/containerd/certs.d/harbor.internal/hosts.toml是否存在证书内容是否正确检查 containerd 配置文件是否被多次加载config.toml里如果同时配置了registry.mirrors和certs.d可能会有优先级冲突最后重启 containerd 再试这个问题根因要么是配置路径写错要么是证书不被信任。最让人头疼的是 curl 能访问 HTTPS但 containerd 不认证书这时需要在 hosts.toml 里配置ca路径或者使用skip_verify true先验证是不是证书问题。开发测试环境里我一般直接使用 HTTP 方式访问 Harbor省去证书配置的麻烦。但如果你要模拟生产还是建议用 HTTPS并且把 CA 统一分发到所有节点。7.2 kubeadm init 卡住不动的常见原因现象执行 kubeadm init 后一直卡在[wait-control-plane] Waiting for the kubelet to boot等了几分钟还是没反应。排查思路先看 kubelet 的运行状态systemctl status kubelet大概率是 active 但不断重启查看 kubelet 日志journalctl -u kubelet -f --no-pager看具体报错我遇到最多的情况是 kubelet 的 cgroup driver 与 containerd 不一致。kubeadm 默认使用 systemd而 containerd 默认可能是 cgroupfs两者不匹配会导致 kubelet 无法正常启动修复方法是统一 containerd 的 cgroup driver[plugins.io.containerd.grpc.v1.cri.containerd.runtimes.runc] [plugins.io.containerd.grpc.v1.cri.containerd.runtimes.runc.options] SystemdCgroup true这个配置非常关键而且它不报错只是集群迟迟起不来特别容易让人摸不着头脑。另一种常见情况是 harbor 里的镜像 tag 与 kubeadm 默认拉取的 tag 不一致。比如 kubeadm 期望v1.29.2的 kube-apiserver 镜像但 Harbor 里只有v1.29.1kubeadm init 会一直重试拉取表现就是长时间卡住。解决办法是在kubeadm-config.yaml里显式指定镜像版本或者按照 kubeadm 的期望版本把镜像补全。7.3 证书过期与时间同步问题离线环境还有一个容易被忽略的坑时间同步。内网机器如果无法访问公网的 NTP 服务器系统时间会逐渐偏移。而 Kubernetes 的证书体系对时间非常敏感节点时间漂移超过 5 分钟kubelet 与 apiserver 之间的 TLS 握手就会失败表现是x509: certificate has expired or is not yet valid。排查链路对比各节点时间date看是否一致如果时间不一致配置内网 NTP 服务器让所有节点同步同一时间源如果时间已经偏移很久证书过期可能已经发生简单的systemctl restart kubelet解决不了需要重新签发证书或者调整系统时间后重启集群组件这里建议在集群初装时就把时间同步纳入部署脚本。内网搭建一个简单的 NTP 服务其他所有节点指向它。如果没有条件搭建 NTP 服务至少要在 cron 里定期手工同步一次防止时间漂移过多。7.4 节点重启后集群无法自动恢复开发测试环境经常遇到断电重启集群节点起来后如果发现 Pod 全部处于Pending或Unknown状态先别慌。先确认 containerd 是否启动再看 kubelet 状态。很多时候只是 kubelet 没起来手动systemctl start kubelet就能恢复。但如果节点 IP 变了比如 DHCP 重新分配了地址那就麻烦了。etcd 记录的是旧 IPkubelet 上报的地址变了apiserver 可能不认。这种情况我建议在初始化时就把节点 IP 固定下来通过/etc/hosts或 NetworkManager 配置静态 IP。开发测试环境虽然灵活但 Kubernetes 集群的节点 IP 必须稳定。另外Flannel 或 Calico 的 overlay 网络状态也依赖节点 IP。如果 IP 变了即使 kubelet 恢复Pod 之间的通信可能仍不通这时候需要删掉 CNI 插件重建 Pod。这类问题没有现成的一键修复命令最可靠的办法是保证 IP 不变化从源头上规避。写在最后的小建议离线部署 Kubernetes 这件事说难也难说简单也简单。难的是各种细节交织在一起尤其是镜像、证书、DNS 这些隐性配置任何一个环节出问题都会导致整体失败。简单的是只要你把准备工作做扎实按顺序推进大多数问题都能在发生前就避免。我给准备做离线部署的同学一个建议第一次做的时候一定要写详细的部署文档把每一个命令、每一个配置项、每一处镜像版本都记录下来。做完之后把文档里的命令整理成脚本这样第二次部署只需要改几个参数就能跑通。我现在维护的开发测试集群从零到可用只需要两个脚本一个是准备阶段的物料下载脚本一个是初始化阶段的部署脚本。团队里任何人都能照着文档从零复现一套环境这就是离线部署流程化的价值。还有一点离线环境的镜像仓库就是你的生命线一定要做好备份和容量监控。Harbor 数据目录最好用独立磁盘定期备份数据库和镜像元数据。我见过有人因为 Harbor 磁盘满了导致镜像 push 失败进而整个 CI 流水线瘫痪这个连锁反应在离线环境里特别致命。如果你也采用了类似方案运行多套开发测试集群记得把仓库的监控告警也纳入日常巡检。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门