2026/10/21 19:13:27

Kubernetes多master高可用架构原理与零停机升级实战

Kubernetes多master高可用架构原理与零停机升级实战 1. 为什么单 master 不是“不够用”而是“不敢用”我第一次在某高校实验室部署一个用于图像处理Demo的Kubernetes集群时用的是最经典的单 master 架构一台8核16G的云服务器跑etcd kube-apiserver controller-manager scheduler三台worker节点负责跑模型推理任务。初期一切顺利直到某天凌晨三点监控告警疯狂刷屏——API Server响应延迟飙升到12秒所有kubectl命令超时正在运行的推理服务开始批量OOM重启。运维同学紧急登录发现master节点CPU持续100%、内存使用率98%而排查下来既不是业务负载突增日志显示QPS平稳也不是磁盘IO瓶颈iostat一切正常。最后翻到kube-controller-manager的日志里一行不起眼的报错E0523 03:17:42.112345 1 leaderelection.go:331] error retrieving resource lock kube-system/kube-controller-manager: Get https://10.0.1.10:6443/apis/coordination.k8s.io/v1/namespaces/kube-system/leases/kube-controller-manager: context deadline exceeded这行报错背后是一个被绝大多数新手忽略的底层事实单 master 的高可用性不是靠“它没挂”来保障的而是靠“它不能挂”来硬扛的。只要master节点上任何一个核心组件尤其是apiserver或etcd因资源争抢、内核OOM killer误杀、系统级bug或短暂网络抖动导致不可用整个集群就进入“半瘫痪”状态——你无法扩缩容、无法查日志、无法更新配置甚至连Pod状态都可能变成Unknown。更隐蔽的风险在于很多团队把单master当成“过渡方案”结果一用就是半年甚至一年期间不断往上面堆监控、日志、CI/CD插件直到某次内核升级后etcd进程莫名退出才意识到备份快照根本没验证过恢复耗时47分钟业务SLA直接违约。这不是理论推演。我在过去三年参与过的12个中等规模K8s落地项目中有7个在单master阶段遭遇过至少一次超过5分钟的控制平面中断。其中3次源于etcd数据目录权限被误改2次是kubelet与apiserver证书过期未轮换还有2次纯粹是宿主机内核panic——而这些故障在多master架构下本可以做到用户无感。关键不在于“master会不会挂”而在于“挂了之后集群还能不能继续提供服务”。单master的答案永远是“不能”而多master的答案是“取决于你设计得够不够细”。所以“将单master升级为多master”从来不是一道简单的“加机器”选择题而是一次对集群治理能力的全面体检。它逼你直面那些平时被掩盖的问题etcd数据一致性策略是否合理证书体系是否支持滚动更新负载均衡器能否正确健康检查apiserver的readiness端点甚至包括你的CI/CD流水线是否还硬编码着master节点的IP地址这些问题在单master时代可以“先放一放”但在升级过程中任何一个疏漏都会让整个迁移过程卡在凌晨三点让你对着终端日志发呆。真正的难点从来不在“怎么做”而在“为什么必须现在做”。2. 多master的本质不是堆机器而是重构信任链很多人看到“多master”第一反应是“那我再买两台同配置服务器把apiserver、etcd、controller-manager全复制一份不就行了”——这是最危险的认知陷阱。多master集群不是单master的简单横向扩展而是一次对Kubernetes控制平面信任模型的彻底重构。它的核心目标只有一个让集群的“大脑”具备自我诊断、自我选举、自我恢复的能力而不是依赖某个物理节点的绝对稳定。要理解这一点必须拆开看Kubernetes控制平面的四个核心组件如何协作etcd集群的唯一真相源Single Source of Truth存储所有对象的状态Pod、Service、ConfigMap等。它本身就是一个分布式键值数据库天然支持多节点集群但其一致性协议Raft要求奇数节点3/5/7且对网络延迟极其敏感。kube-apiserver集群的唯一入口Gateway所有读写请求必须经过它。它本身是无状态的可以水平扩展但所有实例必须连接到同一个etcd集群并共享一套认证授权配置。kube-controller-manager和kube-scheduler这两个组件负责集群的“决策”如Pod调度、Node健康检查、Endpoint同步。它们本身也是无状态的但必须保证同一时刻只有一个实例在执行关键操作否则会导致状态冲突比如两个scheduler同时给同一个Pod分配Node。问题来了如果只是把这四个组件原样复制到三台机器上会发生什么etcd集群能起来但三个apiserver会各自连接自己的本地etcd副本如果没配对数据立刻分裂三个controller-manager会同时尝试更新Node状态导致NodeCondition反复在Ready/NotReady之间跳变负载均衡器把流量打到任意apiserver但某个apiserver背后的etcd副本落后了2秒你kubectl get pod看到的就可能是“已删除”状态而实际Pod还在运行。因此多master的真正技术骨架是由三组精密咬合的机制构成的2.1 etcd集群从“单点存储”到“共识引擎”etcd不是“装上就能用”的服务它是整个多master架构的地基。它的部署必须满足三个硬性条件节点数必须为奇数Raft协议要求多数派quorum才能达成共识。3节点集群允许1节点宕机5节点允许2节点宕机。切记4节点集群的容错能力与3节点完全相同都只允许1节点宕机但写入性能反而更低因为每次写入需获得3票而非2票。网络必须低延迟、高可靠Raft的心跳和日志复制对网络RTT极其敏感。实测表明当三节点间平均RTT超过50ms时etcd leader选举成功率开始下降超过100ms写入延迟会指数级增长。这意味着跨可用区AZ部署etcd必须谨慎评估网络质量。数据目录必须独立且持久化etcd的数据目录默认/var/lib/etcd绝不能放在系统盘或临时存储上。我们曾在一个项目中因误将etcd数据目录挂载到云盘的临时缓存分区导致一次宿主机重启后etcd无法启动因为缓存分区被清空Raft日志丢失。部署时每个etcd节点需通过--initial-cluster参数显式声明所有成员# 在node1上启动etcd etcd --name infra1 \ --initial-advertise-peer-urls http://10.0.1.11:2380 \ --listen-peer-urls http://10.0.1.11:2380 \ --listen-client-urls http://10.0.1.11:2379 \ --advertise-client-urls http://10.0.1.11:2379 \ --initial-cluster-token etcd-cluster-1 \ --initial-cluster infra1http://10.0.1.11:2380,infra2http://10.0.1.12:2380,infra3http://10.0.1.13:2380 \ --initial-cluster-state new \ --data-dir /var/lib/etcd提示--initial-cluster-state new仅在首次初始化集群时使用。后续节点加入必须用existing否则会创建全新集群导致数据丢失。2.2 apiserver从“单一网关”到“无状态服务池”apiserver是唯一可以真正水平扩展的组件。它的扩展逻辑非常清晰只要所有实例连接到同一个健康的etcd集群并共享完全一致的证书、RBAC规则和准入控制器配置它们就是完全等价的。这意味着你可以用任何标准的四层负载均衡器如HAProxy、Nginx、云厂商SLB将客户端流量分发到多个apiserver实例。但这里有个致命细节负载均衡器的健康检查必须指向apiserver的/readyz端点而不是简单的TCP端口探测。因为apiserver进程存活不代表它能正常工作——它可能正卡在与etcd的连接上或者证书已过期。/readyz会主动检查etcd连接、informer同步状态等关键依赖。一个典型的HAProxy配置片段如下frontend k8s_apiserver bind *:6443 mode tcp default_backend apiserver_nodes backend apiserver_nodes mode tcp balance roundrobin option httpchk GET /readyz http-check expect status 200 server master1 10.0.1.11:6443 check fall 3 rise 2 server master2 10.0.1.12:6443 check fall 3 rise 2 server master3 10.0.1.13:6443 check fall 3 rise 2注意http-check expect status 200是关键。如果只用TCP检查当apiserver因etcd断连而无法响应readyz时HAProxy仍会把流量打过去导致客户端收到503错误。2.3 controller-manager与scheduler从“单点决策”到“领导者选举”这两个组件的多实例部署核心在于领导者选举Leader Election机制。Kubernetes内置了一套基于etcd的租约Lease对象的选举协议。当多个controller-manager实例启动时它们会尝试在kube-system命名空间下创建一个名为kube-controller-manager的Lease对象。只有成功创建并持续续租该对象的实例才会成为Active Leader执行所有控制循环其他实例则进入Spectator模式只监听Lease对象变化不做任何写操作。这个机制的精妙之处在于它不依赖外部协调服务如ZooKeeper完全基于Kubernetes自身API构建且选举过程对业务Pod完全透明。但这也带来一个实操陷阱Lease对象的租约时间renewDeadline和心跳间隔leaseDuration必须与你的网络环境匹配。默认值leaseDuration15s, renewDeadline10s在局域网内很稳但在跨AZ部署时若网络偶尔抖动导致心跳超时可能引发频繁的leader切换造成Controller Manager日志刷屏。我们在线上环境通常将leaseDuration调至30秒renewDeadline调至20秒以换取更高的稳定性。3. 升级路径不是“停机重建”而是“滚动缝合”将一个运行中的单master集群升级为多master最常被问到的问题是“需要停机吗”答案是可以做到零停机但前提是你的升级策略足够精细且对每个环节的失败回滚路径了然于心。我们采用的是一种“滚动缝合”策略——像外科医生缝合伤口一样逐层、逐组件地将新master节点“缝入”现有集群每一步都可验证、可回退。整个过程分为五个严格递进的阶段缺一不可3.1 阶段一基础设施准备与证书体系加固这是最容易被跳过的一步却是后续所有步骤成败的基础。单master集群的证书通常是kubeadm init自动生成的其ca.crt和ca.key默认只保存在master节点上且apiserver证书的Subject Alternative Name (SAN)往往只包含master节点IP和localhost。当你新增master节点时它需要用自己的IP访问apiserver而旧证书根本不包含这个IP握手必然失败。因此必须首先生成一套新的、支持多IP的CA和apiserver证书。核心操作是从原master节点安全导出ca.crt和ca.key注意ca.key是最高机密传输必须加密创建一个新的kubeadm-config.yaml明确列出所有master节点的IP和DNS名称kind: ClusterConfiguration apiVersion: kubeadm.k8s.io/v1beta3 kubernetesVersion: v1.28.0 certificatesDir: /etc/kubernetes/pki clusterName: my-cluster controlPlaneEndpoint: lb.my-cluster.local:6443 # 这是负载均衡器的VIP networking: podSubnet: 10.244.0.0/16 serviceSubnet: 10.96.0.0/12 --- kind: InitConfiguration apiVersion: kubeadm.k8s.io/v1beta3 nodeRegistration: criSocket: unix:///var/run/containerd/containerd.sock taints: [] labels: {} --- kind: JoinConfiguration apiVersion: kubeadm.k8s.io/v1beta3 controlPlane: localAPIEndpoint: advertiseAddress: 10.0.1.12 # 新master节点自己的IP bindPort: 6443 discovery: bootstrapToken: apiServerEndpoint: lb.my-cluster.local:6443 token: abcdef.0123456789abcdef caCertHashes: - sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx使用kubeadm init phase certs all --config kubeadm-config.yaml命令为所有master节点预生成证书包括apiserver、front-proxy、etcd等并将证书分发到对应节点。经验教训我们曾在一个项目中因忘记为新master节点生成etcd-healthcheck-client.crt证书导致其etcd进程无法通过健康检查被HAProxy踢出后端整整排查了3小时才定位到证书缺失。务必在分发前用openssl x509 -in /etc/kubernetes/pki/apiserver.crt -text -noout | grep DNS\|IP Address验证所有预期IP和DNS都已写入证书SAN字段。3.2 阶段二etcd集群的“热插入”这是整个升级中最惊心动魄的环节。目标是将原有的单节点etcd无缝融入一个三节点的新集群且全程不中断现有业务。核心思路是先让新节点以“learner”学习者身份加入现有etcd集群同步完全部数据后再将其提升为正式member。具体步骤以新增node2为例在原masternode1上使用etcdctl将node2添加为learner# 在node1上执行 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 add infra2 --learnertrue # 输出类似Member 2a7b3c4d5e6f7g8h has been added to cluster 1a2b3c4d5e6f7g8h as learner在node2上启动一个只读的etcd实例连接到node1并指定其为learneretcd --name infra2 \ --initial-advertise-peer-urls http://10.0.1.12:2380 \ --listen-peer-urls http://10.0.1.12:2380 \ --listen-client-urls http://10.0.1.12:2379 \ --advertise-client-urls http://10.0.1.12:2379 \ --initial-cluster-token etcd-cluster-1 \ --initial-cluster infra1http://10.0.1.11:2380,infra2http://10.0.1.12:2380 \ --initial-cluster-state existing \ --data-dir /var/lib/etcd \ --learnertrue \ --learner-start-staticyes监控learner同步进度。etcdctl --write-outtable endpoint status会显示Is Learner列为true且Raft Term应与node1一致。等待etcdctl endpoint health返回healthy且etcdctl member list中infra2的状态变为started。当同步完成通常需数分钟到数十分钟取决于数据量在node1上执行etcdctl member promote learner-id将其提升为正式member。关键提示--learner-start-staticyes参数至关重要。它确保learner在启动时不会尝试参与选举只专注同步数据。若省略此参数learner可能因Raft状态混乱而崩溃。3.3 阶段三新master节点的“控制平面注入”当etcd集群已稳定为三节点后下一步是将新master节点的控制平面组件apiserver、controller-manager、scheduler接入。此时新节点上的kubelet尚未加入集群它只是一个“裸机”。我们需要用kubeadm join命令但不是加入worker节点的方式而是以--control-plane参数加入控制平面。在node2上执行kubeadm join lb.my-cluster.local:6443 \ --token abcdef.0123456789abcdef \ --discovery-token-ca-cert-hash sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \ --control-plane \ --certificate-key xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx这个命令会启动kubelet并将其注册为master节点带有node-role.kubernetes.io/control-plane:NoSchedule污点将预生成的证书复制到/etc/kubernetes/pki/启动新的apiserver、controller-manager、scheduler容器并配置它们连接到lb.my-cluster.local:6443即我们即将配置的HAProxy VIP将新master节点的etcd member信息写入集群。执行后用kubectl get nodes -o wide应能看到node2状态为NotReady因为CNI插件尚未部署但kubectl get pods -n kube-system应能看到新的kube-apiserver-node2、kube-controller-manager-node2等Pod已Running。3.4 阶段四负载均衡器的“流量接管”这是用户感知最明显的一步。在此之前所有kubectl命令和CI/CD工具都直接连接原master节点IP如10.0.1.11:6443。现在我们必须将所有流量切换到HAProxy的VIP如lb.my-cluster.local:6443。操作本身很简单修改/etc/kubernetes/admin.conf文件将server:字段从https://10.0.1.11:6443改为https://lb.my-cluster.local:6443然后重新加载kubectl配置。但真正的挑战在于验证首先用curl -k https://lb.my-cluster.local:6443/readyz确认VIP可达且apiserver健康然后执行kubectl get nodes观察是否能稳定返回所有节点列表包括新master最关键的是执行一个“写操作”kubectl scale deployment nginx --replicas5确认Pod能成功创建并调度到worker节点。实战技巧在切换VIP前建议先在HAProxy配置中将新master节点node2的权重设为1而原masternode1权重设为10实现灰度流量导入。观察10分钟无异常后再将权重均等化。这样即使新节点apiserver有兼容性问题也只影响10%的请求。3.5 阶段五原master节点的“优雅退役”当新master集群稳定运行24小时以上且所有监控指标API Latency P99、etcd leader changes per hour、controller-manager leader transitions均处于基线范围内后就可以考虑将原master节点node1从控制平面中移除。移除不是简单地关机而是要确保其etcd member和kubernetes node资源被干净清理在node1上执行kubeadm reset --force这会停止所有kubernetes组件并清理/etc/kubernetes/目录在剩余任一master节点上用etcdctl member remove node1-member-id将其从etcd集群中永久删除执行kubectl delete node node1从kubernetes API中移除其节点记录。至此单master到多master的升级宣告完成。整个过程从准备到最终退役我们在线上环境的标准耗时是4小时17分钟含2小时观察期且全程业务无感知。4. 验证与反脆弱别只盯着“能不能用”要看“怎么失效”升级完成后很多人会松一口气执行几个kubectl get命令确认一切正常就认为大功告成。这是最大的认知误区。多master集群的价值不在于它“正常时有多好”而在于它“异常时有多稳”。因此验证工作必须围绕“失效场景”展开这才是真正的反脆弱测试。我们有一套标准化的“混沌验证清单”每次升级后必做4.1 场景一模拟单master节点宕机这是最基础的测试。选择一台master节点如node2执行sudo systemctl stop kubelet强制其离线。预期结果kubectl get nodes应在30秒内将node2状态标记为NotReadykubectl get pods -A应能正常返回且所有Running Pod状态不变kubectl logs、kubectl exec等交互式命令应能正常工作它们会自动重试到其他apiserverkubectl scale等写操作应能在2秒内完成无超时。关键观察点用kubectl get componentstatuses虽已弃用但仍是快速检查controller-manager/scheduler状态的捷径确认controller-manager和scheduler的Status仍为Healthy且其Age字段显示的Leader节点已切换到其他master。4.2 场景二模拟etcd网络分区比单节点宕机更危险的是网络分区Network Partition。例如让node1和node2之间的网络完全不通但node1-node3、node2-node3网络正常。此时etcd集群会分裂为两个子集{node1}和{node2, node3}。根据Raft协议只有拥有多数派2/3的子集{node2, node3}能继续提供写服务而{node1}会进入只读状态。测试方法在node1上执行sudo iptables -A OUTPUT -d 10.0.1.12 -j DROP sudo iptables -A OUTPUT -d 10.0.1.13 -j DROP模拟其无法与其他节点通信。预期结果etcdctl endpoint health在node1上会显示unhealthy而在node2/node3上显示healthykubectl get nodes应能正常返回且node1状态为NotReady最关键的是在node2或node3上执行kubectl create deploy test --imagenginx应能成功创建Deployment且其Pod被调度到worker节点。这证明控制平面的写能力未受影响。教训分享我们曾在一个金融客户项目中因未做此测试上线后遭遇一次跨机房网络抖动导致主数据中心的master节点被隔离但集群因缺乏多数派而拒绝所有写操作CI/CD流水线全部阻塞。此后我们将此测试纳入所有集群的SOP。4.3 场景三模拟apiserver证书轮换证书过期是生产环境中最隐蔽的故障源。Kubernetes的证书默认有效期为1年但很多团队从未规划过轮换流程。多master集群的证书轮换必须确保所有master节点的证书在同一窗口期内更新否则会出现部分apiserver因证书过期而被客户端拒绝的情况。标准轮换流程在任一master节点上执行kubeadm certs renew all生成新证书将新证书/etc/kubernetes/pki/下的所有.crt和.key文件安全分发到所有master节点覆盖旧文件逐个重启各master节点上的kubelet服务sudo systemctl restart kubelet使其加载新证书用openssl x509 -in /etc/kubernetes/pki/apiserver.crt -dates验证每个节点的证书有效期已更新。避坑指南kubeadm certs renew命令不会自动更新admin.conf、kubelet.conf等客户端配置文件中的证书。你必须手动用kubeadm init phase kubeconfig all --config kubeadm-config.yaml重新生成并分发到所有管理员和CI/CD服务器。我们为此专门写了一个Ansible Playbook确保零遗漏。4.4 场景四压力下的Leader选举稳定性高并发场景下controller-manager的Leader选举是否健壮我们用一个简单的压测脚本来验证# 持续每秒创建一个Job持续5分钟 for i in {1..300}; do kubectl create job stress-job-$i --imagebusybox -- sh -c sleep 1 sleep 1 done同时在后台监控kubectl get events -n kube-system | grep leader changed。一个健康的集群在300次Job创建过程中leader切换次数应为0或1次初始选举。如果出现5次以上切换则说明etcd集群或网络存在隐性瓶颈需要进一步排查。5. 运维守则多master不是终点而是新运维范式的起点当集群成功升级为多master后一个全新的运维挑战才真正开始。它不再是一个“单点设备”而是一个需要持续协同治理的分布式系统。我们总结了三条铁律是过去所有踩坑经验的结晶5.1 守则一etcd集群的“黄金三角”监控必须前置etcd的健康是整个集群的生命线但它的指标维度远超普通服务。我们强制要求所有多master集群必须监控以下三个核心指标并设置分级告警指标健康阈值告警级别说明etcd_disk_wal_fsync_duration_seconds_bucket(P99) 10msP1WAL写入延迟过高预示磁盘IOPS瓶颈或硬件故障etcd_network_peer_round_trip_time_seconds_bucket(P99) 50msP2节点间网络延迟跨AZ部署时此值易超标etcd_server_leader_changes_seen_total0 (24h)P1Leader频繁切换通常由网络抖动或etcd配置不当引起实操心得我们曾用PrometheusGrafana搭建了一套etcd专项看板其中最有效的视图是一个“Raft状态矩阵”X轴是所有etcd节点Y轴是is_leader,is_learner,raft_term,raft_index等状态字段每个单元格用颜色标识绿色正常黄色警告红色异常。运维人员一眼就能看出哪个节点状态异常无需翻日志。5.2 守则二所有master节点的“配置漂移”必须零容忍在单master时代修改/etc/kubernetes/manifests/kube-apiserver.yaml是常见操作。但在多master时代任何对单个master节点配置的手动修改都是灾难的种子。因为kubeadm管理的静态Pod配置是通过kubeadm init或kubeadm upgrade统一生成的。如果你在node2上手动加了一个--feature-gates...参数下次kubeadm upgrade时这个参数会被覆盖导致node2 apiserver启动失败。解决方案只有一条所有配置变更必须通过kubeadm config进行声明式管理。例如要为所有master节点启用ServerSideApply特性门控正确的做法是编辑kubeadm-config.yaml在ClusterConfiguration下添加apiServer: extraArgs: feature-gates: ServerSideApplytrue执行kubeadm upgrade apply v1.28.0 --config kubeadm-config.yaml该命令会自动将新配置推送到所有master节点。血泪教训某电商客户曾因一名工程师在生产master上手动修改了kube-scheduler的--bind-address参数导致其无法绑定到0.0.0.0只能监听127.0.0.1。当HAProxy流量打过来时连接被拒绝。问题持续了17分钟直到有人想起kubeadm的配置管理机制。从此他们将/etc/kubernetes/manifests/目录设为只读并在CI/CD中加入配置合规性扫描。5.3 守则三备份与恢复的“双盲演练”必须季度执行多master集群的备份绝不是简单地etcdctl snapshot save。一个完整的备份策略必须包含etcd快照使用etcdctl snapshot save并验证其完整性etcdctl snapshot statusKubernetes证书/etc/kubernetes/pki/目录的完整归档kubeadm配置kubeadm-config.yaml的最新版本集群状态快照kubectl get all --all-namespaces -o yaml cluster-state.yaml。但更重要的是恢复演练。我们要求每季度进行一次“双盲演练”由另一组运维人员在完全不知道备份介质位置和恢复步骤的情况下仅凭文档从零开始恢复一个测试集群。演练必须覆盖两种场景场景A小范围故障仅etcd数据损坏用快照恢复etcd其他组件保持运行场景B全集群毁灭所有master节点不可用需用备份介质从头部署全新集群并将worker节点重新加入。个人体会我在某次双盲演练中发现备份脚本里一个tar命令漏写了-C参数导致证书目录被错误打包。这个Bug在三年里从未暴露直到演练时恢复失败。那一刻我深刻意识到备份的价值不在于它存在而在于它被证明能用。现在我们的备份脚本末尾必定跟着一行etcdctl snapshot restore ... echo Restore OK失败则立即告警。多master集群的终极价值不在于它让你少操了多少心而在于它让你在面对故障时拥有了从容选择“何时修复”而非“是否能修”的底气。这种底气来自于对每一个组件原理的敬畏对每一条配置参数的审慎以及对每一次看似微小的变更都抱持着“它可能在凌晨三点毁掉一切”的警惕。这才是运维的真正修行。