离线环境手动搭建K8S集群:从证书到Calico全流程详解

发布时间:2026/10/20 20:07:41

离线环境手动搭建K8S集群:从证书到Calico全流程详解 离线手动搭建K8S集群听起来像是自找麻烦。网上一搜全是 kubeadm 一条命令拉起来的教程生产环境里也确实没人手动拆零件装。但我自己把完整流程走了一遍之后最大的感受是——这不只是麻烦而是一堂极其高效的K8S内部原理课。所有在文档里模糊不清的概念比如 apiserver 到底怎么跟 etcd 说话、kubelet 又是怎么找到 apiserver 的、Pod 的网段和集群的网段到底谁定谁全都在手动搭的过程中被迫搞明白了。如果你是个刚入门 K8S 不久、不想只会复制粘贴 kubeadm init的人或者你接下来的环境天生就没有外网、连 docker pull 都费劲那这篇文章是写给你的。它会完整地讲解在没有互联网、没有 kudeadm 的情况下怎么一步步把 etcd、kube-apiserver、kube-controller-manager、kube-scheduler、kubelet、kube-proxy 用最原始的手动方式装起来并且最终能跑起来一个业务 Pod。1. 为什么偏偏要离线手动两个约束条件背后的真实收益先看离线的意义。离线场景绝对不是教科书里的假设现实中很常见——机房是隔离区生产网不允许访问外网或者企业内部有个内部软件源但容器镜像还是要人工拷进去。这种情况下用 kubeadm 的一大障碍是它默认要从网上拉镜像虽然可以通过配置文件指定镜像仓库但一旦你连内网仓库都没有整个流程就卡死在第一步。而手动搭建时所有组件都是静态二进制从官网 tar 包解压出来的你只需要拷一次二进制和镜像文件后面全靠 systemd 拉起网络依赖极小。再说手动。kubeadm 最大的问题恰恰在于它把一切细节都封装得太好了——它自动生成证书、自动生成 kubeconfig、自动初始化集群、自动把 Pod 网络插件装好。你用它是爽了但出了问题之后排查起来很痛苦你根本不知道 apiserver 的证书和 kubelet 的证书是分开签发的更不知道 role 绑定错了会导致 scheduler 起不来。手动搭建等于把 kubeadm 帮你做决定的部分翻出来重新问自己一遍每个组件之间怎么打交道、信任关系怎么建立、网段怎么不冲突答案就全在过程中了。我的建议是如果你完全没接触过 K8S不要上来就手动搭至少先用 kubeadm 跑通一个集群对正常长什么样有个印象。反过来如果你已经能熟练使用 kubectl 操作 Pod、Deployment但对控制面内部还是一团浆糊那就在一个干净的测试环境里手动搭一次非常值。手动搭建不是一个生产可用的替代方案而是一个搞懂 K8S 的捷径。2. 开始之前的物料准备证书、二进制、镜像一个都不能少动手之前物料准备是第一步也是离线和手动场景下最容易翻车的地方。你需要准备四样东西K8S 二进制、etcd 二进制、CNI 插件二进制、网络插件镜像如果想用容器方式部署 CNI。四样东西少一样后面就会卡住。2.1 版本选型和二进制获取我这次在模拟项目X的环境里用的版本是 Kubernetes 1.26.6etcd 3.5.9CNI 插件用的是 calico v3.27。这几个版本互相兼容是我在离线环境实测下来最稳的组合。二进制不推荐从源码编译除非你是装在某奇怪的 CPU 架构上否则直接用官方 release 页面里的服务器二进制 tar 包就行。kubernetes 的 tar 包里包含 apiserver、controller-manager、scheduler、kubelet、kube-proxy、kubectl 等全部二进制解压之后进 bin 目录就能看到。当然离线环境意味着你不能在现场下载。我通常的做法是在能联网的机器上一次性把需要的所有包下载好打完 tar 之后用移动硬盘或内网传输工具拷进去。文件清单列一下方便你对号入座kubernetes-server-linux-amd64.tar.gz约 1GBetcd-v3.5.9-linux-amd64.tar.gz约 30MBcalico 的 calicoctl 二进制和 calico.yaml 镜像清单cni-plugins-linux-amd64-v1.3.0.tgz约 80MBcalico 所需的几个镜像calico/cni、calico/node、calico/kube-controllers通过 docker save 保存为 tar 文件2.2 离线环境下 tar 包分发和镜像搬运的两种思路镜像的搬运有两种思路一种是推——把镜像推到内网镜像仓库让节点从内网拉另一种是拉——用 docker save 导出 tar 包拷到目标机器后 docker load 再导入。离线环境没有内网仓库的时候只能用第二种。但要注意一点docker load 只是把镜像放进了 Docker 的本地存储里并不会自动打上 tag。如果你 save 的时候没有带 repository 和 tagload 之后镜像会变成一堆没有 tag 的镜像后面容器启动直接报找不到镜像。正确做法是 save 的时候指定完整名字load 完立刻 docker tag 补上 tag。我在实际操作中还发现一个坑calico.yaml 里的镜像地址默认是 docker.io/calico/cni:v3.27.0离线节点上即使 load 了镜像如果 K8S 用 containerd 容器运行时而不是 dockershimcrictl 和 containerd 本身并不会自动识别 docker 的本地存储。所以如果节点用的是 containerd那得把镜像通过 ctr 导入到 containerd 的镜像存储里。不同运行时对应不同的导入工具这是个很容易忽略的差异点。这次我们用 containerd 做演示后面对应的导入命令会写清楚。另外这些镜像你不用全都拷到所有节点。分类放好etcd 镜像只发给 etcd 节点控制面组件的镜像如果有容器化部署的只发给 master 节点calico 的镜像则发给所有节点因为每个节点都会跑 calico 的 agent。规划好分发的粒度能省下不少拷贝的时间和磁盘空间。2.3 节点规划与网段设计离线手动集群的常见节点规划是三台机器一台 master两台 worker。master 节点同时充当 etcd 节点也就是所谓堆叠式 etcd最省机器。生产环境里 etcd 单独三台更好但在练习环境里复用节点没问题。网段设计是这次搭建中我认为最需要提前想清楚的部分一共涉及三个网段宿主机通信网段就是三台机器所在的物理/虚拟网络比如 192.168.1.0/24Pod 网段Cluster CIDRK8S 给每个 Pod 分配的网段例如 10.244.0.0/16Service 网段Service CIDRK8S 给 Service 分配的虚拟 IP 网段例如 10.96.0.0/12这三个网段必须互不重叠否则后面路由一乱你根本没法排查。我见过有人把 Pod 网段设成和宿主机网段192.168.1.0/24 相同的 192.168.0.0/24结果 Calico 启动后疯狂报 BGP 路由冲突折腾了两天才发现是网段规划问题。设计网段的第一原则宁可空间浪费也别互相踩踏。3. 证书体系设计手动搭建时最绕不过去的一关K8S 内部的几乎所有通信都是 TLS。手动搭建时证书必须自己签发这一步也是很多人第一次手动搭建时倒下的地方。不要慌证书体系整体看透了一遍之后会发现它没那么复杂只是数量多了一点。我用的是最经典的 openssl 自签发方案没有用 cfssl——不是 cfssl 不好而是 openssl 大多数人更熟悉出了问题更好排查。3.1 证书要签哪些、签给谁、有什么用按通信关系划分整个集群需要这几类证书etcd 的证书etcd 集群节点之间通信用的 peer 证书以及客户端访问 etcd 时验证用的 server 证书。除此之外etcd 还需要一个 client 证书供 apiserver 作为客户端访问 etcd 时使用。apiserver 的证书这是最复杂的一张。kubelet、kubectl、controller-manager、scheduler 都要访问 apiserver所以 apiserver 要给外部签发一张服务端证书并且把所有的访问入口地址写进 SANSubject Alternative Name。不仅仅是本机 IP还包括 Service 网段里那个虚拟的 kubernetes.default IP通常是 10.96.0.1。如果漏了 SAN 里的某个地址后面 kubectl 访问就会报证书验证失败。组件间的 client 证书kubelet 访问 apiserver 时要用 kubelet 自己的证书而不是共用 apiserver 的证书。controller-manager 和 scheduler 也一样每个组件都需要自己的 client 证书且带正确的 CNCommon Name和 OOrganization因为 K8S 的鉴权机制会用这两个字段识别调用者身份。kube-proxy 的证书它访问 apiserver 也需要证书单独签一张。整体算一下需要签发的证书至少八九张。建议你建一个清晰的文件目录来放这些证书我的习惯是 /etc/kubernetes/pki 下面再按组件分子目录比如 /etc/kubernetes/pki/etcd、/etc/kubernetes/pki/apiserver。这样后面查问题的时候一眼就能找到对应证书在哪。3.2 制作 CA 证书和核心签发脚本先创建 CA。CA 本质上是自签的根证书其余证书都由它签发。创建 CA 的命令很简单但重要的是 CA 私钥文件的权限要设成 600因为一旦私钥泄露整个集群的 TLS 信任体系就崩塌了。创建 CA 之后我给整个签发过程写了一个脚本批量生成所有证书。核心逻辑是先为每个组件生成私钥再生成 CSR 证书请求文件最后用同一个 CA 签发。有几个参数我现在都记得因为它们太关键了apiserver 证书的 SAN 必须是以下内容的集合本机 IPmaster 节点 IP比如 192.168.1.10三个 master 的 IP如果做了 HA但我们的堆叠式单 master 只有一台Service 网段第一个 IP10.96.0.1额外的几个 ClusterIP比如 Kubernetes svc 的 IP域名kubernetes.default.svc.cluster.local、kubernetes.default.svc、kubernetes.default、kubernetes如果只填了本机 IPkubectl 在本机能用但 kubelet 从 worker 节点访问 apiserver 时就会报证书错误因为 K8S 校验 apiserver 证书时必须看到访问时用的 IP 或域名在 SAN 列表里。etcd 证书方面peer 证书的 SAN 要包含三台机器的 IP即使目前只有一台也建议预留后续扩展的 IP因为 etcd 的 peer 之间是用 IP 直连的。server 证书则主要是给客户端访问用的IP 也写本机和 localhost 就够了。其实 etcd 的证书配置空间很灵活把自己控制范围内的节点 IP 全填进去是逻辑最安全的选择。3.3 生成 kubeconfig 文件而不是直接用证书连接很多人以为证书搞完手里的牌就齐了——只对了一半。实际操作中kubelet、controller-manager、scheduler 这些组件并不是直接拿证书去访问 apiserver 的而是通过 kubeconfig 文件。kubeconfig 是一个 YAML 文件里面封装了 apiserver 的地址、CA 证书路径、客户端的证书和私钥路径。换句话说它把所有连接凭证打包在一个文件里让组件不用在命令行里显式写一堆证书路径。这个文件要分成四份admin.kubeconfig给 kubectl 用的、controller-manager.kubeconfig、scheduler.kubeconfig、kubelet.kubeconfigworker 节点上 kubelet 用。生成 kubeconfig 并不需要手写 YAML直接用 kubectl config 系列命令就能生成用 --kubeconfig 指定输出文件路径即可。注意不同的文件里user 的 name 要和证书的 CN 一致。比如 kubelet 证书 CN 是 system:node:节点名那 kubelet.kubeconfig 里的 user 也要写成这个因为 apiserver 会根据 CN 来判断你是谁然后根据 RBAC 决定你有没有权限。而且 K8S 内置的 RBAC 规则里system:node 前缀的证书才有节点相关权限弄错了调度器会报 Unauthorized。CSR 的操作建议是把整个 K8S 组件证书的生成脚本固定成一个可复现的 shell 脚本放进仓库。我这边踩过的一次大坑是apiserver 证书过期后全部重新签发时etcd 的 client 证书没有一起更新导致 apiserver 连不上 etcd集群直接脑死亡。这提醒我证书之间的依赖关系要提前梳理清楚最好在脚本注释里写清楚每张证书被谁消费。4. 从 etcd 到 apiserver启动控制面组件的顺序和参数设计证书弄完开始真正往集群里塞二进制了。K8S 控制面的启动顺序是有讲究的先是 etcd然后 apiserver再 controller-manager 和 scheduler。这个顺序源于依赖关系apiserver 依赖 etcd 作为存储后端controller-manager 和 scheduler 依赖 apiserver 提供的 API 来干活。顺序乱了你会看到一些很奇怪的报错比如 controller-manager 启动后狂报failed to list *v1.Endpoints——大概率就是 apiserver 还没起。4.1 用 systemd 管理 etcd参数里的两处关键设计二进制解压到 /usr/local/bin 后我在 /opt/etcd 下建了 data 目录然后写了一个 systemd unit 文件来管理 etcd 服务。unit 文件本身不难写关键在 ExecStart 里的启动参数。手动搭建中etcd 的启动参数有两点值得认真对待第一initial-cluster 参数。哪怕是单节点也要写成 initial-clusteretcd1http://192.168.1.10:2380 这种带名字的格式不能省略名字只写 IP。如果省略etcd 在启动时会默认用 hostname 作为自身节点名而集群配置里的名字和实际名字对不上就会启动失败。第二证书相关参数要完整。手动方式下必须显式设置 client-cert-authtrue、peer-client-cert-authtrue然后分别指向对应的证书和私钥。没有设置的后果是apiserver 连接 etcd 时会因为证书验证不过而连不上——即使数据目录里看起来正常实际 etcd 一直在拒绝非法的 TLS 连接。我不推荐用 docker 跑 etcd除非你已经很熟悉 containerd 和 ctr 的用法。二进制方式写 systemd 反而更简单升级、排查也直观。而且 etcd 的数据目录建议放在独立的挂载盘上哪怕是模拟环境这个习惯也要养成——etcd 是 K8S 的黑匣子全集群状态都在里面磁盘挂了基本等于集群数据全没。启动完之后验证一下etcdctl endpoint health 能看到 healthy 状态就是成功了。这个命令需要指定证书参数建议把 etcdctl 的访问别名写进环境变量省得每次敲一大串证书路径。4.2 kube-apiserver启动参数最多的一个组件apiserver 的启动参数是我见过所有组件里最多的。不需要一条条全记但有几类必须理解--etcd-servers指向 etcd 的地址和端口必须带 https 前缀并搭配证书参数--service-cluster-ip-range、--service-node-port-range决定了 Service 网段和 NodePort 的端口范围。service-node-port-range 默认是 30000-32767手动时如果你希望用特定端口范围必须提前定好--client-ca-file、--tls-cert-file、--tls-private-key-fileapiserver 自身的 TLS 配置--authorization-modeNode,RBAC开启节点认证和 RBAC 授权。如果你把这个参数漏了后面 kubectl 可能可以访问但 kubelet 也会以很高权限操作集群非常危险--enable-bootstrap-token-auth / --token-auth-file手动场景下不需要 bootstrap token全部走证书认证所以不配也行apiserver 的 systemd unit 里我习惯把千参数拆成多行每条参数一个反斜杠连接这样 readable 程度高很多。启动后立刻在 master 上执行 kubectl get componentstatuses 检查各个控制面组件状态——如果你看到 etcd 显示 unhealthy先别慌大概率是 apiserver 访问 etcd 的证书没配对仔细核对 --etcd-certfile、--etcd-keyfile、--etcd-cafile 三项参数即可。4.3 controller-manager 和 scheduler权限前置与感知顺序controller-manager 和 scheduler 是控制面的后台大脑。controller-manager 负责监控各种资源状态比如 Deployment 副本数对不对、Node 有没有挂然后自动修正scheduler 负责给新 Pod 选节点。这两个组件的启动参数相对简单重点依然是证书和 kubeconfig。controller-manager 这里有个小细节我值得提--cluster-cidr 参数要和 Pod 网段一致。如果这个参数没设或设错controller-manager 里负责给 Service 分配 IP 的 controller 会出问题而且 cloud-provider 相关的路由功能可能异常。手动搭建时我吃过这个亏设成了默认值结果发现 Service 老拿不到 IP。scheduler 更简单只要知道 apiserver 地址和 kubeconfig 路径就行。但启动顺序上scheduler 必须等 apiserver 正常响应之后才能完整工作。你可以在 apiserver 还没起来之前启动 scheduler但它的寻找可调度节点动作永远会失败因为它看不到任何 Node 对象。所以我建议按顺序一个一个来别图省事一键全起。这两个组件起来后在 master 上执行 kubectl get pods --namespacekube-system 你会发现除了网络插件之外的空空如也——这是正常的因为 kubelet 还没注册 Node集群还感知不到计算节点。5. kubeletkube-proxy让 worker 节点真正入伙控制面起好master 自己还不能调度 Pod——K8S 里真正管理容器的角色是 kubelet它在每个节点上跑负责跟容器运行时打交道并把状态汇报给 apiserver。所以接下来要在所有 worker 节点也包括 master上安装 kubelet 和 kube-proxy。5.1 containerd 配置和 kubelet 的错位细节先装 containerd 和 CNI 插件二进制。我之前提到过离线环境下镜像导入是个容易出错的地方。如果你用的是 containerd 做运行时将 calico 镜像从 tar 导入到 containerd 用 ctr 命令。但 ctr -nk8s.io 这个参数非常重要——containerd 支持多命名空间K8S 默认使用 k8s.io 命名空间。如果导入时没指定命名空间docker 里 load 的镜像在 K8S 里完全找不到。kubelet 的启动参数里--node-ip 非常关键。在多网卡机器上kubelet 如果不指定 node-ip它可能选错 IP 注册到集群里导致后续 apiserver 访问这个节点走错网卡Pod 通信直接异常。建议显式指定。再说一个我反复踩过的坑kubelet 的 --cgroup-driver 必须与 containerd 的配置一致。K8S 1.24 以后 dockershim 被移除大家都用 containerd 了containerd 配置文件里默认可能没有设置 cgroup driver会导致 kubelet 启动报错说failed to run Kubelet: failed to validate kubelet cgroup driversystemd。解决办法是修改 containerd 的 config.toml把 SystemdCgroup 设为 true然后重启 containerd。这个坑的原因也很简单K8S 默认希望 cgroup 驱动是 systemd但 containerd 默认不启用 systemd cgroup如果两边不一致资源隔离就会出问题。kubelet 的 unit 文件里还建议设置 --pod-infra-container-image 指向一个 pause 镜像。手动搭建时这个镜像也要从外部导入。pause 镜像的作用是给每个 Pod 提供捡跑的基础容器负责持有网络命名空间。如果你在离线环境里忘了导这个镜像节点上 Pod 会一直处于 ContainerCreating且报错信息是拉取镜像失败——非常误导人因为它引用的镜像地址看起来一点都不像 pause 容器。5.2 kube-proxy 和集群网络的第一次打通kube-proxy 负责实现 Service 的 ClusterIP 和 NodePort 转发规则它本身不负责 Pod 之间的通信。它的启动参数主要就是 apiserver 地址和 kubeconfig模式默认 iptables。手动搭建时kube-proxy 有两种部署方式二进制以 systemd 托管或者以 DaemonSet 跑在集群里。这里我选择二进制 systemd 方式因为这一阶段集群内 Pod 网络还没建立以 DaemonSet 方式部署镜像反而跑不起来——鸡生蛋蛋生鸡的经典问题。当所有节点的 kubelet 都起来后回到 master 上执行 kubectl get nodes正常情况下能看到三台节点处于 NotReady 状态。NotReady 是正常的因为每个节点上的 kubelet 会持续向 apiserver 汇报状态而状态里包含一个 network 条件它取决于 CNI 插件是否就绪。没有 CNI 插件节点就永远 NotReady。这一步是控制平面已通、数据平面未通的临界点也是很多手动搭建教程止步的位置。接下来就该把 calico 请进场了。6. 用 Calico 打通 Pod 网络从 NotReady 到业务可用CNIContainer Network Interface是一个接口标准规定了容器运行时如何把网络配置交给 CNI 插件。我们选择 Calico因为它在离线环境里也有相对完整的部署方案BGP 模式不需要额外的键值存储不像 Flannel 的 VXLAN 需要 etcd 存储信息自带的 kube-controllers 也不依赖外部的 IPAM。当然如果用 Flannel 也可以只是 Calico 在企业场景覆盖更广包括 NetworkPolicy 的支持。手动搭建时用 Calico 可以顺带把 NetworkPolicy 的机制学一下。6.1 导入镜像离线环境最大的实操陷阱Calico 部署用 yaml 清单文件K8S 会按照清单创建 DaemonSet、Deployment、Service 等资源。但这些资源包含的镜像如果不存在Pod 就会 ImagePullBackOff。离线环境下这个问题更加尖锐因为节点根本没有外网。我们手边已经有 calico 的镜像 tar 包但导入的时候要注意如果 containerd 的命名空间是 k8s.io则镜像也必须在 k8s.io 下tar 包里的镜像 tag 要与清单里的 tag 完全一致一个字符都不能差清单文件里的镜像地址默认是 docker.io/calico/cni 这种全路径如果你导入时没有这个全路径 tagcontainerd 会当作一个不存在的镜像去拉取正确的导入方式是docker save 时保留完整路径比如 docker.io/calico/cni:v3.27.0docker load 或 ctr 导入后镜像 tag 自动就是 docker.io/calico/cni:v3.27.0。如果你之前 save 的是简化版 calico/cni:v3.27.0那 load 后还得多一步 docker tag把它改成 docker.io/calico/cni:v3.27.0。这不算技术难题但很少有人当场能反应过来——报错信息总是长长一串而原因却在镜像 tag 少了个前缀。如果节点上这种 Pod 一直 ContainerCreating建议先 crictl ps -a 看看具体的 sandbox 容器状态八成是 pause 镜像缺失再 crictl images 确认 calico 镜像是否存在。这套命令可以帮你快速定位是运行时问题还是网络问题。6.2 部署 Calico 清单时的两个必要修改拿到 calico.yaml 之后直接 kubectl apply -f 大概率不能工作。必须修改两个地方第一Pod 网段。calico.yaml 默认的 IPPool 网段是 192.168.0.0/16这和我们的宿主机网段冲突必须改成之前规划的 10.244.0.0/16。这一步如果你忘了calico 启动后会自动创建 IPPool你会看到节点上的 Pod 拿到 192.168.x.x 的地址这在你的宿主机网络里完全不通。第二在可能的情况下把 CALICO_IPV4POOL_CIDR 环境变量在清单里也改为 10.244.0.0/16保证单点配置不会覆盖掉。我建议在部署之前先检查一遍 calico.yaml 里所有相关的配置项包括 cni_network_config 里的 MTU 设置。如果宿主机网卡 MTU 是 1500而容器网络设置了一个明显偏大的 MTUPod 之间的性能会莫名变差。不是每个环境都需要调但知道这个选项在哪真出问题时你就能马上排查。6.3 Node 就绪后部署一个 Nginx 验证全链路Calico 的 DaemonSet 起来后等所有 calico-node Pod 都 Running回到 master 执行 kubectl get nodes应该看到三台节点都 Ready 了。此时集群的网络数据面已经打通跨节点的 Pod 可以直接互相访问Service 的规则也由 kube-proxy 生效了。为了验证全链路我习惯在集群里部署一个 Nginx 实例并暴露一个 NodePort 服务。这个验证只有三个步骤创建 Deployment 和 Service查看 Service 分配的 ClusterIP从集群外部访问任一节点 IP NodePort 端口如果访问失败先检查 Service 的 Endpoints 是否正常填充。如果 Endpoints 为空说明 kube-proxy 或者网络插件没把 Pod 的 IP 关联好这个问题的复现思路很固定从 NodePort 一层层往 Pod IP 追先确认节点 iptables 规则存在再确认 Pod IP 能 ping 通。手动搭建的每个组件都是我们亲手拉起来的所以排查链路反而比 kubeadm 更清晰——你知道每个组件在哪个机器上跑也知道它的配置文件在哪剩下的就是按部就班看日志。到这一步一个离线环境下的手动 K8S 集群就完成了。它能做的事情和 kubeadm 搭出来的几乎一样只是整个构建过程的透明度完全不同。7. 手动搭建完毕后的行动清单证书续期、备份与版本升级集群能跑起来前程似锦但手动搭建的维护责任比 kubeadm 重得多。你在搭建时绕开的工具在维护时也要绕过。这里给一份我总结的行动清单都是实践中真正会碰到的事。7.1 kubelet 证书轮换手动集群最容易踩的定时炸弹K8S 组件之间通信的证书都有有效期。手动搭建时如果你的自签证书有效期设成了默认的一年那一年后你就要面临证书过期的灾难。关键点不只是 apiserver 证书kubelet 的证书更容易被忽视——kubelet 默认会自动申请短期证书一年但如果集群没有配置证书轮换到期后 kubelet 就无法再访问 apiserver节点会变成一个失联节点非常难排查。手动集群建议在搭建时就考虑好证书续期方案。最简做法是写一个定时任务脚本定期检查集群里所有证书的过期时间提前一个月发告警。另一种做法是直接利用 K8S 自带的 kubelet 证书轮换机制配置 --rotate-certificatestrue 和 --rotate-server-certificatestrue。但注意开启这个功能的前提是集群配置了证书签名服务CSR API而这又要求 apiserver 正确配置了 --client-ca-file并且有相应的 RBAC 规则。你可以在文件 /etc/kubernetes/kubelet.conf 里查看 kubelet 持续连接的凭据理解这个机制的闭环。我个人的经验是手动搭建完集群第一件事就是把证书检查脚本写好并设成节假日前一天自动跑。这听起来有点夸张但我见过不止一次因为证书过期整个集群控制的墓碑上写着节点 NotReady、apiserver 拒绝一切请求而日志却只显示 tls: handshake failure 的灾难现场。7.2 etcd 备份和恢复演练K8S 的所有状态都保存在 etcd 里。手动搭建时etcd 的备份、恢复都是一条条命令自己敲的绕不开工具封装。但反而是手动搭的集群备份做起来最直接etcdctl snapshot save 一条命令然后定时拷走快照文件就行。真正值得说的是恢复演练。我建议在节点上准备一个恢复脚本脚本的逻辑很简单停掉 kubelet 和 etcd用快照文件恢复 etcd 数据目录再启动 etcd 和 kubelet。整个演练就在沙箱环境反复执行几遍别等到真出事再第一次做恢复——那样大概率会出现快照文件在但恢复不了的情况因为 etcd 数据目录里的 wal 文件和快照的版本对不上或者目录权限不对。手动搭建给了我们练习的机会别浪费。恢复之后最重要的一件事是验证集群状态kubectl get nodes 和 kubectl get pods --all-namespaces。如果 Pod 状态显示异常先别慌很多情况下是网络策略或者镜像拉取的问题需要配合日志逐层排查。7.3 离线升级路径手动搭建是可以平滑升级的用 kubeadm 的最大心理优势是升级有配套工具。手动搭建的集群升级本质上就是替换二进制下载新版本的 tar 包把旧二进制备份用新二进制替换重启对应 systemd 服务。听起来简单但里面有严谨的顺序要求先升级 etcd如果有新版本再升级 apiserver它会短暂停止服务再升级 controller-manager 和 scheduler最后逐个节点升级 kubelet 和 kube-proxy。升级前要重点检查的是 API 兼容性apiserver 的新版本是否还接受旧版本对象。如果跨大版本升级比如从 1.26 升到 1.27需要先看一下弃用特性清单确保清单里没有你正在使用的字段否则升级后资源可能被强制转换或者直接拒绝。升级过程中的版本差异也会影响之前的配置。如果 kubelet 的新版本不认旧版本 CNI 插件的配置格式你在升级时就要同步更新 CNI 插件的版本。这也是为什么我建议把整个集群的组件版本、镜像版本、配置文件全记录在一个文档里升级时对照着改就不会遗漏。还有一个细节离线环境升级时的 yaml 清单。kubeadm 可以自动生成新版本对应的清单手动搭建必须自己准备好新版本下的配置文件。如果是升级控制面组件可以直接复用原来的 systemd 参数其中 --feature-gates 等参数有变化时单独处理。升级完验证一条业务链路过一遍确认无碍再切流量。手动搭建集群的售后工作说穿了就是三件事证书别过期、etcd 别挂、升级有预案。能做齐这三件手动集群的稳定度完全不输 kubeadm 出来的集群。我在实际搭建过程中还有一个很深的体会当你在离线环境里被迫手动搭了一整套集群之后再回头用 kubeadm你会看得懂它每一步在干什么而不是盲目相信一行命令。很多文档里没有细讲的概念比如为什么 kubelet 需要访问 apiserver 而 apiserver 又要访问 etcd、为什么 Pod 网段要独立设计都会变成你排障时的肌肉记忆。如果你有条件找三台闲置的虚拟机把上面的步骤照着走一遍绝对比你读十遍理论文章收获要大。最后再说一个实用建议动手之前装好 bash 命令历史和时间戳记录每敲一条命令就回看一次手动搭建过程中最大的敌人不是技术难度而是你记不清到哪一步了。
延伸阅读

