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 infra1=http://10.0.1.11:2380,infra2=http://10.0.1.12:2380,infra3=http://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)必须与你的网络环境匹配。默认值(leaseDuration=15s, renewDeadline=10s)在局域网内很稳,但在跨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为例):
- 在原master(node1)上,使用etcdctl将node2添加为learner:
# 在node1上执行 ETCDCTL_API=3 etcdctl --endpoints=https://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 --learner=true # 输出类似:Member 2a7b3c4d5e6f7g8h has been added to cluster 1a2b3c4d5e6f7g8h as learner- 在node2上,启动一个只读的etcd实例,连接到node1,并指定其为learner:
etcd --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 infra1=http://10.0.1.11:2380,infra2=http://10.0.1.12:2380 \ --initial-cluster-state existing \ --data-dir /var/lib/etcd \ --learner=true \ --learner-start-static=yes- 监控learner同步进度。
etcdctl --write-out=table 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-static=yes参数至关重要。它确保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 --replicas=5,确认Pod能成功创建并调度到worker节点。
实战技巧:在切换VIP前,建议先在HAProxy配置中,将新master节点(node2)的权重设为1,而原master(node1)权重设为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状态标记为NotReady;kubectl get pods -A应能正常返回,且所有Running Pod状态不变;kubectl logs、kubectl exec等交互式命令应能正常工作(它们会自动重试到其他apiserver);kubectl 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上显示healthy;kubectl get nodes应能正常返回,且node1状态为NotReady;- 最关键的是:在node2或node3上执行
kubectl create deploy test --image=nginx,应能成功创建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 --image=busybox -- 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) | < 10ms | P1 | WAL写入延迟过高,预示磁盘IOPS瓶颈或硬件故障 |
etcd_network_peer_round_trip_time_seconds_bucket(P99) | < 50ms | P2 | 节点间网络延迟,跨AZ部署时此值易超标 |
etcd_server_leader_changes_seen_total | 0 (24h) | P1 | Leader频繁切换,通常由网络抖动或etcd配置不当引起 |
实操心得:我们曾用Prometheus+Grafana搭建了一套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: "ServerSideApply=true"- 执行
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 status); - Kubernetes证书:
/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集群的终极价值,不在于它让你少操了多少心,而在于它让你在面对故障时,拥有了从容选择“何时修复”而非“是否能修”的底气。这种底气,来自于对每一个组件原理的敬畏,对每一条配置参数的审慎,以及对每一次看似微小的变更,都抱持着“它可能在凌晨三点毁掉一切”的警惕。这,才是运维的真正修行。