Kubernetes v1.36.3 高可用集群 —— 部署与原理完整指南

Kubernetes v1.36.3 高可用集群 —— 部署与原理完整指南

前言

本文是一份从零手动搭建 Kubernetes v1.36.3 高可用集群的完整实战指南。不同于简单的“一键安装”教程,本文深入剖析了每个核心组件的工作原理、通信机制和部署细节,旨在帮助读者不仅“知其然”,更“知其所以然”。

目标读者

  • 希望深入理解 Kubernetes 高可用架构的运维工程师和架构师
  • 需要搭建生产级 K8s 集群的 DevOps 团队
  • 准备 Kubernetes CKA/CKAD 认证,想掌握底层原理的学习者
  • 对传统单 Master 集群不满足,希望构建真正高可用环境的实践者

你将获得

  1. 7节点完整拓扑:包含 3 Master + 2 Worker + 2 LB 的经典高可用架构
  2. 组件深度解析:从容器运行时到控制平面,每个组件的作用与交互关系
  3. 通信全链路:证书体系、TLS 握手、watch 机制等关键通信路径
  4. 逐行可执行命令:所有脚本和配置均来自实际生产环境,已逐台验证
  5. 故障切换验证:HAProxy + Keepalived 的 VIP 漂移、apiserver 摘除等真实场景测试

无论你是初次接触 K8s 高可用部署,还是希望优化现有集群架构,本文提供的原理讲解 + 实战步骤 + 排错经验都将为你提供完整的技术栈参考。让我们从理解架构开始,一步步构建这个稳定可靠的 Kubernetes 高可用集群。