更多相关文章

2026/10/20 20:02:38

VS2019中直接使用wxWidgets二进制文件的工程化实践

1. 项目概述:为什么“直接使用二进制文件”是wxWidgets在VS2019中落地的关键突破口在Windows桌面应用开发圈里,wxWidgets一直是个“熟悉又陌生”的存在——熟悉,是因为它跨平台、开源、C原生、社区稳定;陌生,是因为从官…

2026/10/20 20:02:38

贝叶斯非负奖励模型BNRM:稳定RLHF奖励建模的新思路

做RLHF做到第三个月的时候,我彻底对“奖励模型”这四个字失去了安全感。当时用的标准奖励模型在验证集上的偏好分类准确率已经刷到了很不错的水平,可一旦接到策略优化里,模型就开始疯狂输出那种“看起来很有道理、读起来全是空话”的内容——…

2026/10/20 20:02:38

百度网盘免费发放500G空间了,大家记得及时领取

话不多说,直接上图免费网盘空间500G是有30天时效性的,不过每个自然月都是可领取的,大家过期之后记得重新领取即可。比较适合短期应急使用,全程免费,官方长期活动。

2026/10/20 21:08:09

开源视频生成模型H3-Base优化实战:VDN-H3、FastH3与QuantFunc对比

H3-Base 开源那天,我工作室的群一整天都没消停。大家兴奋的点倒不只是"又多了一个开源视频生成模型",而是这个基础模型在前后文一致性和镜头运动连续性上,确实做出了不少闭源方案都未必搞定的效果。紧接着社区传统节目就上演了&…

