
简介面向Kubernetes 1.18.8离线部署场景的集成资源包专为内网环境或需要快速搭建集群的运维工程师、平台开发人员准备压缩包围绕Master与Worker节点初始化所需组件和配置进行整理可低成本完成控制平面、工作节点与容器运行时的安装。整个压缩包共21个文件、610.15MB主要类型有sh安装脚本、yaml资源配置、service系统服务定义、tar/tgz离线镜像包以及kubeadm、kubectl、kubelet、crictl、conntrack、sealos等核心二进制和工具既有init.sh、master.sh、docker.sh等分阶段脚本也有calico.yaml、kubeadm.conf等关键配置便于按顺序执行和自定义修改。目前已有290人学习下载资源整合了离线镜像与Docker运行时并配有kubelet.service等systemd管理文件适合需要在内网搭建1.18.8版本Kubernetes集群、验证网络插件或多节点调度场景的读者对照shell脚本和YAML说明操作可有效降低上手门槛减少排查组件兼容性的时间。1. kube1.18.8.tar.gz一段被反复下载的离线安装包把kube1.18.8.tar.gz这个文件名扔进搜索引擎的大多是有过集群安装经验、又被内网环境按在地上摩擦过的人。这个包不是官方发布的固定产物而是某个团队把 Kubernetes 1.18.8 这套二进制、关联镜像和必要配置脚本一起打成 tar 包供离线环境安装用的“口粮”。它的出现非常朴素某机房里主机不能访问外网但业务又必须用 Kubernetes 统一管理于是先在一台能联网的机器上把kubelet、kubeadm、kubectl、etcd和一堆镜像缓存好再用 tar 包扔进内网。这个包解决的痛点是实打实的不用在离线机器上折腾 apt 源、不用面对docker pull的timeout、不用为一堆缺失的镜像翻来覆去找关系。这篇文章就是围绕kube1.18.8.tar.gz展开的落地笔记。内容包括我怎么拆这个包、怎么校验文件、怎么在离线环境里把 1.18.8 集群跑起来以及若干次部署过程中沉淀下来的参数和坑。适合的读者是手里有离线服务器、正在为装 Kubernetes 发愁的运维和平台工程师适合的场景是内网 IDC、专有云、科研实验室这类没有稳定外网的网络环境。下文不会替你重新发明 kubeadm只会告诉你用这个 tar 包时哪些步骤能省、哪些步骤绝对不能省。2. 拆包与规划先做容量设计再碰 tar.gz2.1 压缩包里到底装了什么先看清目录结构再动手拿到kube1.18.8.tar.gz之后第一件事不是急着tar -xzf而是先看一眼压缩包里都有哪些路径。常见做法是先用tar -tzf列出文件清单tar -tzf kube1.18.8.tar.gz | head -50这条命令只是列出 tar 包内的文件名不解压。看到输出后你就能知道包里是单纯的二进制目录还是连images、scripts、yaml一起打包的完整离线安装套件。一个完整的离线包通常包含这几类内容。kubernetes/bin/kubelet、kubeadm、kubectl 等核心二进制etcd/bin/etcd 及 etcdctlimages/一堆.tar格式的镜像文件比如kube-apiserver.tar、kube-controller-manager.tar、coredns.tar、pause.tarscripts/拉镜像、打 tag、初始化集群的辅助脚本yaml/calico、dashboard 等附加组件的编排文件如果文件列表里看不到images/目录后面镜像的导入流程就要自己处理提前做到心里有数。解压时我一般会单独建一个目录避免把二进制散落得到处都是。mkdir -p /data/kube-offline tar -xzf kube1.18.8.tar.gz -C /data/kube-offline这里的-C参数指定了解压目标目录这样后面找文件、写脚本都有固定路径不用在系统目录里翻来翻去。解压后留意一下kubernetes/bin下三个二进制的执行权限如果解压过程中权限丢了后续运行kubeadm init会直接报 permission denied。补权限用一行命令chmod x /data/kube-offline/kubernetes/bin/*2.2 版本匹配与节点角色规划先把清单画出来kube1.18.8.tar.gz这个文件名已经锁死了 Kubernetes 版本但这不意味着你不需要关心其他组件的版本。Kubernetes 1.18.8 对应的 etcd 版本一般是 3.4.3coredns 版本一般是 1.6.7如果包里自带的 etcd 是其他版本部署时就要警觉——Kubernetes 控制平面组件和 etcd 的小版本不能差太远否则可能出现未知的兼容性问题。规划节点角色是更关键的一步。我的经验是把节点分成两类控制节点Control Plane和工作节点Worker。控制节点跑kube-apiserver、kube-controller-manager、kube-scheduler、etcd需要 4 核 8G 起步工作节点跑kubelet和kube-proxy2 核 4G 可以跑一些轻量业务但生产环境建议 4 核 8G 以上。磁盘方面etcd所在的目录用 SSD容量至少 20G这块不能省etcd的 fsync 频次对 IO 延迟极其敏感。网络规划也要提前定好。我一般用 Pod CIDR10.244.0.0/16Service CIDR10.96.0.0/12这两个网段不能和主机现有内网网段重叠否则 flannel 或 calico 一启动路由表就乱成一锅粥。如果机房网段恰好落在了10.244.0.0/16或者10.96.0.0/12范围内必须换成其他网段否则集群内部通信会出现玄学级的间歇性不通。2.3 离线镜像区的准备把“联动文件”一起捋清无论 tar 包里有没有附带images目录离线部署 Kubernetes 都离不开镜像导入这一步。解压完成后下一步是确认本机 Docker 或 containerd 是否处于可用状态。这里有一个常见分歧点1.18.8 这个版本时期Docker 还是绝对的主流运行时containerd 虽已可用但使用率不高。如果你用的是 Dockershim那镜像直接用docker load导入如果你打算用 containerd 做运行时那要用的命令是ctr -nk8s.io images import。我先说一下大多数离线包里images目录和脚本的关系。tar 包里通常带一个load-images.sh脚本它会遍历images目录下所有.tar文件并逐个导入。但实际项目里我见过好几份离线包里的脚本和镜像版本不匹配——脚本里写着coredns:1.6.6但镜像文件名是coredns-1.6.7.tar。遇到这种情况不能用脚本一把梭要自己手动确认版本对应关系。# 列出images目录中所有镜像文件 ls -lh /data/kube-offline/images/ # 手动导入pause镜像kubelet的gc机制依赖pause docker load -i /data/kube-offline/images/pause-3.2.tar # 导入apiserver、controller-manager、scheduler、etcd镜像 docker load -i /data/kube-offline/images/kube-apiserver-1.18.8.tar docker load -i /data/kube-offline/images/kube-controller-manager-1.18.8.tar docker load -i /data/kube-offline/images/kube-scheduler-1.18.8.tar docker load -i /data/kube-offline/images/etcd-3.4.3.tar手动 load 的好处是每导一个镜像都能看到进度和日志出错时能立即定位是文件损坏还是镜像格式不受支持。如果 tar 包内的镜像数量比较多直接写个 for 循环更省事但要加一层对.tar后缀的判断避免把不相关文件也带进去。导入完成后用docker images | grep kube复查镜像是否齐备这一步检查不能跳过。3. 用 kubeadm 引导集群配置生成与参数调优3.1 生成 kubeadm 配置把默认参数改成离线环境需要的镜像和二进制都就位后下一步就是生成kubeadm的初始化配置。kubeadm init可以直接带参数跑但离线环境下镜像仓库地址、镜像版本、Pod CIDR 往往与默认值不一样所以我习惯先把配置写成文件再让kubeadm读取。好处是后续kubeadm join和集群运维时有据可查不至于把初始化参数当成黑匣子。先执行kubeadm config print init-defaults kubeadm-init.yaml生成的 YAML 里可以看到默认的imageRepository: k8s.gcr.io和kubernetesVersion: v1.18.0。这里的imageRepository必须改成你内网里的私有镜像仓库地址比如registry.private.localkubernetesVersion改成v1.18.8否则 kubeadm 会尝试从公网拉v1.18.0的镜像。还需要改的几个关键字段是localAPIEndpoint.advertiseAddress控制节点 IP、networking.podSubnetPod 网段、networking.serviceSubnetService 网段和nodeRegistration.criSocket如果不用 Docker而是使用 containerd就要改成/run/containerd/containerd.sock。修改完成后建议先跑一遍kubeadm config migrate检查配置文件的版本格式是否兼容因为 1.18.8 的 kubeadm 配置格式和后续版本略有出入。配置文件示例apiVersion: kubeadm.k8s.io/v1beta2 kind: ClusterConfiguration kubernetesVersion: v1.18.8 imageRepository: registry.private.local controlPlaneEndpoint: 192.168.10.10:6443 networking: podSubnet: 10.244.0.0/16 serviceSubnet: 10.96.0.0/12 --- apiVersion: kubeadm.k8s.io/v1beta2 kind: InitConfiguration localAPIEndpoint: advertiseAddress: 192.168.10.10 bindPort: 6443 nodeRegistration: criSocket: /var/run/dockershim.sockimageRepository这个字段是离线场景下最重要的参数。如果你的内网没有一个统一的镜像仓库而是把镜像直接 load 到各节点的 Docker 里那这个字段可以保留默认值吗答案是也可以但前提是 load 进来的镜像必须带完整的仓库地址前缀比如k8s.gcr.io/kube-apiserver:v1.18.8。离线包里的镜像文件名如果没有仓库前缀那 load 之后镜像名是不带k8s.gcr.io前缀的kubeadm 带着前缀去查镜像会查不到。这时要么手动把镜像打上k8s.gcr.io/前缀的 tag要么改配置文件里的imageRepository为实际镜像的前缀。我遇到的坑往往是离线包内的镜像根本没有前缀于是统一用脚本打 tag脚本思路如下# 先load镜像 docker load -i /data/kube-offline/images/kube-apiserver-1.18.8.tar # 给镜像补上k8s.gcr.io前缀 docker tag $(docker images -q kube-apiserver:v1.18.8) k8s.gcr.io/kube-apiserver:v1.18.8打完 tag 后可以加一次校验看看所有 kubeadm 可能拉取的镜像是否都已存在于本地kubeadm config images list --config kubeadm-init.yaml把上面命令的输出结果逐一与docker images的输出对照。kubeadm 自带有--dry-run参数但那个参数不会检查本地镜像是否存在所以最可靠的方式还是自己对照列表。3.2 预检 act 与 init跑通“黑匣子”第一段配置写好后第一道关口是环境预检。常见做法是强制执行kubeadm init --dry-run或直接kubeadm init跑一遍但我建议先跑kubeadm init --dry-run因为这能提前发现一些环境层面的硬伤比如/etc/docker/daemon.json里配置了iptables: false导致网络规则不生效、swap 没关、SELinux处于 enforcing 状态。Kubernetes 1.18 要求 swap 必须关闭预检脚本会直接报错。系统调优通常在这里完成swapoff -a sed -i /swap/s/^/#/ /etc/fstab modprobe br_netfilter sysctl -w net.ipv4.ip_forward1这几个命令的真实意图是把运行容器和转发数据包所依赖的内核功能打开。swapoff只在当前运行时生效改/etc/fstab是为了重启后不自动挂载 swapbr_netfilter的作用是让网桥流量也经过 iptables 规则Pod 间的网络策略才能生效。预检通过后开始正式初始化kubeadm init --config kubeadm-init.yaml --v5加上--v5是为了在初始化卡住时能看到详细日志。kubeadm init的执行过程可以拆成三段第一步生成证书和 kubeconfig第二步拉取镜像并启动控制平面容器第三步安装 CoreDNS 并更新 kubelet 配置。如果第二步卡住多半是镜像拉取问题第三步卡住多半是网络或 kubelet 本身的问题。初始化的输出结尾会有一段You should now deploy a pod network to the cluster的提示这说明控制平面已经起来了。初始化成功后会生成一个 config 文件mkdir -p $HOME/.kube cp -i /etc/kubernetes/admin.conf $HOME/.kube/configkubectl get nodes这时能看到控制节点已经 Ready但工作节点的 Ready 状态要等网络插件装好后才会有。如果主节点在初始化后的一段时间内处于NotReady状态这不算异常它的真正状态取决于 CNI 插件是否正常工作。3.3 工作节点加入token、CA 哈希与离线包三件套控制平面起来后工作节点加入集群是离线部署里最容易翻车的地方。原因是工作节点一样需要二进制、镜像和配置缺一个都无法注册成功。所以离线包里通常会预先为每个工作节点拷贝一份相同的kubernetes/bin和images目录。加入集群前工作节点上先做两件事装好 Docker并把控制平面初始化时加载过的镜像也 load 一遍。工作节点不跑 etcd也不需要 apiserver、controller-manager、scheduler 这三个镜像只需要 kube-proxy、pause、coredns 这几个基础镜像。但为了省事很多离线部署会把所有镜像一股脑 load 进去磁盘开销略大但不影响正确性。我一般会做一点小小的裁剪只 loadkube-proxy、pause、coredns这三个其余跳过能省几个 GB 的镜像存储空间。工作节点上的kubeadm join命令是初始化结束后控制平面输出的那一长串。如果当时没有保存可以重新生成 tokenkubeadm token create --print-join-command这条命令会打印出带有新 token 和--discovery-token-ca-cert-hash参数的 join 命令把它复制到工作节点执行即可。不过这里有个离线环境特有的问题--discovery-token-ca-cert-hash是控制节点 CA 证书的哈希如果控制节点的证书在离线包分发过程中被重新生成过那么旧 hash 就会失效join 时报错。稳妥做法是重新打印 join 命令再执行而不是把旧命令存到文档里反复用。join 执行成功后工作节点kubectl get nodes应立即能看到新节点注册但状态同样是NotReady需要等 CNI 网络插件在集群中运行起来这个我们放到下一章说明。4. 离线环境下的镜像分发与 CRI 配置4.1 两种镜像分发方案的取舍手动 load 还是自建仓库离线集群的镜像分发本质上是解决“在没有互联网连接的前提下如何把容器镜像从控制节点传递到工作节点”的问题。实际上业界常见做法有两种第一种是手动docker savedocker load把镜像文件拷贝到离线包所在目录再在各节点 load第二种是在内网部署一个 Harbor 或 Registry把镜像 push 进去各节点配置docker daemon指向内网 registry 拉取镜像。第一种方案的优点是实现简单、无需维护额外组件缺点是镜像更新时需要重新拷文件、重新 load批量升级节点时有点耗费时间。第二种方案适合集群规模较大或版本迭代频繁的场景但要先花一点时间部署 registry 并同步镜像。如果你的kube1.18.8.tar.gz本身已经包含了全部镜像文件我建议先用手动 load 跑通集群后续再根据业务迭代节奏决定是否补装 Harbor。先把集群跑起来比什么都重要镜像仓库这件事是优化项而不是必需项。手动 load 批量执行的小技巧是把镜像文件按固定的命名规则放好循环导入# images目录下为 xxx.tar 格式 for img in /data/kube-offline/images/*.tar; do echo load $img docker load -i $img done这个循环脚本不要写成不带引号的形式文件名中一旦出现空格脚本就会把路径截断。加载完成后最好把每个镜像文件与镜像 ID 对应关系记录到一个文本文件里后续在集群中排查问题时能快速定位。4.2 containerd 作为运行时sandbox_image 换掉才是关键如果离线包部署时选择 containerd 作为 CRI 实现有一个隐藏参数是绝对不能忽略的sandbox_image默认是k8s.gcr.io/pause:3.2但你无法访问外网拉取它。而且如果你跳过 containerd 的sandbox_image配置kubelet 创建 Pod 沙箱时会持续从默认地址拉取 pause 镜像表现出来就是 Pod 一直ContainerCreating事件报Failed to pull image k8s.gcr.io/pause:3.2。把这个参数改掉的方式是编辑 containerd 的配置文件/etc/containerd/config.toml在[plugins.io.containerd.grpc.v1.cri]段下找到sandbox_image字段并替换[plugins.io.containerd.grpc.v1.cri] sandbox_image registry.private.local/pause:3.2改完后要重启 containerdsystemctl restart containerd需要注意重启 containerd 会把正在运行的容器从节点上全部干掉所以这个改动应该在节点加入集群之前完成而不是在集群已经运行后再改。还有一点是 containerd 的config.toml如果不存在可以先用containerd config default /etc/containerd/config.toml生成默认配置再做修改。这种情况多发生在离线环境里 containerd 是刚用二进制方式安装的。另外/etc/containerd/config.toml里如果设置了systemd_cgroup false而 kubelet 配置里用的是 systemd cgroup 驱动也会导致节点无法启动容器两者必须一致。4.3 节点上的 kubelet 配置cgroup driver 不一致是最大坑节点能成功 join 集群与 kubelet 的 cgroup driver 有直接关系。Docker 和 containerd 的 cgroup driver 如果与 kubelet 指定的不一致kubelet 启动后会反复报错节点状态会从 Ready 到 NotReady 来回跳。Kubernetes 1.18 时代的推荐做法是让 Docker、containerd 与 kubelet 都使用systemd作为 cgroup driver。Docker 的配置在/etc/docker/daemon.json{ exec-opts: [native.cgroupdriversystemd], log-driver: json-file, log-opts: { max-size: 100m } }改完重启 Dockersystemctl restart docker这里还有一个容易忽略的点如果/etc/docker/daemon.json原本不存在需要手动创建并确保 JSON 格式合法否则 Docker 会启动失败。kubelet 对应的 cgroup driver 配置在kubeadm-init.yaml的nodeRegistration.kubeletExtraArgs中指定或者直接写在 kubelet 的 systemd unit 文件中。kubeadm init默认会按 Docker 实际情况推断 cgroup driver但离线环境里如果先用 containerd 跑过、再切换回 Dockerkubelet 的配置就会残留之前的信息导致一次改动后 kubelet 起不来。遇到这个现象先rm -rf /var/lib/kubelet下的旧配置缓存再重新 init这是我在几个项目里都踩过的坑。5. 避坑与排查用这个离线包部署 K8s 的 5 条血泪经验5.1 init 超时卡在 pull images日志显示拉取失败现象kubeadm init执行到Pull images阶段长时间无进展日志打印一行行拉取错误最终在[wait-control-plane]阶段超时失败。原因配置文件里的imageRepository没有指向离线包实际镜像所在的位置。kubeadm 默认从k8s.gcr.io拉取镜像离线环境访问不到这个地址报错属于必然。解决先把kubeadm-init.yaml里的imageRepository字段改成registry.private.local如果镜像已直接 load 到节点 Docker 中、没有配 registry则必须把镜像手动打上k8s.gcr.io/前缀的 tag。这里我踩过的最深的一个坑是离线包里镜像文件名是kube-apiserver.tar不带版本号load 之后镜像 tag 是v1.18.8但imageRepository又没改于是 kubeadm 反复拉k8s.gcr.io/kube-apiserver:v1.18.8却始终失败。改完配置后一定再跑一次kubeadm config images list和本地镜像列表比对确认每个条目都能对上。5.2 工作节点 join 后一直 NotReady事件日志显示 CNI 失败或 kubelet 报错现象join 命令正常执行返回提示符也没有报错控制平面上kubectl get nodes能看到新节点但节点的 STATUS 长时间为NotReady。原因绝大多数情况是 CNI 网络插件没有部署或者部署了但与 1.18.8 版本不兼容。如果kubectl get pod -n kube-system里能看到 calico/flannel 的 Pod 处于CrashLoopBackOffkubelet 检查网络插件失败节点就永远不会 Ready。解决先看节点上的 kubelet 日志journalctl -u kubelet -f -n 100如果日志里出现Container runtime network not ready就去检查 CNI 配置目录/etc/cni/net.d/是否存在有效配置文件。安装 calico 时要注意 calico 版本与 Kubernetes 1.18 的兼容性。calico 3.17 后续版本对 Kubernetes 1.18 的支持比较稳定。如果你离线包里带的是 calico 3.14 这类早期版本它默认依赖的 etcd 存储接口在 1.18 上还能用但kubectl apply -f calico.yaml前要手动修改里面的 Pod CIDR默认值是192.168.0.0/16如果与你的podSubnet不一致calico 会把所有节点上的路由都配错。第二次部署时我改好了 Pod CIDRcalico 启动就正常了。5.3 kubectl get nodes 报 connection refusedapiserver 也没起来现象初始化成功提示已经打印但执行kubectl get nodes报The connection to the server 192.168.10.10:6443 was refused。原因这通常不是 kube-apiserver 宕机而是后知后觉地发现 apiserver 一直在 crash-loop。多半是证书问题又或者是 etcd 没起来、apiserver 连不上 etcd 而反复重启。解决看 apiserver 的容器状态docker ps -a | grep apiserver docker logs kube-apiserver --tail 100如果是certificate signed by unknown authority的报错多数是从其他集群拷贝了/etc/kubernetes/目录导致的。kubeadm 初始化时已经生成过证书不能拿不同集群的证书乱凑。修复手段是清空/etc/kubernetes/和/var/lib/etcd重新 init如果是 etcd 连接失败检查控制节点上的etcd容器是否正常docker logs etcd里如果出现rafthttp: failed to find member多半也是从旧集群带过来的数据残留。用rm -rf /var/lib/etcd清掉数据文件再重来apiserver 就能正常起来了。5.4 dashboard 页面打不开证书或 NodePort 地址问题现象部署完 dashboard 后浏览器访问 NodePort 地址一直超时或者打开页面出现证书警告、页面空白。原因排行榜上最常见的原因是 dashboard 的 Service 类型设置不对或者 RBAC 权限不足。但离线环境里还有一个更容易被忽略的点dashboard 镜像的版本与 Kubernetes 1.18 不完全兼容。dashboard 2.0.x 之后对 Kubernetes 1.18 的适配已经相当完善但如果你离线包里带的是 1.10.x 那个时代的镜像登录页面就可能出现 403 或 500。解决先用kubectl get svc -n kubernetes-dashboard确认 Service 的类型和端口映射。如果是ClusterIP在集群外访问不到改成 NodePort 或使用kubectl proxy的方式访问。离线包若带了 dashboard yaml先检查里面有没有配置 Service 的type字段没有的话手动补spec: type: NodePort ports: - port: 443 targetPort: 8443 nodePort: 30001如果 dashboard 版本过旧优先换一个有-arm64或-amd64架构的匹配版本。dashboard 登录用的 token 在 1.18 版本里要从kubectl -n kubernetes-dashboard get secret里取如果灵异事件频发先检查dashboard-metrics-scraper这个组件是否 Crash它崩溃会导致页面部分区域白屏。5.5 证书过期1.18.8 默认一年期证书带来的隐患现象集群运行几个月后突然kubectl不可用报certificate has expired or is not yet validapiserver 日志里同样可以看到证书失效的报错。原因kubeadm 在 1.18 版本默认生成的证书有效期是 1 年不是 10 年。/etc/kubernetes/pki/下的 apiserver 证书、etcd 证书都带着明确的Not After日期。如果离线包是在几个月前打的包而部署时间又晚证书实际有效期就进一步缩短。解决部署完成后立刻检查证书有效期kubeadm alpha certs check-expiration如果发现有效期不够长可以在 init 之前给kubeadm init传--certificate-expiration参数或修改配置。但 1.18.8 的kubeadm对自定义证书有效期支持有限更稳妥的办法是初始化后马上把证书备份到安全位置同时加入监控系统的证书过期告警设置一个 30 天的提醒阈值。后续版本中有kubeadm renew命令来续期证书但 1.18.8 版本的kubeadm也支持kubeadm alpha certs renew all可以在证书快到期时执行续期然后重启控制平面容器。这条命令会更新/etc/kubernetes/pki/下的证书但 kubeconfig 里的 client 证书也要同步更新用完kubectl apply五件套重新加载admin.conf。我把这件事写进了节点维护文档每次集群发布时都记得跑一遍检查避免年底集中翻车。6. 验收与加固把离线集群变成可维护的资产6.1 集群自检脚本与验收清单先解决“看起来正常但实际有隐患”集群搭建完成后不要急着交给业务方先跑一套自检脚本。自检内容的粒度要细到什么程度不仅要看节点 Ready还要看核心组件的镜像版本是否和离线包一致、证书有效期、kube-proxy 与 kubelet 的启动参数、Etcd 集群健康状态。下面这段脚本我在多个项目上用过能快速覆盖主要指标#!/bin/bash # cluster-check.sh echo node status kubectl get nodes -o wide echo core components kubectl get pods -n kube-system -o wide echo etcd health kubectl -n kube-system exec etcd-$(hostname) -- sh -c \ ETCDCTL_API3 etcdctl --endpointshttps://127.0.0.1:2379 \ --cacert/etc/kubernetes/pki/etcd/ca.crt \ --cert/etc/kubernetes/pki/etcd/healthcheck-client.crt \ --key/etc/kubernetes/pki/etcd/healthcheck-client.key \ endpoint health echo certificate expiry kubeadm alpha certs check-expiration echo node disk/image df -h | grep -E (/|/var/lib/docker|/var/lib/etcd)脚本里的endpoint health如果输出healthy说明 etcd 没问题。证书检查如果显示expiration少于 180 天就要把证书续期排上维护计划。验收清单里还应该有一条在所有工作节点上跑一个测试 Pod确认跨节点互通。部署一个简单的busybox从一个节点ping另一个节点的 Pod IP能通就说明 CNI 策略和路由没问题。6.2 备份 kubeadm 配置与证书别等节点挂了才后悔集群维护中最值钱的东西是/etc/kubernetes/目录下的证书和控制平面配置。证书丢了整个集群的 kubeconfig 就废了所有客户端都要重新授权。所以集群初始化完成后第一件事就是打包备份证书目录。tar -czf kube-certs-$(date %Y%m%d).tar.gz /etc/kubernetes/pki /etc/kubernetes/*.conf备份文件不要放在控制节点本机应当同步到一台独立的备份服务器或对象存储。注意备份里有 etcd 的 CA 证书、apiserver 的私钥这些都是敏感文件要限制访问权限。恢复备份时先停掉 kubelet 和相关容器再把备份包解压到原路径之后重启 kubelet。实际遇到过控制节点系统盘损坏后从备份恢复的案例整套流程 30 分钟内就能恢复集群没有耽误业务太久。6.3 平滑更新节点镜像和 Kubernetes 版本的“后悔药”kube1.18.8.tar.gz 只是个起点集群一定会面临升级。1.18.8 的节点升级方式有两条路线一是kubeadm upgrade适用于离线包内带了新版本二进制的情况二是全量替换节点适用于虚拟机模板或物理机重装的场景。前者省事后者干净。我推荐的做法是维护一个“升级脚本包”里面放着新旧两套二进制的对比和镜像替换命令。升级前先备份/etc/kubernetes/然后一份一份替换/usr/local/bin下的kubeadm、kubelet、kubectl二进制不要一次性替换所有节点。控制平面升级顺序是先升级kube-apiserver、kube-controller-manager、kube-scheduler再升级kubelet最后升级工作节点的kubelet和kube-proxy。每升级一个组件都要执行kubectl get nodes确认状态发现异常立即回滚。回滚的话术很简单把旧版二进制拷回去重启 kubelet重载镜像。kubeadm 自带 upgrade 功能不会自动回滚所以旧二进制要保留一段时间等集群稳定运行一周后再清理。我在生产环境养成的习惯是离线包里保留上一版本的完整二进制和镜像防止新版本引入一些不可预见的兼容问题。这个习惯已经帮我救回了一次重要集群。希望这篇文章里提到的拆包、调参、排查的方法能帮到你每一步都少踩几个坑真正把离线集群变成可控的资产。本文还有配套的精品资源点击获取