适用对象:希望从头手动搭建一套与本环境完全一致的 7 节点 K8s v1.36.3 高可用集群的人。
本文所有命令均来自本环境实际使用的脚本(k8s-ha/scripts/*k8s-ha/configs/*),并已对照运行中的 7 台虚拟机逐台核对。
完成时间:2026-08-09


目录


第 0 章 总览

0.1 集群拓扑

物理网络 192.168.1.0/24           ┌──────────────────────────────┐
网关 192.168.1.1                  │  VIP 192.168.1.100:6443      │ ← 控制平面统一入口
宿主机(装 VMware) 192.168.1.88    │  Keepalived VRRP id 51       │
                                   └───────────────┬──────────────┘
                                  VIP 浮动于 lb / lb2 之间
              ┌──────────────────────┐  单播 VRRP  ┌──────────────────────┐
              │ lb    192.168.1.10   │◄──────────►│ lb2   192.168.1.16   │
              │ MASTER  pri 150     │            │ BACKUP  pri 100      │
              │ HAProxy TCP :6443   │            │ HAProxy TCP :6443    │
              │ 状态页 :8404/stats  │            │ 状态页 :8404/stats   │
              └───────────┬──────────┘            └──────────┬───────────┘
                          └───────────┬──────────────────────┘
                                      │ 两台 HAProxy 后端完全相同
              ┌───────────────────────┼───────────────────────┐
    ┌─────────▼────────┐   ┌──────────▼───────┐   ┌───────────▼──────┐
    │ master1  .11     │   │ master2  .12     │   │ master3  .13     │
    │ apiserver/etcd   │   │ apiserver/etcd   │   │ apiserver/etcd   │
    │ scheduler/cm     │   │ scheduler/cm     │   │ scheduler/cm     │
    └──────────────────┘   └──────────────────┘   └──────────────────┘
                    ┌────────────────────┴────────────────────┐
          ┌─────────▼─────────┐                    ┌──────────▼────────┐
          │ worker1  .14      │                    │ worker2  .15      │
          │ kubelet/containerd│                    │ kubelet/containerd│
          └───────────────────┘                    └───────────────────┘

0.2 节点清单

主机名IP规格角色关键组件
lb192.168.1.102C/2G/20G负载均衡(主)HAProxy 2.4.30 + Keepalived 2.2.4(MASTER, pri 150)
lb2192.168.1.162C/2G/20G负载均衡(备)HAProxy 2.4.30 + Keepalived 2.2.4(BACKUP, pri 100)
master1192.168.1.112C/4G/20G控制平面 1kube-apiserver / etcd / scheduler / controller-manager / kube-proxy / calico-node
master2192.168.1.122C/4G/20G控制平面 2同上
master3192.168.1.132C/4G/20G控制平面 3同上
worker1192.168.1.142C/4G/20G计算节点 1kubelet / kube-proxy / calico-node
worker2192.168.1.152C/4G/20G计算节点 2同上
VIP192.168.1.100控制平面入口Keepalived 虚拟 IP,浮动于 lb / lb2

登录账号:tantan / 791018(已配免密 sudo;宿主机对 7 台均可 SSH 免密)。

0.3 软件版本(已逐台 SSH 核对)

组件版本部署形态
Kubernetesv1.36.3(deb 包 1.36.3-1.1,已 apt-mark holdkubeadm / kubelet / kubectl
etcd3.6.8(镜像 etcd:3.6.8-0控制平面静态 Pod,堆叠式 3 成员
CoreDNSv1.14.2Deployment 2 副本
containerd1.7.18系统服务(CRI 运行时)
runc1.7.18containerd 底层 OCI 运行时
crictlv1.32.0命令行调试工具
Calico(CNI)v3.32.1DaemonSet calico-node + Deployment calico-kube-controllers
kube-proxyIPVS 模式,rr 调度,strictARP=trueDaemonSet
HAProxy2.4.30系统服务(仅 lb / lb2)
Keepalived2.2.4系统服务(仅 lb / lb2)
OS / 内核Ubuntu 22.04.5 LTS / 6.8.0-90cgroup v2

注:lblb2 上残留有旧版 kubeadm/kubelet/kubectl v1.32.7 二进制(历史清理残留),它们不运行任何 K8s 组件,仅作为负载均衡器使用,因此不影响集群。全新搭建时 lb/lb2 不需要安装 kube* 工具。

0.4 网段规划

网段用途
192.168.1.0/24节点网络(虚拟机桥接物理网,与宿主机/网关同二层)
10.244.0.0/16Pod 网络(刻意避开 192.168.x,否则与物理网冲突)
10.96.0.0/12Service(ClusterIP)网络

第 1 章 七台虚拟机各安装了哪些 K8s 相关软件

1.1 lb / lb2(负载均衡器,只装 2 个组件)

软件包/版本作用
HAProxyhaproxy 2.4.30TCP 层四层负载均衡,把 :6443 的请求轮询转发给 3 台 master 的 apiserver;提供 :8404/stats 状态页
Keepalivedkeepalived 2.2.4通过 VRRP 协议在两台 LB 间浮动 VIP 192.168.1.100;并监控 HAProxy 存活做主备切换

二者通过 check_haproxy.sh(检测 haproxy 进程)联动:HAProxy 挂掉 → VIP 漂到另一台。

1.2 master1 / master2 / master3(控制平面,每节点装)

软件版本部署形态作用
kubeadmv1.36.3命令行工具集群初始化与节点加入的"引导器"
kubeletv1.36.3系统服务节点上的"节点代理",管 Pod 生命周期
kubectlv1.36.3命令行工具集群操作客户端
containerd1.7.18系统服务(CRI)容器运行时
runc1.7.18二进制OCI 容器底层运行时
crictlv1.32.0命令行工具容器调试
kube-apiserverv1.36.3静态 Pod集群唯一入口 API 服务
kube-schedulerv1.36.3静态 Pod调度器(3 台选 1 主)
kube-controller-managerv1.36.3静态 Pod控制器管理器(3 台选 1 主)
etcd3.6.8静态 Pod集群键值数据库(3 成员堆叠)
kube-proxyv1.36.3DaemonSet(IPVS)Service 流量转发
calico-nodev3.32.1DaemonSetCNI 网络插件(每节点一个)
calico-kube-controllersv3.32.1DeploymentCalico 控制面(路由/策略同步)
CoreDNSv1.14.2Deployment集群 DNS

1.3 worker1 / worker2(计算节点,每节点装)

软件版本部署形态作用
kubeletv1.36.3系统服务节点代理
kubectl(可选)v1.36.3命令行工具客户端(便于排障,非必需)
containerd1.7.18系统服务容器运行时
runc1.7.18二进制OCI 运行时
crictlv1.32.0命令行工具调试
kube-proxyv1.36.3DaemonSet(IPVS)Service 转发
calico-nodev3.32.1DaemonSetCNI 插件

worker 节点不运行 kube-apiserver / etcd / scheduler / controller-manager / CoreDNS,镜像也更省(只拉 kube-proxypause)。


第 2 章 组件原理:每个组件的作用与工作原理

2.1 容器运行时层:containerd / runc / CRI

  • containerd:行业标准的容器运行时,实现了 Kubernetes 的 CRI(Container Runtime Interface)。它负责拉取镜像、管理容器生命周期、镜像存储、网络命名空间。Kubernetes 不直接操作容器,而是通过 CRI 让 containerd 干活。
  • runc:OCI 规范的底层运行时,真正调用 Linux 内核 clone()/cgroups/namespaces 创建进程。containerd 通过 runc 把镜像变成运行中的容器。
  • pause 容器pause:3.10.2):每个 Pod 的第一个容器,只持有一个网络命名空间(持有 Pod 的 IP),其余同 Pod 容器通过 pause 共享网络栈。sandbox_image 指向阿里云镜像,避免拉 registry.k8s.io 失败。
  • crictl:CRI 兼容的调试工具,等价于 docker 的"运维版",用于 crictl ps / images / logs / exec

关键配置

  • SystemdCgroup = true:让容器 cgroup 交给 systemd 管,与 kubelet 的 cgroupDriver: systemd 一致(否则 kubelet 起不来或告警)。
  • 镜像加速:containerd 的 certs.ddocker.io / registry.k8s.io / quay.io / ghcr.io 分别配了国内镜像(daocloud.io 等),否则境外镜像拉不动。

2.2 kubelet(节点代理)

每个节点(包括 master)都跑一个 kubelet。它是节点上的"大管家":

  • 向 apiserver 注册本节点,持续上报节点状态(CPU/内存/就绪)。
  • 监听 apiserver 分配给本节点的 Pod,调用 CRI 让 containerd 把 Pod 跑起来。
  • 做探针(liveness/readiness/startup),失败就重启容器。
  • 挂载 ConfigMap/Secret/PV。
  • 静态 Pod(Static Pod)机制:kubelet 会监控 /etc/kubernetes/manifests/ 目录,里面的 yaml 直接由 kubelet 以"脱离 apiserver 调度"的方式启动——这正是控制平面的 kube-apiserver / etcd / scheduler / controller-manager 的运行方式。好处:即使 apiserver 还没起来,etcd/apiserver 也能先被 kubelet 拉起,形成"先有鸡还是先有蛋"的自举闭环。

KUBELET_EXTRA_ARGS=--node-ip=<本机IP>:强制 kubelet 上报固定 IP,避免多网卡时选错。

2.3 kube-apiserver(API 网关 + 集群大脑)

  • 集群唯一的对外/对内操作入口。所有组件(kubectl、kubelet、scheduler、controller-manager、kube-proxy)都只跟它通信。
  • 把收到的对象(Pod/Service/Deployment…)写入 etcd 并从中读取。
  • 做认证(证书/Token)、鉴权(RBAC)、准入控制(admission webhook)。
  • 本身无状态、可水平扩展:3 台 master 各跑一个 apiserver,前端用 HAProxy 做负载均衡,VIP 提供统一地址,所以"挂掉一台 apiserver,集群照常工作"。
  • --service-node-port-range 30000-32767、证书 SAN 包含了 VIP/各 master IP/域名,保证从任意地址访问都证书可信。

2.4 etcd(分布式键值数据库)

  • Kubernetes 的"唯一真实数据源(source of truth)",所有集群状态都存这里。
  • 采用 Raft 共识算法 保证 3 成员数据一致:写入需多数派(2/3)确认;某成员宕机不影响读写(只要活着的 ≥2)。
  • 堆叠式(stacked)部署:etcd 作为静态 Pod 与 apiserver 同机,每个 master 一个 etcd 成员,3 成员互为 peer 通过 2380 端口复制数据,2379 端口供本机 apiserver 访问。
    • 优点:部署简单、节点数少。
    • 代价:master 整机宕机 = 同时丢了 apiserver 和一个 etcd 成员,但 3 取 2 仍可用。
  • 数据目录 /var/lib/etcd,务必放可靠磁盘。

2.5 kube-scheduler(调度器)

  • 监听 apiserver 里"尚未绑定节点"的 Pod,按资源请求、亲和性、污点容忍等规则选出最合适的节点,把结果写回 apiserver(Pod.Spec.NodeName)。
  • 3 台各跑一个,但通过 leader election(租约) 同一时刻只有 1 个真正干活,其余 standby,避免重复调度。

2.6 kube-controller-manager(控制器管理器)

  • 一堆控制器的集合:Node 控制器(管节点上下线)、Replication 控制器(维持副本数)、Endpoints 控制器、ServiceAccount 控制器等。
  • 核心逻辑:"实际状态"与"期望状态"不一致就调谐(reconcile)。例如 Deployment 期望 3 副本,少一个它就创建。
  • 同样 leader election,3 台选 1 主。

2.7 kube-proxy(Service 转发,IPVS 模式)

  • 每个节点一个,监听 apiserver 的 Service/Endpoints 变化,把 ClusterIP/NodePort 的流量转发到后端 Pod。
  • 本集群用 IPVS 模式(不是 iptables):
    • 在节点上创建 kube-ipvs0 虚拟网卡,把每个 Service 的 ClusterIP 配上去。
    • 用内核 ip_vs 模块做负载均衡(调度算法 rr 轮询),性能远高于 iptables 链式规则,且规则数量与 Service 数无关。
    • strictARP=true:让节点对 VIP 网段响应 ARP 时不被 Calico 抢答,避免 ARP 冲突(CNI 与 kube-proxy 共存必须的配合)。

2.8 CoreDNS(集群 DNS)

  • 把 Service 名(如 mysvc.default.svc.cluster.local)解析成 ClusterIP。
  • 也是 kube-proxy 的"客户":Pod 里 /etc/resolv.conf 的 nameserver 指向 10.96.0.10(CoreDNS ServiceIP),CoreDNS 再通过 apiserver 查询 Service/Endpoints。
  • 跨节点 Pod 互访、Ingress、Service Mesh 都依赖它。

2.9 Calico(CNI 网络插件)

  • 负责 Pod 网络:给每个 Pod 分配 10.244.0.0/16 里的 IP,并保证跨节点 Pod 能互通。
  • 本集群用 IPIP CrossSubnet 模式:
    • calico-node DaemonSet 在每台节点创建一个 tunl0 隧道口。
    • 同二层网段(都在 192.168.1.0/24)的 Pod 间直接路由、不封装;跨子网才走 IPIP 封装。鉴于所有节点同二层,实际几乎零封装开销。
    • Calico 用 BGP/路由表把"哪个 Pod 子网在哪一节点"广播出去,节点间靠 Linux 路由表转发。
  • calico-kube-controllers 负责把 K8s 的 NetworkPolicy、Pod/Service 变化同步进 Calico 的数据面。
  • IP_AUTODETECTION_METHOD=can-reach=192.168.1.1:自动探测用哪块网卡做 Calico 后端,避免选错(如选到 docker0)。

2.10 HAProxy(控制平面负载均衡)

  • 在 lb/lb2 上监听 :6443,backend 是 3 台 master 的 :6443balance roundrobin 轮询。
  • mode tcp:四层转发,不解析 HTTP,对 apiserver 的 TLS 透明。
  • 长连接超时设到 4 小时timeout client/server/tunnel 4h):因为 kubelet/controller 与 apiserver 之间是 watch 长连接,超时太短会导致每分钟断流、集群抖动。
  • check 每 3 秒探一次后端 apiserver 的 :6443,失败则把该 master 摘出(这正是"摘除 master1 apiserver 后 15 秒内 HAProxy 标记 DOWN"的来源)。

2.11 Keepalived(VIP 高可用)

  • 在两台 LB 之间用 VRRP 协议协商谁持有 VIP 192.168.1.100
  • 单播模式(unicast):直接指定对端 IP,不依赖交换机组播(云/受限交换机环境下更稳)。
  • state MASTER/BACKUP + priority 决定初始角色:lb=150、lb2=100。
  • track_script chk_haproxy:每 2 秒查一次 HAProxy 进程,连挂 3 次(约 6s)就把本机优先级减 weight(-60)
    • lb 正常 150;HAProxy 挂 → 150-60=90 < lb2 的 100 → VIP 漂到 lb2。
    • lb 恢复 → 回到 150 > 100 → 抢回 VIP(非抢占配置 nopreempt 下按优先级自然回归)。
  • auth_pass k8sha99:VRRP 报文认证,防误抢。

2.12 控制平面 HA 设计小结(stacked etcd)

            VIP:6443 (Keepalived)
                 │
        ┌────────┴────────┐
     HAProxy(lb)      HAProxy(lb2)      ← 互备,VIP 浮动
        │  │  │               │  │  │
        └──┬─┴─┬──────────────┘  │
           ▼    ▼               ▼
       master1 master2 master3
       (apiserver+etcd) (apiserver+etcd) (apiserver+etcd)
            │        │          │
            └── etcd Raft 复制(2380)──┘
  • apiserver 3 副本 × etcd 3 成员,任意单点故障(1 台 master 宕机、1 台 LB 宕机)集群仍可用。
  • 客户端/节点只认 VIP,HAProxy 把请求摊到健康的 apiserver。

第 3 章 通信细节:组件之间的相互关系与通信

3.1 总体通信骨架

kubectl / 节点 kubelet
        │  (HTTPS, 经 VIP:6443)
        ▼
   [Keepalived VIP] ──► HAProxy(lb/lb2):6443 ──roundrobin──► masterN:6443 (kube-apiserver)
                                                                   │
                                                        ┌──────────┴──────────┐
                                                  etcd:2379 (本机)      watch/心跳
                                                        │                     │
                                              etcd peer 复制:2380      scheduler / controller-manager
                                                                              │
                                                                        写回 apiserver

3.2 证书与 TLS 体系(谁信任谁)

Kubernetes 全栈走 mTLS(双向 TLS)。kubeadm 生成一套 PKI,核心有:

  • CA(根证书)/etc/kubernetes/pki/ca.crt + ca.key。它给所有其他证书签名。apiserver、kubelet、etcd、scheduler、controller-manager 都信任这个 CA。
  • apiserver 证书:SAN 包含 192.168.1.100(VIP)、192.168.1.10~13、127.0.0.1、10.96.0.1、k8s-vip、各主机名、kubernetes.default.svc...。这就是为什么从 VIP 或任意 master IP 访问 apiserver 都不会证书报错。
  • etcd 证书serverCertSANs/peerCertSANs 包含 3 个 master IP 与主机名,使 etcd 成员间互信、apiserver 连 etcd 也互信。
  • kubelet 客户端证书:kubelet 用 kubelet-client 证书连 apiserver;apiserver 用 kubelet-api 证书反向连 kubelet(如 kubectl exec/logs)。
  • --certificate-keya1b2c3...):kubeadm 用它加密 etcd/CA 等证书,在加入新 control-plane 时通过 --upload-certs 临时放到集群 Secret 里,让 master2/3 能拿到同一套 PKI(否则每个 master 各签各的,etcd 无法互信)。

3.3 关键通信路径逐条说明

(1) 客户端 → apiserver(经 VIP)

  • kubectl 读 kubeconfig,server: https://192.168.1.100:6443
  • 请求先到 Keepalived 当前持有 VIP 的那台 LB,HAProxy 在 :6443 接收,按 roundrobin 选一个健康 master 转发(TCP 透传,不改 TLS)。
  • apiserver 校验客户端证书/Token → RBAC → 操作 etcd。

(2) apiserver ↔ etcd(本机 localhost)

  • 每台 master 的 apiserver 只连本机 etcd:--etcd-servers=https://127.0.0.1:2379,走 mTLS。
  • 写请求由本机 etcd 通过 Raft 复制到另两个成员的 :2380,需多数派确认。

(3) etcd 成员之间(2380 peer)

  • 3 个 etcd 通过 :2380(peer port)建立 Raft 复制通道,用 etcd-peer 证书互信。
  • leader 处理写,follower 同步;leader 心跳/选举超时触发重新选主。

(4) kubelet ↔ apiserver(watch + 心跳)

  • kubelet 启动后用客户端证书连 apiserver(或 VIP),建立长连接 watch 本节点应跑的 Pod。
  • 每 10 秒发一次节点状态心跳;apiserver 超过 node-monitor-grace-period 没收到就标记节点 NotReady。
  • 这正是 HAProxy 长连接超时设 4 小时的原因——watch 连接不能随便断。

(5) scheduler / controller-manager ↔ apiserver(watch + leader election)

  • 二者都 watch apiserver(未调度 Pod / 实际状态变化)。
  • 通过 apiserver 里的 Lease 对象kube-node-lease 命名空间 + control-plane endpoint)做 leader 选举:谁抢到 lease 谁就是 active,其余阻塞;active 挂了 lease 过期,其他人接手。所以 3 台 master 上这俩组件"看起来都在跑,但只有一个真干活"。

(6) kube-proxy ↔ apiserver(watch Service/Endpoints)

  • 每个节点的 kube-proxy watch apiserver 的 Service 和 Endpoints 变化,更新本机 ip_vs 规则:把 ClusterIP 绑到 kube-ipvs0,并写后端 Pod IP。

(7) Calico ↔ apiserver(watch + BGP)

  • calico-node watch Pod/Node/NetworkPolicy;通过 Felix 写本机路由表;节点间用 BGP(或 IPIP 隧道)把 Pod 子网路由广播出去,使跨节点 Pod 互通。
  • calico-kube-controllers watch K8s 对象,把期望网络状态同步到 Calico datastore(这里直接存 etcd/K8s CRD)。

(8) Pod 间通信(跨节点)

  • Pod A(节点 X)发往 Pod B(节点 Y,IP 10.244.x.y):
    1. 出 Pod 走 pause 的网络命名空间,路由判定目的在 10.244.0.0/16 且不在本机。
    2. 查节点路由表 → 下一跳是节点 Y(经 tunl0 或直接二层),Calico 已写好这条路由。
    3. 同二层(本环境)直接路由到节点 Y 的 ens33;Calico 在 Y 上再把包递给 Pod B 的 cali* 网卡。
    4. 全程源/目的都是 Pod IP,Service 的 ClusterIP 只在进出节点时被 kube-proxy 做一次 DNAT。

(9) Service 访问流(ClusterIP / NodePort)

  • 集群内 Pod 访问 myapp:8080 → 解析到 ClusterIP 10.96.x.y(CoreDNS)→ 命中 kube-ipvs0 → IPVS 按 rr 选一个后端 Pod IP 做 DNAT → Pod。
  • 外部访问 NodePort 30xxx → 任一层节点 IP:30xxx → kube-proxy 的 ip_vs 规则 → 后端 Pod。

(10) DNS 解析流

  • Pod 内 getent hosts mysvc → 查 10.96.0.10:53(CoreDNS)→ CoreDNS 查 K8s Service API → 返回 ClusterIP。
  • 外部域名 → CoreDNS 走转发(默认 forward 到节点 /etc/resolv.conf 的上游,即 223.5.5.5 等)。

(11) 控制平面主备切换(HAProxy 摘点)

  • 某 master apiserver 进程死 → HAProxy 的 tcp-check 连不上 :6443fall 3 次后标记 DOWN → 新请求不再分发给它 → 集群经 VIP 仍可用,scheduler/cm 通过另两台继续工作,etcd 仍 2/3 多数派可用。
  • apiserver 恢复 → HAProxy rise 2 重新标 UP。

(12) LB 主备切换(VIP 漂移)

  • lb 的 HAProxy 挂 → chk_haproxy.sh 连失败 → Keepalived 优先级降到 90 → 低于 lb2(100) → VRRP 抢位 → VIP 漂到 lb2 → kubectl(指向 VIP)无感切换。
  • lb 恢复 → 优先级回 150 → 重新持有 VIP(非抢占环境下按优先级自然回归)。

3.4 一句话关系图(依赖链)

Keepalived 管 VIP
   └─ HAProxy 用 VIP 做 apiserver 负载均衡
        └─ apiserver 是唯一入口,背后是 etcd(Raft)
             ├─ kubelet 听 apiserver 跑 Pod(经 containerd/runc)
             ├─ scheduler/cm 经 apiserver 做调度与调谐(leader 选举)
             ├─ kube-proxy 经 apiserver 维护 Service 转发(IPVS)
             └─ Calico 经 apiserver 维护 Pod 网络(路由/BGP)
CoreDNS 是 apiserver 的"常客",给 Service 提供名字

第 4 章 从零手动部署(详细步骤)

前置:一台装好 VMware Workstation 的 Windows 宿主机(IP 192.168.1.88),能联网(可选代理 http://192.168.1.88:7890)。下面所有 ssh 命令从宿主机执行。
本指南以桥接模式 + 阿里云/道客镜像为主路径,不依赖国外源,照做即可联网拉取。

步骤 0:准备 7 台 Ubuntu 22.04 虚拟机

节点主机名IPvCPU内存磁盘
1lb192.168.1.1022G20G
2lb2192.168.1.1622G20G
3master1192.168.1.1124G20G
4master2192.168.1.1224G20G
5master3192.168.1.1324G20G
6worker1192.168.1.1424G20G
7worker2192.168.1.1524G20G
  • 安装 Ubuntu 22.04.5,磁盘用 ext4,安装时先不连网或装完再配静态 IP。
  • 创建用户 tantan(密码 791018),加 sudo;或装完用下面的 sshsetup 脚本统一处理。
  • 7 台都用 桥接(Bridged) 网卡接入 192.168.1.0/24,网关 192.168.1.1

强烈建议:先装好 1 台"模板机",用 VMware 冷克隆 出其余 6 台,再各自改 IP/主机名(参考 README 第九节的克隆注意事项:必须重置 machine-id/etc/ssh/ssh_host_*、解除 NetworkManager 的 MAC 绑定)。lb2 是 lb 的克隆机并改造成 BACKUP,详见本文 4.2。


步骤 1:所有 K8s 节点(master×3 + worker×2)通用前置

master1/2/3、worker1/2 共 5 台 上执行(lb/lb2 跳过此步,它们只需 HAProxy/Keepalived)。

1.1 换阿里云源 + 关自动更新 + 装 openssh
# 以 tantan 登录后切 root
sudo -i
cat > /etc/apt/sources.list <<'EOF'
deb http://mirrors.aliyun.com/ubuntu/ jammy main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ jammy-updates main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ jammy-backports main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ jammy-security main restricted universe multiverse
EOF

# 永久禁用 unattended-upgrades,避免抢 dpkg 锁
systemctl mask unattended-upgrades.service apt-daily.timer apt-daily-upgrade.timer 2>/dev/null
cat > /etc/apt/apt.conf.d/20auto-upgrades <<'EOF'
APT::Periodic::Update-Package-Lists "0";
APT::Periodic::Unattended-Upgrade "0";
APT::Periodic::Download-Upgradeable-Packages "0";
APT::Periodic::AutocleanInterval "0";
EOF

apt-get update
apt-get install -y openssh-server
systemctl enable --now ssh
1.2 内核模块 + sysctl(K8s 与 IPVS 必需)
cat > /etc/modules-load.d/k8s.conf <<'EOF'
overlay
br_netfilter
EOF
modprobe overlay; modprobe br_netfilter

cat > /etc/modules-load.d/ipvs.conf <<'EOF'
ip_vs
ip_vs_rr
ip_vs_wrr
ip_vs_sh
nf_conntrack
EOF
for m in ip_vs ip_vs_rr ip_vs_wrr ip_vs_sh nf_conntrack; do modprobe $m; done

cat > /etc/sysctl.d/99-kubernetes.conf <<'EOF'
net.bridge.bridge-nf-call-iptables  = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward                 = 1
net.ipv4.conf.all.rp_filter         = 0
net.ipv4.conf.default.rp_filter     = 0
vm.swappiness                       = 0
fs.inotify.max_user_instances       = 8192
fs.inotify.max_user_watches         = 524288
EOF
sysctl --system

# 关 swap(K8s 要求;内存充足可不依赖 swap)
swapoff -a
sed -i -E '/\sswap\s/s/^([^#])/#\1/' /etc/fstab
1.3 部署 containerd(CRI 运行时)
apt-get install -y containerd
mkdir -p /etc/containerd
containerd config default > /etc/containerd/config.toml

# 关键两处:SystemdCgroup + 国内 pause 镜像
sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
sed -i 's#sandbox_image = "registry.k8s.io/pause:3.8"#sandbox_image = "registry.aliyuncs.com/google_containers/pause:3.10.2"#' /etc/containerd/config.toml

# 配置镜像加速(docker.io / k8s.io / quay.io / ghcr.io)
mkdir -p /etc/containerd/certs.d/docker.io /etc/containerd/certs.d/registry.k8s.io \
         /etc/containerd/certs.d/quay.io /etc/containerd/certs.d/ghcr.io
cat > /etc/containerd/certs.d/docker.io/hosts.toml <<'EOF'
server = "https://docker.io"
[host."https://docker.m.daocloud.io"]
  capabilities = ["pull", "resolve"]
[host."https://registry-1.docker.io"]
  capabilities = ["pull", "resolve"]
EOF
cat > /etc/containerd/certs.d/registry.k8s.io/hosts.toml <<'EOF'
server = "https://registry.k8s.io"
[host."https://k8s.m.daocloud.io"]
  capabilities = ["pull", "resolve"]
[host."https://registry.k8s.io"]
  capabilities = ["pull", "resolve"]
EOF
cat > /etc/containerd/certs.d/quay.io/hosts.toml <<'EOF'
server = "https://quay.io"
[host."https://quay.m.daocloud.io"]
  capabilities = ["pull", "resolve"]
[host."https://quay.io"]
  capabilities = ["pull", "resolve"]
EOF
cat > /etc/containerd/certs.d/ghcr.io/hosts.toml <<'EOF'
server = "https://ghcr.io"
[host."https://ghcr.m.daocloud.io"]
  capabilities = ["pull", "resolve"]
[host."https://ghcr.io"]
  capabilities = ["pull", "resolve"]
EOF

# 若宿主机有代理(可选,本环境为 192.168.1.88:7890)
# cat > /etc/systemd/system/containerd.service.d/http-proxy.conf <<EOF
# [Service]
# Environment="HTTP_PROXY=http://192.168.1.88:7890"
# Environment="HTTPS_PROXY=http://192.168.1.88:7890"
# Environment="NO_PROXY=localhost,127.0.0.1,10.0.0.0/8,192.168.0.0/16,.svc,.cluster.local"
# EOF

systemctl daemon-reload
systemctl restart containerd
systemctl enable containerd

cat > /etc/crictl.yaml <<'EOF'
runtime-endpoint: unix:///run/containerd/containerd.sock
image-endpoint: unix:///run/containerd/containerd.sock
timeout: 30
debug: false
EOF

验证:ctr -v / crictl info 应正常,SystemdCgroup 在 config.toml 中为 true

1.4 安装 kubeadm / kubelet / kubectl v1.36.3
apt-get install -y apt-transport-https ca-certificates curl gpg conntrack socat ebtables ipset ipvsadm
mkdir -p /etc/apt/keyrings
curl -fsSL https://mirrors.aliyun.com/kubernetes-new/core/stable/v1.36/deb/Release.key \
  | gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
chmod 644 /etc/apt/keyrings/kubernetes-apt-keyring.gpg

cat > /etc/apt/sources.list.d/kubernetes.list <<'EOF'
deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://mirrors.aliyun.com/kubernetes-new/core/stable/v1.36/deb/ /
EOF

apt-get update
apt-get install -y kubelet=1.36.3-1.1 kubeadm=1.36.3-1.1 kubectl=1.36.3-1.1
apt-mark hold kubelet kubeadm kubectl
systemctl enable kubelet

注意 kubeadm v1.36 的配置 API 是 kubeadm.k8s.io/v1beta4。下面 init 配置必须匹配。

1.5 静态 IP + 主机名 + hosts(每台按自己 IP 改)

master1(192.168.1.11) 为例(其余节点把 IP/主机名换掉即可):

hostnamectl set-hostname master1
cat > /etc/hosts <<'EOF'
127.0.0.1       localhost
127.0.1.1       master1
::1             localhost ip6-localhost ip6-loopback

192.168.1.10    lb
192.168.1.16    lb2
192.168.1.11    master1
192.168.1.12    master2
192.168.1.13    master3
192.168.1.14    worker1
192.168.1.15    worker2
192.168.1.100   k8s-vip kube-apiserver.k8s.local
EOF

nmcli con add type ethernet ifname ens33 con-name k8s-net \
  ipv4.method manual ipv4.addresses 192.168.1.11/24 \
  ipv4.gateway 192.168.1.1 ipv4.dns "223.5.5.5,119.29.29.29" \
  ipv4.route-metric 100 ipv6.method disabled connection.autoconnect yes
nmcli con up k8s-net

提示:本环境用 netcfg.sh 一键完成 1.5(变量 NODE_NAME/NODE_IP),可批量推到各节点。


步骤 2:部署负载均衡器 lb 与 lb2(HAProxy + Keepalived)

lb(192.168.1.10) 上:

apt-get install -y haproxy keepalived psmisc

# 允许绑定非本地 VIP
cat > /etc/sysctl.d/98-haproxy.conf <<'EOF'
net.ipv4.ip_nonlocal_bind = 1
net.ipv4.ip_forward       = 1
EOF
sysctl --system

# HAProxy 配置
cat > /etc/haproxy/haproxy.cfg <<'EOF'
global
    log /dev/log local0
    log /dev/log local1 notice
    chroot /var/lib/haproxy
    stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners
    stats timeout 30s
    user haproxy
    group haproxy
    daemon
    maxconn 20000
defaults
    log     global
    mode    tcp
    option  dontlognull
    option  redispatch
    option  tcpka
    retries 3
    timeout connect 10s
    timeout client  4h
    timeout server  4h
    timeout tunnel  4h
    timeout check   5s
    maxconn 18000
frontend k8s-apiserver
    bind *:6443
    mode tcp
    option tcplog
    default_backend k8s-apiserver-backend
backend k8s-apiserver-backend
    mode tcp
    option  tcp-check
    balance roundrobin
    default-server inter 3s downinter 5s rise 2 fall 3 slowstart 60s maxconn 3000 maxqueue 256 weight 100
    server master1 192.168.1.11:6443 check
    server master2 192.168.1.12:6443 check
    server master3 192.168.1.13:6443 check
frontend stats
    bind *:8404
    mode http
    stats enable
    stats uri /stats
    stats refresh 10s
    stats admin if TRUE
EOF
haproxy -c -f /etc/haproxy/haproxy.cfg
systemctl enable --now haproxy

# Keepalived(MASTER)
mkdir -p /etc/keepalived
cat > /etc/keepalived/check_haproxy.sh <<'EOF'
#!/bin/bash
if ! /usr/bin/killall -0 haproxy >/dev/null 2>&1; then exit 1; fi
exit 0
EOF
chmod +x /etc/keepalived/check_haproxy.sh

cat > /etc/keepalived/keepalived.conf <<'EOF'
! Configuration File for keepalived  --  lb (MASTER)
global_defs {
    router_id LVS_K8S_LB
    script_user root
    enable_script_security
    vrrp_skip_check_adv_addr
}
vrrp_script chk_haproxy {
    script "/etc/keepalived/check_haproxy.sh"
    interval 2
    weight -60
    fall 3
    rise 2
    timeout 2
}
vrrp_instance VI_K8S {
    state MASTER
    interface ens33
    virtual_router_id 51
    priority 150
    advert_int 1
    unicast_src_ip 192.168.1.10
    unicast_peer { 192.168.1.16 }
    authentication { auth_type PASS; auth_pass k8sha99 }
    virtual_ipaddress { 192.168.1.100/24 dev ens33 label ens33:vip }
    track_script { chk_haproxy }
}
EOF
systemctl enable --now keepalived

lb2(192.168.1.16) 与 lb 几乎一致,仅 3 处不同(即本环境的 lb2-setup.sh 逻辑):

apt-get install -y haproxy keepalived psmisc
# (HAProxy 配置、check_haproxy.sh、sysctl 同 lb,一字不差)

cat > /etc/keepalived/keepalived.conf <<'EOF'
! Configuration File for keepalived  --  lb2 (BACKUP)
global_defs {
    router_id LVS_K8S_LB2
    script_user root
    enable_script_security
    vrrp_skip_check_adv_addr
}
vrrp_script chk_haproxy {
    script "/etc/keepalived/check_haproxy.sh"
    interval 2
    weight -60
    fall 3
    rise 2
    timeout 2
}
vrrp_instance VI_K8S {
    state BACKUP
    interface ens33
    virtual_router_id 51
    priority 100
    advert_int 1
    unicast_src_ip 192.168.1.16
    unicast_peer { 192.168.1.10 }
    authentication { auth_type PASS; auth_pass k8sha99 }
    virtual_ipaddress { 192.168.1.100/24 dev ens33 label ens33:vip }
    track_script { chk_haproxy }
}
EOF
systemctl enable --now keepalived haproxy

切换原理:lb 正常优先级 150;HAProxy 挂 → weight -60 → 90 < lb2 的 100 → VIP 漂到 lb2。weight 必须让降权后低于备机,否则永不切换(早期 -40 即因 150-40=110>100 而失效)。

验证:ip -4 addr show ens33 | grep 192.168.1.100 在 lb 上能看到 VIP;nc -z 192.168.1.100 6443 && echo VIP_OK


步骤 3:初始化 master1(kubeadm init)

master1 上创建初始化配置 /etc/kubernetes/kubeadm-init.yaml

---
apiVersion: kubeadm.k8s.io/v1beta4
kind: InitConfiguration
bootstrapTokens:
  - token: "abcdef.0123456789abcdef"
    description: "kubeadm bootstrap token"
    ttl: "24h0m0s"
    usages: [signing, authentication]
    groups:
      - system:bootstrappers:kubeadm:default-node-token
localAPIEndpoint:
  advertiseAddress: 192.168.1.11
  bindPort: 6443
certificateKey: "a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f90"
nodeRegistration:
  name: master1
  criSocket: unix:///run/containerd/containerd.sock
  imagePullPolicy: IfNotPresent
  taints:
    - key: node-role.kubernetes.io/control-plane
      effect: NoSchedule
  kubeletExtraArgs:
    - name: node-ip
      value: "192.168.1.11"
timeouts:
  controlPlaneComponentHealthCheck: 6m0s
  kubeletHealthCheck: 5m0s
  discovery: 5m0s
  etcdAPICall: 2m0s
  kubernetesAPICall: 2m0s
  tlsBootstrap: 5m0s
  upgradeManifests: 5m0s
---
apiVersion: kubeadm.k8s.io/v1beta4
kind: ClusterConfiguration
kubernetesVersion: v1.36.3
clusterName: k8s-ha
controlPlaneEndpoint: "192.168.1.100:6443"
imageRepository: registry.aliyuncs.com/google_containers
certificatesDir: /etc/kubernetes/pki
networking:
  dnsDomain: cluster.local
  serviceSubnet: 10.96.0.0/12
  podSubnet: 10.244.0.0/16
apiServer:
  certSANs:
    - "192.168.1.100"
    - "192.168.1.10"
    - "192.168.1.11"
    - "192.168.1.12"
    - "192.168.1.13"
    - "127.0.0.1"
    - "10.96.0.1"
    - "k8s-vip"
    - "lb"
    - "master1"
    - "master2"
    - "master3"
    - "localhost"
    - "kubernetes"
    - "kubernetes.default"
    - "kubernetes.default.svc"
    - "kubernetes.default.svc.cluster.local"
  extraArgs:
    - name: authorization-mode
      value: "Node,RBAC"
    - name: service-node-port-range
      value: "30000-32767"
controllerManager:
  extraArgs:
    - name: bind-address
      value: "0.0.0.0"
    - name: node-cidr-mask-size
      value: "24"
scheduler:
  extraArgs:
    - name: bind-address
      value: "0.0.0.0"
etcd:
  local:
    dataDir: /var/lib/etcd
    serverCertSANs: ["192.168.1.11","192.168.1.12","192.168.1.13","master1","master2","master3"]
    peerCertSANs:   ["192.168.1.11","192.168.1.12","192.168.1.13","master1","master2","master3"]
dns: {}
---
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cgroupDriver: systemd
failSwapOn: false
maxPods: 110
clusterDomain: cluster.local
clusterDNS: ["10.96.0.10"]
rotateCertificates: true
serverTLSBootstrap: false
evictionHard:
  imagefs.available: "5%"
  memory.available: "100Mi"
  nodefs.available: "5%"
  nodefs.inodesFree: "3%"
systemReserved:
  cpu: "200m"
  memory: "256Mi"
---
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
mode: ipvs
ipvs:
  scheduler: rr
  strictARP: true
clusterCIDR: "10.244.0.0/16"

确保 apiserver/etcd 不走代理(避免连本机被代理截胡):

export no_proxy="localhost,127.0.0.1,::1,10.0.0.0/8,10.96.0.0/12,10.244.0.0/16,192.168.0.0/16,.svc,.cluster.local"
export NO_PROXY="$no_proxy"
unset http_proxy https_proxy HTTP_PROXY HTTPS_PROXY

kubeadm init --config /etc/kubernetes/kubeadm-init.yaml --upload-certs --v=5

成功后:

mkdir -p /root/.kube /home/tantan/.kube
cp -f /etc/kubernetes/admin.conf /root/.kube/config
cp -f /etc/kubernetes/admin.conf /home/tantan/.kube/config
chown -R tantan:tantan /home/tantan/.kube
export KUBECONFIG=/etc/kubernetes/admin.conf
kubectl get nodes   # 此时应只有 master1,状态 NotReady(还没装 CNI)

生成后续 join 命令(保存到文件,给 master2/3 和 worker 用):

CERT_KEY=$(kubeadm init phase upload-certs --upload-certs | tail -1)
JOIN=$(kubeadm token create --print-join-command)
echo "$JOIN --control-plane --certificate-key $CERT_KEY" > /tmp/join-cp.txt
echo "$JOIN" > /tmp/join-worker.txt
cat /tmp/join-cp.txt      # 形如:kubeadm join 192.168.1.100:6443 --token abcdef.xxx --discovery-token-ca-cert-hash sha256:yyy --control-plane --certificate-key zzz
cat /tmp/join-worker.txt

关键:join 命令里的地址是 VIP:6443,所以新节点会经由 HAProxy 找到任意一台健康 master,HA 从加入那一刻就生效。


步骤 4:加入 master2、master3(control-plane)

master2(192.168.1.12)master3(192.168.1.13) 上,先设好 no_proxy,再执行步骤 3 生成的 control-plane join 命令:

export no_proxy="localhost,127.0.0.1,::1,10.0.0.0/8,10.96.0.0/12,10.244.0.0/16,192.168.0.0/16,.svc,.cluster.local"
export NO_PROXY="$no_proxy"
unset http_proxy https_proxy HTTP_PROXY HTTPS_PROXY

kubeadm join 192.168.1.100:6443 --token abcdef.0123456789abcdef \
  --discovery-token-ca-cert-hash sha256:<master1的hash> \
  --control-plane --certificate-key <CERT_KEY> \
  --cri-socket unix:///run/containerd/containerd.sock --node-name master2 --v=5

加入后固定 node-ip(master2 用 192.168.1.12,master3 用 192.168.1.13):

cat > /etc/default/kubelet <<EOF
KUBELET_EXTRA_ARGS=--node-ip=192.168.1.12
EOF
systemctl daemon-reload; systemctl restart kubelet

# 复制 kubeconfig 便于本地 kubectl
mkdir -p /root/.kube /home/tantan/.kube
cp -f /etc/kubernetes/admin.conf /root/.kube/config
cp -f /etc/kubernetes/admin.conf /home/tantan/.kube/config
chown -R tantan:tantan /home/tantan/.kube

master2/3 加入时会从 master1 通过 --certificate-key 拉取同一套 PKI(CA/etcd 证书),因此 3 台 etcd 能互信组成 Raft。


步骤 5:加入 worker1、worker2

worker1(192.168.1.14)worker2(192.168.1.15) 上执行 worker join 命令(不带 --control-plane):

export no_proxy="localhost,127.0.0.1,::1,10.0.0.0/8,10.96.0.0/12,10.244.0.0/16,192.168.0.0/16,.svc,.cluster.local"
export NO_PROXY="$no_proxy"
unset http_proxy https_proxy HTTP_PROXY HTTPS_PROXY

kubeadm join 192.168.1.100:6443 --token abcdef.0123456789abcdef \
  --discovery-token-ca-cert-hash sha256:<master1的hash> \
  --cri-socket unix:///run/containerd/containerd.sock --node-name worker1 --v=5

cat > /etc/default/kubelet <<EOF
KUBELET_EXTRA_ARGS=--node-ip=192.168.1.14
EOF
systemctl daemon-reload; systemctl restart kubelet

此时 kubectl get nodes 应看到 5 个节点,但都是 NotReady——因为还没装 CNI 网络插件


步骤 6:部署 Calico CNI

master1(有 kubeconfig)上:

export KUBECONFIG=/etc/kubernetes/admin.conf
CALICO_VER=v3.32.1
cd /root

# 下载 manifest(多处兜底源)
for url in \
  "https://raw.githubusercontent.com/projectcalico/calico/${CALICO_VER}/manifests/calico.yaml" \
  "https://cdn.jsdelivr.net/gh/projectcalico/calico@${CALICO_VER}/manifests/calico.yaml" \
  "https://ghproxy.net/https://raw.githubusercontent.com/projectcalico/calico/${CALICO_VER}/manifests/calico.yaml" ; do
  curl -fsSL --max-time 90 -o /root/calico.yaml "$url" && break
done

# 适配本集群
sed -i -e 's|# - name: CALICO_IPV4POOL_CIDR|- name: CALICO_IPV4POOL_CIDR|' \
       -e 's|#   value: "192.168.0.0/16"|  value: "10.244.0.0/16"|' /root/calico.yaml
# IPIP 改为 CrossSubnet(同二层不封装,跨子网才封装)
sed -i '/name: CALICO_IPV4POOL_IPIP/{n;s|value: "Always"|value: "CrossSubnet"|}' /root/calico.yaml

kubectl apply -f /root/calico.yaml

# 网卡自动探测:选能到达网关的那块网卡,避免选错
kubectl -n kube-system set env daemonset/calico-node IP_AUTODETECTION_METHOD=can-reach=192.168.1.1
kubectl -n kube-system set env daemonset/calico-node FELIX_IGNORELOOSERPF=true

kubectl -n kube-system rollout status daemonset/calico-node --timeout=600s
kubectl -n kube-system rollout status deployment/calico-kube-controllers --timeout=300s
kubectl wait --for=condition=Ready nodes --all --timeout=300s

装完 Calico 后,节点陆续变 Ready,Pod 网络打通。


步骤 7:验证集群

export KUBECONFIG=/etc/kubernetes/admin.conf
kubectl get nodes -o wide
# 期望:5 节点 Ready,版本 v1.36.3,ROLES 含 control-plane / <none>

kubectl get pods -n kube-system -o wide
# 期望:etcd-master1/2/3、kube-apiserver-master1/2/3、kube-scheduler-*、
#       kube-controller-manager-*、kube-proxy-*、calico-node-*、coredns-* 全部 Running

kubectl get csr   # 应无 Pending(证书已自动签发)

# 跑一个测试 Deployment 验证调度 + 网络
kubectl create deployment nginx --image=registry.aliyuncs.com/google_containers/nginx:1.27 --replicas=4
kubectl rollout status deploy/nginx
kubectl get pods -o wide               # 跨节点分布
kubectl expose deploy nginx --port=80 --type=ClusterIP
kubectl run curltest --rm -it --image=registry.aliyuncs.com/google_containers/curl -- sh -c 'curl -s nginx | head'

HA 故障切换验证(可选但推荐):

# 1) 摘除 master1 的 apiserver,观察 HAProxy 与集群
ssh tantan@192.168.1.10 "sudo systemctl stop haproxy"     # 约 10s 后 VIP 漂到 lb2
kubectl get nodes                                       # 经 VIP 仍正常
ssh tantan@192.168.1.10 "sudo systemctl start haproxy"  # 约 12s 后 VIP 抢回

# 2) 停 master1 apiserver 进程
ssh tantan@192.168.1.11 "sudo mv /etc/kubernetes/manifests/kube-apiserver.yaml /tmp/ && sleep 20"
kubectl get nodes                                       # 经 VIP 仍正常,15s 内 HAProxy 标记 master1 DOWN
ssh tantan@192.168.1.11 "sudo mv /tmp/kube-apiserver.yaml /etc/kubernetes/manifests/ && sleep 20"
kubectl get nodes                                       # master1 apiserver 自动恢复

步骤 8(可选):宿主机自动化 —— 开机自启 / 关机挂起 / 桌面控制台

这部分不属于 K8s 本身,但本环境已配套(详见 README 第十、十一节):

  • 两个计划任务 K8sVM-Autostart / K8sVM-Autosuspend(S4U + Administrator 身份)实现宿主机开机拉起 7 台、关机挂起 7 台。
  • 桌面图标 K8s 虚拟机控制台.lnk 一键 start/suspend。
  • 脚本:k8s-ha/scripts/vm-power.ps1vm-panel.ps1make-shortcut.ps1

附录 A:加入新节点 / 证书续期 / 常见坑

A.1 加入新的 worker

kubeadm token create --print-join-command          # master1 上生成,24h 有效
# 新 worker 上执行该命令(加 --node-name 与 kubelet --node-ip)

A.2 加入新的 control-plane

kubeadm init phase upload-certs --upload-certs     # 拿 certificate-key
kubeadm token create --print-join-command          # 拿 join 命令
# 拼接: <join> --control-plane --certificate-key <key>

A.3 证书续期

控制平面证书默认 1 年到期(本集群约 2027-08-08)。到期前:

kubeadm certs renew all          # 在每台 master 上
# 然后重启各控制平面静态 Pod:临时移走 /etc/kubernetes/manifests/ 下对应 yaml 再放回

A.4 常见坑(本环境实际踩过)

  1. SystemdCgroup=false:kubelet 会报错或反复重启。containerd 必须 SystemdCgroup = true 且与 kubelet cgroupDriver: systemd 一致。
  2. pause 镜像拉不到:把 sandbox_image 改成本地可达的镜像(阿里云 pause:3.10.2),否则每个 Pod 的 sandbox 起不来。
  3. kube-proxy 用 iptables 而非 IPVS:本集群显式 mode: ipvs + 加载 ip_vs* 内核模块;若用 iptables 也是可行的,但大集群下 IPVS 性能更好。
  4. Keepalived weight 配错-40 导致 150-40=110 > 备机 100,永远不会切换;必须 ≤ -50(本环境 -60)。
  5. HAProxy 超时太短:长连接 timeout client/server 必须足够大(本环境 4h),否则 watch 频繁断流、集群抖动。
  6. VIP 与物理网段冲突:Pod 网段必须避开节点物理网段(本环境用 10.244.0.0/16 而非默认的 192.168.0.0/16)。
  7. Calico 选错网卡:用 IP_AUTODETECTION_METHOD=can-reach=192.168.1.1 锁定,避免选到 docker0/lo。
  8. apiserver/etcd 走代理:加入/初始化时必须 unset http_proxy 并设 no_proxy192.168.0.0/16,.svc,.cluster.local,否则 kubeadm 连本机 apiserver 被代理截胡而失败。
  9. swap 未关kubeadm init 会因 swap 开启而失败(除非 failSwapOn: false,本环境已设置)。
  10. hosts 不一致 / 主机名解析失败:7 台 /etc/hosts 必须互相包含,且主机名唯一,否则 etcd TLS 与节点注册会出问题。

附:本指南与脚本/配置的对应关系

本文章节对应脚本 / 配置
步骤 1.1scripts/aptbase.sh
步骤 1.2~1.4scripts/reset-install.sh
步骤 1.5scripts/netcfg.sh
步骤 2scripts/lb-setup.shscripts/lb2-setup.shscripts/lb-keepalived-update.shconfigs/haproxy.cfgconfigs/keepalived.conf
步骤 3scripts/init-master1.shconfigs/kubeadm-init.yaml
步骤 4~5scripts/join-tpl.sh(本地渲染每节点实例)
步骤 6scripts/calico.shconfigs/calico-v3.32.1.yaml
步骤 7scripts/verify.shscripts/status.sh
步骤 8scripts/vm-power.ps1scripts/vm-panel.ps1scripts/make-shortcut.ps1
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值