首页 / 资讯中心 / 文章详情

K8s单控制面扩容三节点高可用实战:从规划到踩坑

K8s单控制面扩容三节点高可用实战:从规划到踩坑 ★ FEATURED ARTICLE
1. 扩容单机K8s前先想清楚这几个问题我最近把一套已经跑了一段时间的单机 Kubernetes 集群从单个 master 节点扩成了三个控制面节点。这个事说白了就是给控制面做高可用原来只有一台机器既跑 kube-apiserver、kube-controller-manager、kube-scheduler又跑 etcd机器一挂整条业务链路都没法调度。扩容之后API Server 前面挂了一个负载均衡入口三台控制面节点共享同一份 etcd 数据任意一台宕机另外两台仍能维持 quorum业务不受影响。整个过程用 kubeadm 就能完成不需要重装系统也不需要把业务全部迁移出去。适合那些生产环境已经上了 Kubernetes但控制面还在裸奔的团队。先说结论如果只是测试环境单机本身够用扩容到三台更多是为了练手如果是生产环境我建议至少三台控制面。这里非常容易踩一个坑就是“两台 master 是不是就够了”。不够。etcd 的 quorum 要求是多数派两台节点组成的集群一台挂了之后剩下 1 台1 不可能大于 2/2所以整个控制面会变成只读甚至不可用。三台意味着允许坏一台五台允许坏两台。这个道理和“越多越好”无关而是和故障容忍度强相关。还有一个常见误区是“只要加两台 master高可用就有了”。如果没有负载均衡所有 kubeconfig 和客户端仍然指向原来的 master1 IP那 master1 挂了之后客户端还是连不上。多 master 必须配合一个稳定的虚拟入口通常是 VIP 或域名才有可能真正失败切换。1.1 单节点的故障点到底在哪单机 Kubernetes 表面上“能跑”但控制面的几个组件全挤在同一台机器上kube-apiserver所有请求的前门Pod 创建、删除、查询都走它。kube-controller-manager负责控制器逻辑有 leader 选举。kube-scheduler负责把 Pod 调度到节点。etcd保存集群所有状态是控制面的数据库。单机模式下这几样东西任一个出问题基本就是整个集群不可用。比如 etcd 所在磁盘满了API Server 读写失败kube-apiserver 所在进程被 OOMkubectl直接连不上甚至只是重启服务器也会造成一段空窗期。多 master 架构解决的就是这个“控制面单点”问题。1.2 多 master 架构到底长什么样最常见的 kubeadm 高可用方案是“堆叠 etcd”每个控制面节点上都跑一个 etcd 成员同时再跑 apiserver、controller-manager、scheduler。多个 etcd 成员之间通过 2380 端口通信形成一个 etcd 集群。API Server 不是一个入口而是三个入口前端由一个负载均衡器统一分发流量。这套方案的好处是省机器三个节点既能跑控制面组件又能互相形成 etcd 多数派。缺点是控制和数据存储耦合在一起控制面节点压力大。外部 etcd 方案则把 etcd 单独拆到三台机器上控制面节点可以更多但架构和维护成本更高。对这个项目来说堆叠 etcd 是最合理的因为它贴近 kubeadm 原生支持迁移成本低也容易排查。1.3 控制面高可用不等于业务高可用这一点我必须强调。把 master 从一台扩成三台解决的是“控制面”的高可用不等于你的业务 Pod 高可用。如果集群里还是只有一个 worker或者业务 Pod 只有一份副本那 worker 挂了业务照样断。多 master 扩容完成后还需要审视业务副本数、Pod 多副本调度、存储高可用这些环节。2. 扩容前的规划网络、负载均衡和关键参数扩容不是直接在机器上跑一个 join 命令那么简单。我最开始踩过的坑就是没规划 VIP导致后面加控制面节点时一堆证书和访问端点问题。先把基础规划做好后面会顺很多。2.1 节点角色与 IP 规划我这次用的是三台控制面节点加两台负载均衡器。IP 就用私有网段的例子来演示实际环境按自己的网段替换。主机名IP 地址角色lb0110.20.30.31HAProxy Keepalivedlb0210.20.30.32HAProxy Keepalivedmaster110.20.30.11已有的单机控制面也是数据节点master210.20.30.12新增控制面etcd 成员master310.20.30.13新增控制面etcd 成员worker110.20.30.21业务 workerVIP10.20.30.20负载均衡虚拟 IP所有节点最好使用固定的主机名并且/etc/hosts里统一写清楚。不要因为偷懒就依赖 DNS很多 kubeadm join 失败都和使用 IP 变化、主机名重复有关。2.2 需要放通的端口控制面扩容最容易被防火墙拦住的是 etcd 的 2379、2380 端口。很多人在同一个网段里搭集群以为没有防火墙结果节点上有公司统一的安全 agent直接把端口挡了。端口作用放通范围6443kube-apiserver所有客户端、worker、LB 节点2379etcd clientmaster 节点之间2380etcd peermaster 节点之间10250kubeletmaster 到所有节点10257kube-controller-managermaster 节点之间10259kube-schedulermaster 节点之间如果你用的容器网络插件有额外要求比如 BGP 或 VXLAN也要一并放通。实际操作中端口问题往往是新节点加入后NotReady或者 etcd 一直无法建立 peer 连接。2.3 版本统一和运行时一致性三台控制面节点上的 kubelet、kubeadm、kubectl 版本必须一致最好连 containerd 或 CRI 运行时版本也保持一致。我见过有人 master1 用 v1.28master2 用 v1.29结果 kubeadm join 正常但 kubelet 和 apiserver 版本差异导致证书、API 兼容性出问题。版本统一是成本最低的预防手段。另外加入新节点前要先确认新节点的 containerd 配置和原有节点一致尤其是 systemd cgroup driver 的配置。常见问题是在新节点上没改 containerd 配置导致 kubelet 报 cgroup driver 不匹配。3. 实操过程从单控制面扩容到三节点控制面下面这部分是这次扩容的核心。我会按实际操作的顺序来写尽量把关键命令和为什么这么做讲清楚。3.1 第一步备份原来单节点集群任何控制面组件变更之前备份都是必须的。单节点集群虽然没有真正的 HA但 etcd 里存着所有业务状态不能因为扩容失败把数据搞丢。在 master1 上执行 etcd 快照备份mkdir -p /backup/k8s ETCDCTL_API3 etcdctl \ --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 \ snapshot save /backup/k8s/etcd-snapshot-$(date %F-%H%M).db同时把/etc/kubernetes整个目录拷贝一份。里面包含证书、kubeadm 配置、静态 Pod manifest属于控制面的“配置文件快照”。tar czf /backup/k8s/kubernetes-cfg-$(date %F-%H%M).tar.gz /etc/kubernetes这个备份文件最好再传到另一台机器或对象存储不要只留在 master1 本机。否则 master1 磁盘坏了备份也一起没了。3.2 第二步确定控制面访问端点并补证书 SAN这是整个扩容里最容易被忽略却最容易引发后续故障的一步。原来的单节点集群可能是用kubeadm init直接初始化的所有 kubeconfig 里写的都是https://10.20.30.11:6443。现在前端多了 VIP10.20.30.20如果 API Server 的证书里没有这个 VIP你通过 VIP 访问时浏览器或 kubectl 会直接报证书校验失败。先在 master1 上查看当前 apiserver 证书里的 SANopenssl x509 -in /etc/kubernetes/pki/apiserver.crt -noout -text | grep -A1 Subject Alternative Name正常情况下能看到kubernetes.default.svc、kubernetes.default.svc.cluster.local、127.0.0.1以及 master1 的 IP。要把 VIP 和新 master 的 IP 都加进去重新签发 apiserver 证书。我建议先准备一个 kubeadm 配置cat /tmp/kubeadm-ha.yaml EOF apiVersion: kubeadm.k8s.io/v1beta3 kind: ClusterConfiguration kubernetesVersion: v1.28.12 controlPlaneEndpoint: 10.20.30.20:6443 apiServer: certSANs: - 127.0.0.1 - 10.20.30.11 - 10.20.30.12 - 10.20.30.13 - 10.20.30.20 - k8s-api.local networking: podSubnet: 10.244.0.0/16 serviceSubnet: 10.96.0.0/12 EOF然后把原来证书里已有的内部服务 SAN 全部抄进certSANs不能漏。接着执行kubeadm init phase certs apiserver --config /tmp/kubeadm-ha.yaml这条命令会用同样的 CA 重新生成 apiserver 证书。生成完以后需要触发 kube-apiserver 静态 Pod 重启才能加载新证书。kubectl -n kube-system delete pod kube-apiserver-master1这一步会有短暂的控制面不可用。如果不想打断业务尽量选低峰期操作。最后把本机 admin 配置指向 VIPkubectl --kubeconfig/etc/kubernetes/admin.conf config set-cluster kubernetes --serverhttps://10.20.30.20:6443这一步的意义是让之后的操作都走 VIP提前验证负载均衡入口和证书链路是否正常。3.3 第三步部署 HAProxy Keepalived 负载均衡负载均衡器不要只放一台。只放一台等于把单点从 master 搬到了 LB 上高可用等于没做。我在 lb01 和 lb02 两台机器上都部署 HAProxy再用 Keepalived 做 VIP 漂移。HAProxy 配置核心如下cat /etc/haproxy/haproxy.cfg EOF global log /dev/log local0 maxconn 4096 default-timeout connect 5s default-timeout client 60s default-timeout server 60s frontend k8s-apiserver bind 10.20.30.20:6443 mode tcp default_backend k8s-masters backend k8s-masters mode tcp balance roundrobin option ssl-hello-chk server master1 10.20.30.11:6443 check fall 3 rise 2 server master2 10.20.30.12:6443 check fall 3 rise 2 server master3 10.20.30.13:6443 check fall 3 rise 2 EOF这里我用了 TCP 模式的ssl-hello-chk健康检查因为后端的 6443 是 TLS 端口这种检查方式最省心不会因为健康检查本身阻塞连接。如果 HAProxy 版本支持 HTTP 健康检查也可以配/livez但要确认后端证书校验方式没问题。Keepalived 配置如下lb01 和 lb02 几乎一样只有priority不同vrrp_script check_haproxy { script /usr/bin/killall -0 haproxy interval 2 weight 2 } vrrp_instance k8s_ha { state BACKUP interface eth1 virtual_router_id 88 priority 110 advert_int 1 track_script { check_haproxy } virtual_ipaddress { 10.20.30.20/24 dev eth1 } }lb02 的priority我设为 100。两台都配成 BACKUP配合优先级来选主。如果主节点上的 HAProxy 进程挂了Keepalived 会自动把 VIP 漂移到备节点。如果有网络环境禁止组播就在 Keepalived 配置里加unicast_src_ip和unicast_peer用单播方式同步 VRRP 报文。部署完后在任意一台服务器上测试curl -k https://10.20.30.20:6443/livez如果返回ok说明 VIP 和 HAProxy 已经正常工作。3.4 第四步把 master2 加入控制面新节点 master2 上需要提前装好 containerd、kubeadm、kubelet、kubectl并且完成同样的系统初始化。然后回到 master1 上传控制面证书并创建 tokenkubeadm init phase upload-certs --upload-certs执行后会输出一个--certificate-key注意保存好这个 key 用来解密和控制面证书分发。接着创建 tokenkubeadm token create --print-join-command它会输出一个标准的 join 命令。我们需要把它改造成控制面 join 命令kubeadm join 10.20.30.20:6443 \ --token token \ --discovery-token-ca-cert-hash sha256:hash \ --control-plane \ --certificate-key certificate-key \ --node-name master2在 master2 上执行这条命令。注意这里 join 的地址是 VIP而不是 master1 的 IP这样节点生成的各种 kubeconfig 都会指向 VIP后续客户端切换入口更干净。--control-plane参数告诉 kubeadm这个节点要安装控制面组件并把 etcd 作为新 member 加入。新 etcd 成员会先以 learner 角色加入等数据追上后再提升为正式成员这个机制保证了在扩容过程中老 etcd 集群不至于因为追加成员而产生大量压力。执行完成后在 master2 上验证kubectl --kubeconfig/etc/kubernetes/admin.conf get nodes如果 master2 状态是NotReady先别急着继续要看 kubelet 日志和 etcd 日志。绝大多数情况是端口没放通或者网络插件没能在新节点上启动。3.5 第五步用同样方式加入 master3master3 的操作和 master2 完全相同。有一点要注意不要同时把 master2 和 master3 一起执行 join一次只能加一台。控制面节点加入涉及 etcd 集群变更串行操作能让问题是可控的。master3 加入后在任意一个控制面节点执行kubectl get nodes -o wide正常情况下应该能看到 master1、master2、master3 都是Ready并且角色都是control-plane。再验证 etcd 集群成员kubectl -n kube-system exec etcd-master1 -- sh -c \ ETCDCTL_API3 etcdctl --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输出里应该有三个 member分别对应三台 master。到这里控制面已经扩成了三节点。4. 常见问题与排查技巧实录实战过程中一定会遇到问题。下面这些是我实际踩过的以及同行群里被反复问过的场景。4.1 证书 SAN 缺失导致 VIP 访问失败现象是curl https://VIP:6443/livez报证书错误但直接访问 master1 的 6443 没问题。排查命令echo | openssl s_client -connect 10.20.30.20:6443 -servername k8s-api.local 2/dev/null | openssl x509 -noout -text | grep -A1 Subject Alternative Name如果输出的 SAN 里没有 VIP 或对应的域名就说明 apiserver 证书需要重新签发。具体操作在 3.2 节已经写了。这里再强调一句重新签发证书后必须重启 kube-apiserver而且如果已经有多台控制面节点要保证每台节点上的 apiserver 证书都包含相同的 SAN最好直接用 kubeadm 的 upload-certs 机制同步。4.2 kubeadm join 一直卡住或失败常见原因有三个。第一token 过期。默认 token 只有 24 小时有效期如果 init 之后隔了很久才 join重新生成 token 即可。第二2380 端口不通。新节点 etcd 无法和老节点通信。可以先在 master2 上执行nc -vz 10.20.30.11 2380如果不通检查防火墙和安全组。这个问题在物理机和云服务器上都很常见。第三证书 key 没有传入。控制面 join 必须有--certificate-key否则 kubeadm 拿不到控制面证书。报错信息一般会明确提示缺少certificate-key重新执行 upload-certs 再拿一次 key 就行。4.3 新 master 一直 NotReady新控制面节点加入后NotReady先看节点状态kubectl describe node master2常见原因是容器网络插件没有在新节点上启动。很多 CNI 的 DaemonSet 通过节点亲和性和 toleration 运行在控制面节点上如果新 master 没有对应的 label或者网络策略不允许Pod 就一直 Pending。看一下 CNI 插件 Pod 是否正常运行kubectl get pods -n kube-system -o wide | grep -E calico|flannel|cilium如果是 CNI 问题在 kube-system 日志里通常能看到“network plugin is not ready”之类的提示。另一个原因是 kubelet 的 cgroup driver 和 CRI 不一致。检查 master2 的 kubelet 日志journalctl -u kubelet -f | grep -i cgroup如果出现cgroup driver: cgroupfs is different from systemd就把 containerd 的配置改成SystemdCgroup true后重启 containerd。4.4 etcd 成员异常或日志报错加入新成员之后老节点的 etcd 偶尔会报 peer 连接失败。先用 etcd member list 看状态如果某个 member 是 unstarted就去看对应节点上的 etcd 容器日志kubectl -n kube-system logs etcd-master2 --tail200常见报错是 peer URL 不可达或者 hostname 不一致。etcd 的 peer URL 在创建时会记录节点 IP如果后来节点 IP 变了就需要更新 etcd member。避免这个问题的最好办法是 IP 规划后不要轻易改同时/etc/hosts保持一致。4.5 千万别让两台控制面同时重启三节点控制面允许坏一台不代表允许两台同时不可用。日常维护时哪怕只是想重启一台 master也要先确认另外两台都健康etcd 状态正常。如果重启过程中另外一台又出问题整个 etcd 就会失去 quorum集群 API 直接不可用恢复起来远比普通故障麻烦。4.6 常见问题速查问题现象可能原因处理办法通过 VIP 访问 API 证书报错apiserver 证书缺 VIP SAN重签证书并加入 VIP、域名 SANmaster join 报 token 过期token 默认 24h 有效期重新生成 tokenetcd 无法建立 peer 连接2380 端口未放通检查防火墙 / 安全策略新节点 NotReadyCNI 未启动 / cgroup 不一致检查 CNI Pod 和 kubelet 日志执行 kubeadm join 后卡在等待 kubelet新节点运行时版本不一致统一版本后 reset 重试删除节点后再次加入失败Node 对象或 etcd member 残留清理旧 Node、etcd member 后重新 join5. 扩容完成后的日常运维建议控制面从一台变成三台只是高可用的开始。真正考验的是之后怎么运维。5.1 etcd 备份一定要自动化三台控制面的 etcd 虽然有 HA但 HA 不等于不会丢数据。误删 namespace、配置写坏、多节点同时故障都需要靠备份恢复。我在 master1 上写了一个简单的定时备份脚本cat /usr/local/bin/etcd-snapshot.sh EOF #!/bin/bash BACKUP_DIR/backup/etcd TS$(date %F-%H%M) mkdir -p $BACKUP_DIR ETCDCTL_API3 etcdctl \ --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 \ snapshot save $BACKUP_DIR/snapshot-$TS.db find $BACKUP_DIR -name snapshot-*.db -mtime 7 -delete EOF chmod x /usr/local/bin/etcd-snapshot.sh然后加到 crontab15 3 * * * /usr/local/bin/etcd-snapshot.sh备份文件要定期同步到独立存储不要只在控制面节点本地留一份。5.2 控制面节点的优雅维护流程维护控制面节点和普通 worker 节点不太一样。普通业务 Pod 可以通过kubectl drain踢走但控制面的 kube-apiserver、etcd 是静态 Pod不能简单迁移。必须遵循“一次只动一台先检查后操作”的原则。重启一台 master 前我习惯先确认kubectl get nodes kubectl -n kube-system get pods -o wide | grep -E etcd|kube-apiserver确保三台控制面都 Readyetcd 没有 abnormal member然后再重启。重启后观察 kubelet 和 etcd 日志确认节点重新加入集群并追上数据。5.3 监控指标重点关注什么扩容完以后我给控制面加了几个关键监控指标etcd 是否成功选主对应etcd_server_has_leader。etcd 写盘耗时对应etcd_disk_backend_commit_duration_seconds。API Server 的请求延迟和错误码对应apiserver_request_duration_seconds和apiserver_request_total。控制面节点 kubelet 的up状态。三台控制面里如果一台机器上的 etcdfsync耗时过高会拖慢整个控制面的写请求所以磁盘性能不能省最好用 SSD并监控磁盘 IO 延迟。这套扩容做完之后我个人的体会是真正的难点从来不是执行那几条 join 命令而是前期规划和遇到问题时的边界判断。尤其是证书 SAN 和 VIP 的选择越早决定越少返工。如果你也想给现有单机集群做多 master 扩容建议先把备份做好、把 LB 搭好再动手加第二台和第三台。一次只加一台每加一台就验证一次整个操作会稳很多。
阅读完成 · 觉得有帮助?
咨询建站