Kubernetes v1.36.5 完全离线高可用部署方案3 Master 3 Worker控制面高可用HAProxy 本地负载均衡完全离线、全手动、参考kubekey。0. 方案概述与关键决策0.1 为什么是这套设计你明确要求完全离线、3 master 3 worker、借鉴 KubeKey 的 master 高可用实现、全手动、禁用 kubekey / 自动化脚本、K8s 1.36 最新子版本。经逐项确认最终决策如下grill 收口结果决策项选择说明K8s 版本v1.36.5官方发布页确认1.36 系列最新补丁版2026-09-15 发布控制面高可用HAProxy 本地负载均衡静态 Pod环境无 VIP故不采用 kube-vip采用 KubeKey 的 HAProxy 本地 LB 模式HAProxy 监听端口127.0.0.1:16443非 6443避开与 apiserver(0.0.0.0:6443) 抢端口免改 apiserver bind-address、免 join 时 patchetcdStacked 共置3 master 各一成员奇数 3 节点KubeKey 默认运维最简操作系统Ubuntu 22.04 LTSamd64apt/.deb 离线包离线镜像分发tar 包逐节点ctr import不部署私有仓库每节点完全自包含容器运行时containerd 2.3.xK8s 1.36 官方验证版本LTS 2.3kubeadm 默认 CRI网络插件Calico v3.32.2自包含清单非 operator兼容 K8s 1.34–1.360.2 架构ASCIIworker1 ─┐ worker2 ─┤ kubelet/kube-proxy ── 127.0.0.1:16443 (本机 haproxy) worker3 ─┘ │ ▼ round-robin (TCP) master1 ── haproxy(127.0.0.1:16443) ──┬── master1:6443 (apiserver) master2 ── haproxy(127.0.0.1:16443) ──┼── master2:6443 (apiserver) master3 ── haproxy(127.0.0.1:16443) ──┴── master3:6443 (apiserver) │ stacked etcd3 成员各 master 一个每个节点6 个都跑一个haproxy 静态 Pod监听本机127.0.0.1:16443四层 TCP 轮询到 3 个 master 的:6443。kubeadm --control-plane-endpoint127.0.0.1:16443所有组件kubelet、kubeconfig、kube-proxy统一连本机 haproxy。apiserver 保持默认0.0.0.0:6443不与 haproxy 冲突某 master apiserver 宕机haproxy 健康检查自动摘除流量切到存活节点。0.3 端口与证书要点haproxy 监听127.0.0.1:16443apiserver 监听0.0.0.0:6443。二者在不同端口绝对不冲突。kubeadm 会把controlPlaneEndpoint的主机部分127.0.0.1写入 apiserver 证书 SAN并已在kubeadm-init.yaml的certSANs中显式加入 3 个 master 的 IP 与主机名。haproxy 对后端做HTTPS健康检查check-ssl verify none命中 apiserver 的/healthz。1. 替换表部署前必须填写把下表中的占位值替换为你真实环境的值。全文命令中的XXX均来自此表。占位符含义示例占位请替换MASTER1_IPmaster1 IP10.0.0.11MASTER2_IPmaster2 IP10.0.0.12MASTER3_IPmaster3 IP10.0.0.13WORKER1_IPworker1 IP10.0.0.21WORKER2_IPworker2 IP10.0.0.22WORKER3_IPworker3 IP10.0.0.23POD_CIDRPod 网段10.244.0.0/16SVC_CIDRService 网段10.96.0.0/12LB_PORThaproxy 本机端口16443API_PORTapiserver 端口6443K8S_VERK8s 版本v1.36.5CONTAINERD_VERcontainerd 实际下载版本2.3.x离线准备时记录CALICO_VERCalico 版本v3.32.2OFFLINE_DIR离线资源挂载/拷贝目录/opt/offline网络规划节点/ Pod / Service CIDR均为占位如你已有真实网段直接替换表中对应项并在kubeadm-init.yaml同步修改podSubnet/serviceSubnet。kubeadm-init.yaml内容# kubeadm init 配置文件第一个 mastermaster1 使用# 注意 apiVersion本机 kubeadm 若报不支持 v1beta3# 先运行 kubeadm config print init-defaults 取实际版本替换头部。apiVersion: kubeadm.k8s.io/v1beta3 kind: InitConfiguration localAPIEndpoint: advertiseAddress:MASTER1_IP# 写死为 master1 的 IPbindPort:6443nodeRegistration: criSocket: unix:///run/containerd/containerd.sock imagePullPolicy: IfNotPresent name: master1 kubeletExtraArgs: node-ip:MASTER1_IP--- apiVersion: kubeadm.k8s.io/v1beta3 kind: ClusterConfiguration kubernetesVersion: v1.36.5# 控制面端点指向本机 haproxy127.0.0.1:16443kubeadm 会自动把 127.0.0.1 写入 apiserver 证书 SANcontrolPlaneEndpoint:127.0.0.1:16443imageRepository: registry.k8s.io apiServer:# 额外 SAN把 3 个 master 的 IP 与主机名都加进去避免任何组件直连节点 IP 时报证书错误certSANs: -127.0.0.1-MASTER1_IP-MASTER2_IP-MASTER3_IP-master1-master2-master3networking: podSubnet:10.244.0.0/16serviceSubnet:10.96.0.0/12dnsDomain: cluster.local etcd: local: dataDir: /var/lib/etcd controllerManager:{}scheduler:{}--- apiVersion: kubelet.config.k8s.io/v1beta1 kind: KubeletConfiguration cgroupDriver: systemd2. 拓扑规划角色主机名IP组件master1master1MASTER1_IPkube-apiserver / kube-controller-manager / kube-scheduler / etcd / haproxy / kubeletmaster2master2MASTER2_IP同上master3master3MASTER3_IP同上worker1worker1WORKER1_IPkube-proxy / kubelet / haproxyworker2worker2WORKER2_IP同上worker3worker3WORKER3_IP同上所有节点需满足2 CPU / 2 GB 内存节点间全互通含 6443 / 16443 / 2379-2380 / 10250 等各节点已设置唯一主机名并写入/etc/hosts。3. 离线资源准备有网机器Ubuntu 22.04 amd64在与集群**同架构amd64**且有 Internet 的 Ubuntu 22.04 机器上完成。产出两类离线包二进制 deb 包kubeadm/kubelet/kubectl/kubernetes-cni/cri-tools/containerd.io 及其依赖容器镜像 tar 包K8s 控制面镜像、etcd、coredns、pause、Calico 镜像、haproxy 镜像另需把calico.yamlCalico 清单一并下载。最后用 USB / 内网共享拷贝到每台目标机。3.1 准备 apt 源仅在有网机上执行# Kubernetes 官方 apt 源v1.36 稳定线sudoapt-getupdatesudoapt-getinstall-yapt-transport-https ca-certificatescurlgpgcurl-fsSLhttps://pkgs.k8s.io/core:/stable:/v1.36/deb/Release.key\|sudogpg--dearmor-o/etc/apt/keyrings/kubernetes-apt-keyring.gpgechodeb [signed-by/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.36/deb/ /\|sudotee/etc/apt/sources.list.d/kubernetes.list# Docker 官方 apt 源用于取 containerd.iocurl-fsSLhttps://download.docker.com/linux/ubuntu/gpg\|sudogpg--dearmor-o/etc/apt/keyrings/docker.gpgechodeb [signed-by/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu jammy stable\|sudotee/etc/apt/sources.list.d/docker.listsudoapt-getupdate3.2 下载二进制 deb含依赖--download-only收集到 apt 缓存mkdir-p~/offline/pkgsudoapt-getinstall--download-only-y\kubeadm kubelet kubectl kubernetes-cni cri-tools containerd.io# 把缓存里的所有 .deb含依赖收集出来cp/var/cache/apt/archives/*.deb ~/offline/pkg/ls-lh~/offline/pkg/# 记录 containerd 实际版本写入替换表 CONTAINERD_VERdpkg-deb-f~/offline/pkg/containerd.io_*.deb Version3.3 拉取并导出容器镜像用 containerd 的 ctr镜像名原样保留离线节点按原名导入# 安装 containerd仅用于在有网机拉镜像可复用 3.2 已下载的 debsudodpkg-i~/offline/pkg/containerd.io_*.debsudosystemctl start containerd# —— K8s 控制面镜像用 kubeadm 枚举精确 tag杜绝猜测 ——kubeadm config images list --kubernetes-versionK8S_VER# 先确认清单forimgin$(kubeadm config images list --kubernetes-versionK8S_VER);doctr images pull$imgsafe$(echo$img|tr/:__)ctr imagesexport${safe}.tar$imgdone# —— Calico 镜像先下载清单再 grep 出真实镜像名 ——curl-sSLhttps://raw.githubusercontent.com/projectcalico/calico/CALICO_VER/manifests/calico.yaml\-o~/offline/calico.yamlgrep-oEquay\.io/calico/[a-z0-9._-]:[v0-9.]~/offline/calico.yaml|sort-u~/offline/calico-images.txtecho Calico 镜像清单 ;cat~/offline/calico-images.txtwhileread-rimg;do[-z$img]continuectr images pull$imgsafe$(echo$img|tr/:__)ctr imagesexport${safe}.tar$imgdone~/offline/calico-images.txt# —— haproxy 镜像静态 Pod 用——ctr images pull docker.io/library/haproxy:3.0 ctr imagesexporthaproxy__library_haproxy_3.0.tar docker.io/library/haproxy:3.0mkdir-p~/offline/imagesmv*.tar ~/offline/images/ls-lh~/offline/images/3.4 打包并搬运到离线环境cd~tarczf k8s-offline-K8S_VER.tgz-C~/offline pkg images calico.yaml calico-images.txt# 通过 USB / 内网共享把 k8s-offline-K8S_VER.tgz 传到每台目标机的 OFFLINE_DIR 并解压# 在每台目标机上mkdir-pOFFLINE_DIRtarxzf k8s-offline-K8S_VER.tgz-COFFLINE_DIR4. 所有节点基础准备6 节点通用逐台执行以下命令在master1/master2/master3/worker1/worker2/worker3上各执行一遍。用变量简化以 master1 为例NODE_IPMASTER1_IP其余节点替换为各自 IP。# 4.1 关闭 swapK8s 要求并写入模块/参数swapoff-ased-i/ swap / s/^/#//etc/fstabcat/etc/modules-load.d/k8s.confEOF overlay br_netfilter EOFmodprobe overlay modprobe br_netfiltercat/etc/sysctl.d/k8s.confEOF net.bridge.bridge-nf-call-iptables 1 net.bridge.bridge-nf-call-ip6tables 1 net.ipv4.ip_forward 1 EOFsysctl--system# 4.2 主机名与 hosts每个节点写自己的 其余 5 个或统一一份分发hostnamectl set-hostname本机主机名# 如 master1cat/etc/hostsEOF MASTER1_IP master1 MASTER2_IP master2 MASTER3_IP master3 WORKER1_IP worker1 WORKER2_IP worker2 WORKER3_IP worker3 EOF# 4.3 安装离线 deb含 containerd 及其依赖dpkg-iOFFLINE_DIR/pkg/*.deb# 若报依赖缺失极小概率同一目录已包含全部依赖执行# apt-get -f install -y # 注意此时必须仍处于离线状态依赖已在 pkg/ 内# 4.4 containerd 配置SystemdCgroup 指定 pause 镜像mkdir-p/etc/containerd containerd config default/etc/containerd/config.tomlsed-is/SystemdCgroup false/SystemdCgroup true//etc/containerd/config.tomlsed-is#sandbox_image .*#sandbox_image registry.k8s.io/pause:3.10#/etc/containerd/config.toml# 确认改动生效grep-ESystemdCgroup|sandbox_image/etc/containerd/config.toml systemctl daemon-reload systemctlenable--nowcontainerd# 4.5 加载离线镜像到 containerd 的 k8s.io 命名空间保留原名kubeadm 才能识别forfinOFFLINE_DIR/images/*.tar;doctr-nk8s.io imagesimport$fdone# 校验关键镜像已就位ctr-nk8s.io images list|grep-Ekube-apiserver|etcd|pause|calico|haproxy# 4.6 启动 kubelet此时还未 join会 crashloop属正常systemctlenable--nowkubelet5. 部署 HAProxy 本地负载均衡6 节点通用逐台执行每个节点都部署保证 master 加入时也能通过127.0.0.1:16443找到存活 apiserver。# 5.1 写 haproxy 配置替换 3 个 master IPinstall-d-m0755-oroot-groot /etc/haproxysed-es|MASTER1_IP|MASTER1_IP 实际值|g\-es|MASTER2_IP|MASTER2_IP 实际值|g\-es|MASTER3_IP|MASTER3_IP 实际值|g\OFFLINE_DIR/files/haproxy.cfg/etc/haproxy/haproxy.cfg# 说明上面三处把占位换成真实 IP也可直接 vi 编辑 /etc/haproxy/haproxy.cfg# 5.2 放静态 Pod 清单由 kubelet 托管cpOFFLINE_DIR/files/haproxy-static-pod.yaml /etc/kubernetes/manifests/haproxy.yaml# 5.3 等待 haproxy 监听 16443kubelet 拉起约 10~30 秒foriin$(seq130);doifss-ltn|grep-q:16443;thenechohaproxy up;break;fisleep2doness-ltn|grep16443worker 节点此时/etc/kubernetes/manifests/仅含 haproxymaster 节点后续kubeadm init/join会再写入 apiserver/controller/scheduler 等静态 Pod。haproxy-static-pod.yaml的内容# HAProxy 静态 Pod 清单KubeKey 将 haproxy 以静态 Pod 形式部署在每个节点# 将此文件放到每个节点的 /etc/kubernetes/manifests/haproxy.yaml由 kubelet 托管。# hostNetwork: true 才能在本机 127.0.0.1 上监听配置通过 hostPath 挂载。apiVersion: v1 kind: Pod metadata: name: haproxy namespace: kube-system labels: app: haproxy spec: hostNetwork:truepriorityClassName: system-node-critical containers: - name: haproxy image: docker.io/library/haproxy:3.0 imagePullPolicy: IfNotPresent volumeMounts: - name: cfg mountPath: /usr/local/etc/haproxy/haproxy.cfg readOnly:truelivenessProbe: tcpSocket: port:16443initialDelaySeconds:5periodSeconds:5resources: requests: cpu: 50m memory: 32Mi volumes: - name: cfg hostPath: path: /etc/haproxy/haproxy.cfg type: Filehaproxy.cfg的内容# HAProxy 本地负载均衡配置KubeKey 本地 LB 模式的手动复刻# 每个节点3 master 3 worker都相同监听本机 127.0.0.1:16443# 四层 TCP 轮询到 3 个 master 的 apiserver(:6443)。# 选用 16443 而非 6443 的原因apiserver 保持默认 0.0.0.0:6443# 避免同节点 haproxy 与 apiserver 抢 6443 端口冲突也免改 apiserver bind-address。global log127.0.0.1 local0 maxconn4000chroot/var/empty user haproxy group haproxy daemon defaults log global mode tcp option tcplogtimeoutconnect 30stimeoutclient 30mtimeoutserver 30m frontend kube_api_frontendbind127.0.0.1:16443 mode tcp default_backend kube_api_backend backend kube_api_backend mode tcp balance roundrobin# apiserver 安全端口 6443 提供 /healthz(HTTPS)用 https 健康检查option httpchk GET /healthz http-checkexpectstatus200default-server inter 15s downinter 15s rise2fall2slowstart 60s maxconn1000maxqueue256weight100server master1MASTER1_IP:6443 check check-ssl verify none server master2MASTER2_IP:6443 check check-ssl verify none server master3MASTER3_IP:6443 check check-ssl verify none6. 初始化第一个 Master仅在 master1# 6.1 准备 init 配置替换 master1/2/3 的 IPsed-es|MASTER1_IP|MASTER1_IP 实际值|g\-es|MASTER2_IP|MASTER2_IP 实际值|g\-es|MASTER3_IP|MASTER3_IP 实际值|g\OFFLINE_DIR/files/kubeadm-init.yaml/root/kubeadm-init.yaml# 如本机 kubeadm 不支持 v1beta3先 kubeadm config print init-defaults 取实际 apiVersion 替换头部# 6.2 执行初始化--upload-certs 便于后续 master join 共享证书kubeadm init--config/root/kubeadm-init.yaml --upload-certs --node-name master1# 6.3 配置 kubectlmkdir-p$HOME/.kubecp-i/etc/kubernetes/admin.conf$HOME/.kube/configchown$(id-u):$(id-g)$HOME/.kube/config# 6.4 记录 join 信息务必保存后续 master/worker 加入要用# 输出中类似# kubeadm join 127.0.0.1:16443 --token TOKEN \# --discovery-token-ca-cert-hash sha256:HASH --control-plane --certificate-key CERT-KEY# 若漏存 certificate-key重新生成kubeadm init phase upload-certs --upload-certs7. 加入其余 Mastermaster2、master3各执行一遍# 在 master2 上master3 同理--node-name 改为 master3kubeadmjoin127.0.0.1:16443\--tokenTOKEN\--discovery-token-ca-cert-hash sha256:HASH\--control-plane\--certificate-keyCERT-KEY\--node-name master2# 配置 kubectlmkdir-p$HOME/.kubecp-i/etc/kubernetes/admin.conf$HOME/.kube/configchown$(id-u):$(id-g)$HOME/.kube/config# 在 master1 上验证 etcd 集群成员应看到 3 个ETCDCTL_API3etcdctl\--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\member list-wtablemaster2/3 加入时本机 haproxy第 5 步已部署让127.0.0.1:16443可达存活的 master无需 patch apiserver。8. 加入 Workerworker1/2/3各执行一遍kubeadmjoin127.0.0.1:16443\--tokenTOKEN\--discovery-token-ca-cert-hash sha256:HASH\--node-name worker1# worker2/worker3 同理改 --node-name若 token 过期在 master1 重新生成kubeadm token create --print-join-command9. 安装 Calico 网络插件在 master1# 9.1 修改 Pod CIDR 与集群一致默认是 192.168.0.0/16改为 POD_CIDR# 在 OFFLINE_DIR/calico.yaml 中定位 CALICO_IPV4POOL_CIDR改为# - name: CALICO_IPV4POOL_CIDR# value: POD_CIDRgrep-nCALICO_IPV4POOL_CIDROFFLINE_DIR/calico.yaml# 9.2 应用镜像已在第 3/4 步离线预加载imagePullPolicy 默认 IfNotPresent不会联网kubectl apply-fOFFLINE_DIR/calico.yaml# 9.3 等待就绪kubectl-nkube-system get pods-lk8s-appcalico-node-wkubectl-nkube-system get pods-lk8s-appcalico-kube-controllers10. 集群高可用验证# 10.1 节点全部 Readykubectl get nodes-owide# 10.2 核心组件 Pod 全 Runningkubectl get pods-A# 10.3 etcd 三节点健康ETCDCTL_API3etcdctl\--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\endpoint status-wtable# 期望3 条记录均为 true(isLeader 或 follower)# 10.4 控制面 Leader 选举正常controller-manager / schedulerkubectl-nkube-system get leases kube-controller-manager kube-scheduler# 10.5 控制面故障转移测试在 master1 上停掉 apiserver验证集群仍可用# 找到 apiserver 容器并停止crictlps|grepkube-apiserver crictl stopapiserver容器ID# 另开会话验证流量经 haproxy 切到其他 masterkubectl get nodes# 应仍正常返回kubectl get pods-A# 恢复crictl start apiserver容器ID 或 kubelet 会自动拉起静态 Pod11. 故障排查与注意事项端口不冲突是设计前提haproxy 在16443、apiserver 在6443。若错误把 haproxy 配成6443会与 apiserver 抢端口导致双方异常——务必保持16443。haproxy 必须先于/伴随 kubelet 启动/etc/kubernetes/manifests/haproxy.yaml放在 kubeadm init 之前kubeadm init 时通过127.0.0.1:16443访问 apiserverhaproxy 已在监听。证书 SANcontrolPlaneEndpoint127.0.0.1:16443已自动加入 SANcertSANs中已显式列出 3 个 master IP/主机名。若以后有组件直连节点 IP 报x509先确认对应 IP 已在 SAN。镜像全部离线预加载kubeadm 控制面镜像、etcd、coredns、pause、Calico、haproxy 均已ctr -n k8s.io images importCRI 默认IfNotPresent不会触发联网拉取。若某 PodImagePullBackOff先ctr -n k8s.io images list | grep 镜像确认是否已导入、名字是否完全一致。kubelet 在 join 前 crashloop 属正常未加入集群时 kubelet 反复重启是预期行为join 后自动恢复。kubeadm config apiVersion文档用v1beta3如本机 kubeadm 报错运行kubeadm config print init-defaults查看并替换为实际版本v1beta4 等。Calico CIDRCALICO_IPV4POOL_CIDR必须与kubeadm-init.yaml的podSubnet10.244.0.0/16一致否则 Pod 无法分配 IP。不要用 kubekey / 任何自动化脚本全程手动命令如复用本方案请逐条核对替换表后再执行。12. 附录离线资源清单速查二进制 debpkg/kubeadm、kubelet、kubectl、kubernetes-cni、cri-tools、containerd.io及其依赖由--download-only自动收集。容器镜像images/按原名导入K8s 控制面由kubeadm config images list枚举含kube-apiserver/kube-controller-manager/kube-scheduler/kube-proxy/coredns/etcd/pausetag 与K8S_VER一致Calico由calico.yaml的grep枚举如quay.io/calico/cni、quay.io/calico/node、quay.io/calico/kube-controllers、quay.io/calico/pod2daemon-flexvoltagCALICO_VERdocker.io/library/haproxy:3.0配套文件files/haproxy.cfg、haproxy-static-pod.yaml、kubeadm-init.yaml、README.md本文件。命令速查见各章节关键三步为「基础准备(4) → 部署 haproxy(5) → init/join(6/7/8) → Calico(9) → 验证(10)」。