2026/10/20 21:08:09

队列与约瑟夫环:顺序队列、链式队列及环形逻辑的C++实现

1. 队列与约瑟夫环:从线性结构到环形逻辑的完整实现队列这个结构,很多人第一次学的时候觉得“这不就是排队吗”,然后写了个数组加两个下标就交差了。但真到用的时候,比如做任务调度、消息缓冲、BFS 遍历,才发现边界处理…

2026/10/20 21:08:09

iOS图片选取避坑指南:UIImagePickerController实战与迁移

1. 为什么UIImagePickerController至今仍是绕不开的坎做iOS开发的人,几乎都经历过这样一个阶段:产品经理跑过来跟你说“加个选照片的功能,要能拍照、能选相册、能裁剪”,你心想这不就是调个系统API的事,半小时搞定。结…

2026/10/20 21:08:09

纯前端实现视频首帧截取:canvas与video配合的完整方案与避坑指南

1. 从一个真实需求说起:为什么视频封面不能靠手动截图做过内容管理后台或者视频上传功能的人,大概率都遇到过这个场景:用户上传一个视频文件,列表页需要展示一张封面图。最原始的做法是让用户自己截好图再上传,但实际用…

2026/10/20 21:08:09

OpenJDK 11.0.10 ARM64 Linux 部署指南:架构匹配、环境配置与生产验证

简介:本资源是面向ARM64架构Linux服务器与嵌入式开发者的OpenJDK 11.0.10官方构建版,专为aarch64平台深度适配,解决Java开发者在国产化ARM服务器、云原生边缘计算及信创环境下的JDK运行与开发需求。压缩包共508个文件,涵盖72个jmo…

2026/10/20 21:03:06

技能图谱构建:从数据提取到可视化实践

我无法根据当前输入内容生成符合要求的博文。原因如下:输入中仅提供了项目标题"skills",未提供任何实质性的【项目正文】、【关键词】或【摘要描述】;所谓“相关热搜词”和“最新网络热词”部分为空,未给出具体词汇&…

2026/10/19 18:06:35

金融AI落地指南:从调研报告到技术选型与工程实践

简介:这份PDF报告聚焦2025年金融服务业中国AI现状与趋势,面向金融科技从业者、AI战略规划人员及关注行业数字化转型的研究者,帮助读者把握生成式AI在银行、支付、交易等场景中的落地路径与投资方向。资源包内含1个PDF文件,大小约1…

2026/10/20 1:05:37

单片机C运行时从启动到多任务:中断安全与共享资源冲突

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:05:37

GCC编译四步详解:预处理、编译、汇编、链接实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:05:37

FIW线是什么?FEP注塑线与普通氟塑料线的区别及应用指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