Kubernetes 集群高可用架构设计与故障切换实战指南
很多团队一开始做 Kubernetes 集群,目标都很朴素:先跑起来。但业务一旦真上量,问题就会很现实:
- 单个 master 挂了,集群还能不能管?
- etcd 抖动一下,API Server 为什么全红?
- 节点宕机后,Pod 为啥迟迟没漂移?
- 负载均衡切换了,kubectl 却还是连不上?
我自己第一次接手生产集群高可用改造时,最大的感受是:Kubernetes 的“高可用”不是加几个副本那么简单,它是控制面、数据面、存储面、网络面一起配合的结果。
这篇文章不打算只讲概念,而是从故障现象、架构设计、切换原理、可运行示例、排障路径、止血手段几个角度,把这件事讲透。
背景与问题
在 Kubernetes 里,“高可用”通常至少包含两层意思:
-
控制面高可用:
kube-apiserver、kube-controller-manager、kube-scheduler、etcd 任一节点故障,不影响集群继续管理和调度。 -
工作负载高可用:
某个 worker 节点、Pod、网络链路异常时,业务流量能自动绕过故障点,服务尽量不中断。
在实际生产里,常见故障现象通常长这样:
kubectl get pod卡住或超时- 新 Pod 无法调度,但旧业务似乎还在跑
- Deployment 副本数足够,但 Service 访问间歇性失败
- 节点
NotReady很久,Pod 不迁移 - etcd leader 频繁切换,控制面整体抖动
这些问题背后,往往不是单点故障本身,而是高可用链路上某一环设计不完整。
先明确一个目标:Kubernetes 高可用到底要保什么
如果你要设计一套“靠谱”的 K8s 高可用架构,我建议先把目标拆成 4 个问题:
- API 是否持续可访问
- etcd 是否多数派可写
- 控制器和调度器是否能自动选主
- 业务流量是否能自动绕过故障节点
换句话说,K8s 高可用不是“所有节点都不挂”,而是:
在部分节点或组件失效时,集群仍然具备核心能力,并在可接受时间内完成故障切换。
核心原理
这一节不绕,直接讲最关键的几个机制。
1. 控制面高可用的基本拓扑
生产环境常见方案是:
- 3 台或 5 台 control plane 节点
- 前面挂一个 VIP 或四层负载均衡器
- 每个节点运行 kube-apiserver
- kube-controller-manager / kube-scheduler 以多副本运行,通过 leader election 工作
- etcd 采用奇数节点形成 quorum
下面这张图可以先建立整体认知。
flowchart TB
U[运维/CI/CD/kubectl] --> LB[VIP / LoadBalancer / HAProxy]
LB --> A1[kube-apiserver CP1]
LB --> A2[kube-apiserver CP2]
LB --> A3[kube-apiserver CP3]
A1 --> E1[etcd-1]
A2 --> E2[etcd-2]
A3 --> E3[etcd-3]
C1[kube-controller-manager CP1] -. leader election .- C2[kube-controller-manager CP2]
C2 -. leader election .- C3[kube-controller-manager CP3]
S1[kube-scheduler CP1] -. leader election .- S2[kube-scheduler CP2]
S2 -. leader election .- S3[kube-scheduler CP3]
A1 --> W1[worker-1]
A2 --> W2[worker-2]
A3 --> W3[worker-3]
2. etcd 决定了控制面的“命门”
很多人把 API Server 当成控制面的核心,但真正决定生死的是 etcd。
原因很简单:
- kube-apiserver 是无状态的,多实例容易横向扩展
- etcd 保存了集群状态,是一致性存储
- etcd 要想正常写入,必须满足多数派(quorum)
例如 3 节点 etcd:
- 存活 3 台:可读可写
- 存活 2 台:可读可写
- 存活 1 台:通常不可写,集群控制面基本瘫痪
所以高可用设计里,宁可先确认 etcd 稳不稳,也别只盯着 apiserver 副本数。
3. Controller Manager 和 Scheduler 通过选主避免“双写”
控制器管理器和调度器通常会多副本部署,但并不是所有实例同时执行业务逻辑,而是通过 Leader Election 选出一个 leader。
它的意义是:
- leader 挂了,其他实例会接管
- 避免多个实例同时更新同一资源,造成状态冲突
这类切换一般是秒级到十几秒级,具体取决于:
--leader-elect--leader-elect-lease-duration--leader-elect-renew-deadline--leader-elect-retry-period
4. 节点故障切换不是“瞬时”的
这是非常容易误判的一点。
一个 worker 节点宕机后,Pod 不会在 1 秒内立刻迁走,通常流程是:
- kubelet 心跳中断
- Node Controller 发现节点异常
- 节点状态变成
NotReady - 超过
pod-eviction-timeout或 taint 相关阈值 - 控制器重新创建 Pod
- Service / EndpointSlice 更新流量转发目标
过程图如下:
sequenceDiagram
participant Kubelet as kubelet(NodeA)
participant APIServer as kube-apiserver
participant NodeCtl as node-controller
participant Scheduler as kube-scheduler
participant NodeB as NodeB
Kubelet->>APIServer: 定期上报心跳
Note over Kubelet: 节点宕机/网络中断
NodeCtl->>APIServer: 检测心跳超时
NodeCtl->>APIServer: 将 NodeA 标记为 NotReady
NodeCtl->>APIServer: 驱逐受影响 Pod
Scheduler->>APIServer: 为新 Pod 选择节点
Scheduler->>NodeB: 调度到 NodeB
NodeB->>APIServer: 新 Pod Ready
5. 业务高可用依赖的不只是 Pod 副本
即使你写了 replicas: 3,也不代表高可用已经完成。还要看:
- 副本有没有分散到不同节点
- 有没有跨可用区分布
- 有没有
PodDisruptionBudget - 有没有健康检查
- 有没有拓扑感知调度
- Service 是否正确剔除不健康端点
方案设计:一套更贴近生产的 HA 架构
这里给一套中型生产集群常见设计。
推荐拓扑
- 3 台 control plane
- 3 台 etcd(与 control plane 同机或独立,取决于规模)
- 2 台 HAProxy + Keepalived 提供 VIP
或者云上用 SLB/NLB - 至少 3 台 worker
- 容器网络插件选择支持 NetworkPolicy、稳定性较好的方案,例如 Calico / Cilium
取舍建议
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 单 control plane + 单 etcd | 成本低,搭建快 | 单点明显 | 测试环境 |
| 3 control plane + stacked etcd | 结构简单,常见 | control plane 与 etcd 资源竞争 | 中小型生产 |
| 3 control plane + external etcd | 职责分离,稳定性更好 | 运维复杂度更高 | 中大型生产 |
| 云厂商托管控制面 | 省运维,稳定性高 | 可控性较弱,成本可能更高 | 云上优先 |
控制面状态切换图
stateDiagram-v2
[*] --> Healthy
Healthy --> APIServerDegraded: 单个 apiserver 故障
APIServerDegraded --> Healthy: LB 切走故障实例
Healthy --> LeaderSwitching: scheduler/controller leader 故障
LeaderSwitching --> Healthy: 新 leader 产生
Healthy --> EtcdMinorFailure: 单个 etcd 节点故障
EtcdMinorFailure --> Healthy: 成员恢复且重新加入
EtcdMinorFailure --> ControlPlaneUnavailable: etcd 失去多数派
LeaderSwitching --> ControlPlaneUnavailable: 多个关键组件同时失效
ControlPlaneUnavailable --> [*]
现象复现:模拟节点故障与控制面切换
排障之前,我建议先在测试环境里亲手做一次“可控故障演练”。不做演练,很多参数只停留在纸面上。
场景 1:模拟 worker 节点宕机
先部署一个 3 副本 Nginx 服务。
apiVersion: apps/v1
kind: Deployment
metadata:
name: ha-nginx
spec:
replicas: 3
selector:
matchLabels:
app: ha-nginx
template:
metadata:
labels:
app: ha-nginx
spec:
containers:
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 3
periodSeconds: 5
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 10
periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
name: ha-nginx
spec:
selector:
app: ha-nginx
ports:
- port: 80
targetPort: 80
type: ClusterIP
应用:
kubectl apply -f ha-nginx.yaml
kubectl get pod -o wide -l app=ha-nginx
然后把某个节点停机,或者先做一个温和点的操作:
kubectl drain <node-name> --ignore-daemonsets --delete-emptydir-data
观察:
kubectl get node -w
kubectl get pod -o wide -l app=ha-nginx -w
kubectl get endpointslice -l kubernetes.io/service-name=ha-nginx -w
你会看到:
- 节点状态变化
- Pod 被逐出后在其他节点重建
- EndpointSlice 更新,流量目标变化
场景 2:模拟单个 apiserver 故障
如果前面挂了 HAProxy/NLB,可以直接在某个 control plane 节点上停止 apiserver:
sudo systemctl stop kubelet
或者如果是静态 Pod 模式,临时移走 manifest(测试环境才建议这么做):
sudo mv /etc/kubernetes/manifests/kube-apiserver.yaml /tmp/
然后在客户端连续访问:
for i in {1..20}; do
kubectl get nodes >/dev/null && echo "ok-$i" || echo "fail-$i"
sleep 1
done
如果负载均衡和健康检查配置合理,通常只会有极短时间抖动,或者几乎无感。
实战代码(可运行)
这一节给出一套更完整的可运行示例:
通过 Pod 反亲和 + PDB + 拓扑分散,提升业务在节点故障下的存活能力。
1. 高可用 Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-ha
spec:
replicas: 3
selector:
matchLabels:
app: web-ha
template:
metadata:
labels:
app: web-ha
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: web-ha
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: web-ha
topologyKey: kubernetes.io/hostname
containers:
- name: web
image: nginx:1.25
ports:
- containerPort: 80
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "256Mi"
readinessProbe:
httpGet:
path: /
port: 80
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 3
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 10
periodSeconds: 10
2. PodDisruptionBudget
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: web-ha-pdb
spec:
minAvailable: 2
selector:
matchLabels:
app: web-ha
3. Service
apiVersion: v1
kind: Service
metadata:
name: web-ha
spec:
selector:
app: web-ha
ports:
- name: http
port: 80
targetPort: 80
type: ClusterIP
应用命令:
kubectl apply -f web-ha.yaml
kubectl apply -f web-ha-pdb.yaml
kubectl apply -f web-ha-svc.yaml
kubectl get pods -o wide -l app=web-ha
kubectl get pdb
4. 一个简单的故障切换验证脚本
下面这个 Bash 脚本会持续访问 Service,并打印结果,适合在你 drain 某个节点时观察业务是否出现明显中断。
#!/usr/bin/env bash
set -euo pipefail
NAMESPACE=default
SERVICE=web-ha
PORT=80
while true; do
if kubectl run curl-tmp \
--rm -i --restart=Never \
--image=curlimages/curl:8.5.0 \
--command -- \
curl -s -o /dev/null -w "%{http_code}\n" http://${SERVICE}.${NAMESPACE}.svc.cluster.local:${PORT}; then
echo "$(date '+%F %T') request ok"
else
echo "$(date '+%F %T') request failed"
fi
sleep 2
done
保存为 check-failover.sh 后执行:
chmod +x check-failover.sh
./check-failover.sh
5. 控制面健康检查脚本
这个脚本适合在每个 control plane 节点上执行,用来快速检查 apiserver、etcd、关键组件状态。
#!/usr/bin/env bash
set -euo pipefail
echo "== kube-apiserver /readyz =="
kubectl get --raw='/readyz?verbose' | head -n 30 || true
echo
echo "== component pods =="
kubectl get pods -n kube-system -o wide | egrep 'etcd|kube-apiserver|kube-controller-manager|kube-scheduler' || true
echo
echo "== nodes =="
kubectl get nodes -o wide
echo
echo "== etcd health (if etcdctl exists) =="
if command -v etcdctl >/dev/null 2>&1; then
export ETCDCTL_API=3
etcdctl \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/healthcheck-client.crt \
--key=/etc/kubernetes/pki/etcd/healthcheck-client.key \
endpoint health --cluster
else
echo "etcdctl not found"
fi
定位路径:出了故障,到底先查哪里
我比较推荐按“从外到内、从现象到根因”的顺序排查。不要一上来就翻几十页日志。
第 1 步:确认是控制面故障,还是业务面故障
先问自己两个问题:
kubectl get nodes能不能正常返回?- 集群内业务流量是不是只有部分失败?
如果 kubectl 都连不上,优先看:
- VIP / LB 是否存活
- apiserver 进程是否在
- 6443 端口是否健康
- 证书是否过期
- etcd 是否有多数派
如果 kubectl 正常,但业务访问失败,优先看:
- Service 对应 Endpoints / EndpointSlice
- Pod readiness
- CNI 网络
- kube-proxy / eBPF datapath
- 节点路由与安全组
第 2 步:检查 Node 状态与事件
kubectl get nodes
kubectl describe node <node-name>
kubectl get events -A --sort-by='.lastTimestamp' | tail -n 50
重点看:
NotReadyNetworkUnavailableDiskPressureMemoryPressure- Taint 是否导致 Pod 无法落盘
第 3 步:检查 Pod 是否真的“健康”
很多时候 Pod 是 Running,但并不代表可服务。
kubectl get pod -A -o wide
kubectl describe pod <pod-name> -n <namespace>
kubectl logs <pod-name> -n <namespace> --previous
重点看:
- readinessProbe 失败
- livenessProbe 把容器反复拉起
- OOMKilled
- ImagePullBackOff
- 节点网络问题导致探针超时
第 4 步:检查控制面组件日志
如果是 kubeadm 部署,控制面常以静态 Pod 运行:
kubectl get pod -n kube-system -o wide | egrep 'kube-apiserver|kube-controller-manager|kube-scheduler|etcd'
kubectl logs -n kube-system <apiserver-pod-name>
kubectl logs -n kube-system <etcd-pod-name>
或者直接上节点看容器运行时日志。
第 5 步:确认 etcd 是否发生 leader 抖动或丢失多数派
export ETCDCTL_API=3
etcdctl \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/healthcheck-client.crt \
--key=/etc/kubernetes/pki/etcd/healthcheck-client.key \
endpoint status --cluster -w table
关注:
- 是否所有 endpoint 都 healthy
- leader 是否频繁变化
- raft term 是否异常增长
- db size 是否过大
- 延迟是否明显升高
常见坑与排查
这里我把最容易踩的坑列出来,很多都是线上真实高频问题。
坑 1:以为 2 节点 etcd 也能高可用
这是个经典误区。
2 节点不是高可用,而是高风险。
因为 etcd 依赖多数派:
- 2 个节点,多数派是 2
- 挂 1 个就失去 quorum
建议:etcd 一定使用奇数节点,通常 3 或 5。
坑 2:LB 只做了转发,没做健康检查
你可能部署了多个 apiserver,但如果负载均衡器不会摘除故障后端,那客户端还是会不断打到坏节点。
症状:
kubectl间歇性失败- 有时成功,有时 TLS/EOF 超时
建议:
- 对 6443 做 TCP 健康检查
- 更进一步可探测
/livez或/readyz - 故障后端要自动摘除,恢复后自动加入
坑 3:Pod 有副本,但都调度到同一台节点
这在资源紧张或者没做反亲和时非常常见。
症状:
- Deployment 3 副本都在一个 worker
- 一台节点宕机,业务全没
建议:
- 配置
podAntiAffinity - 使用
topologySpreadConstraints - 跨可用区时按 zone 做拓扑分散
坑 4:PDB 配置不当,导致升级/驱逐卡死
比如只有 2 个副本,你设置了 minAvailable: 2。
这会让 drain、升级、节点维护无法继续。
建议:
- PDB 要结合副本数和维护策略设计
- 不是越严格越好
- 先明确“允许同时损失几个副本”
坑 5:Readiness 没配,故障切换看起来“慢得离谱”
如果没有 readinessProbe,Pod 可能刚启动就被加入流量;
如果探针太宽松,异常 Pod 也可能长时间不被摘掉。
建议:
- 所有对外服务都配 readinessProbe
- livenessProbe 不要过于激进
- 探针阈值要和应用启动时间匹配
坑 6:节点故障后 Pod 没迁移,以为调度器坏了
很多时候不是调度器故障,而是:
- StatefulSet 卷无法挂载到新节点
- 本地盘数据绑死
- DaemonSet 不会“迁移”
- PDB 阻止驱逐
- 节点污点与容忍配置冲突
止血方案:
- 先确认是不是无状态业务
- 必要时手工删除卡死 Pod
- 检查 PVC / StorageClass / VolumeAttachment
- 临时放宽 PDB 或修正污点策略
坑 7:etcd 磁盘慢,控制面像“时好时坏”
这个问题很隐蔽。
控制面不是完全挂掉,而是偶发超时、leader 切换、写入慢。
排查方向:
- 磁盘 IOPS
- fsync 延迟
- etcd compaction / defrag 是否长期没做
- 是否与其他高 IO 任务混部
建议:
- etcd 使用低延迟 SSD
- 避免与重 IO 业务混部
- 定期 compact / defrag
- 做磁盘与延迟监控
止血方案:线上已经出问题了,先怎么保业务
这部分是 troubleshooting 场景里很关键的一段。
理想很重要,但线上先恢复服务更重要。
情况 1:单个 apiserver 故障
操作建议:
- 确认 LB 已摘除故障节点
- 检查该节点 kubelet、容器运行时、证书、磁盘
- 恢复后再重新加入流量池
不要做的事:
- 未确认原因就重启所有控制面节点
- 在 etcd 不稳定时同时操作多个 control plane
情况 2:单个 etcd 成员故障,但集群仍有 quorum
操作建议:
- 先确认剩余 etcd 成员健康
- 检查故障节点网络、磁盘、证书
- 如数据目录损坏,按官方流程移除并重新加入成员
- 先稳住 quorum,再谈修复
原则:
- 3 节点 etcd 挂 1 个,还能撑住
- 但这时已经没有冗余,不要再动第二个
情况 3:worker 故障,业务副本不足
止血动作:
kubectl cordon故障节点,防止新 Pod 再调度过去- 若节点已不可恢复,删除卡死 Pod
- 临时扩容 Deployment 副本
- 若资源不足,优先保障核心服务的 requests/priorityClass
情况 4:LB/VIP 出问题,控制面其实没挂
这是很容易误判为“集群挂了”的情况。
检查命令:
nc -zv <vip-or-lb> 6443
curl -k https://<vip-or-lb>:6443/livez
再分别登录各 control plane 节点:
curl -k https://127.0.0.1:6443/livez
如果节点本地正常、VIP 不通,那问题在 LB 层,不在 Kubernetes 本身。
安全/性能最佳实践
高可用如果只追求“不停机”,但把安全和性能弄丢了,最后还是会反噬运维成本。
安全最佳实践
1. etcd 必须启用 TLS
etcd 是集群大脑,证书、密钥、快照都要严管。
不要裸奔,更不要把客户端证书散落到普通节点。
2. 最小化控制面暴露面
- 6443 仅开放给必要来源
- etcd 端口只允许控制面内部访问
- 使用安全组 / 防火墙限制管理面网络
3. 定期轮换证书并监控过期时间
证书过期是控制面“突然死亡”的高危原因之一。
特别是 kubeadm 集群,要定期检查:
kubeadm certs check-expiration
4. 对 RBAC 做权限收敛
很多排障脚本习惯性用 cluster-admin,但生产环境建议细化权限,避免误操作扩大影响面。
性能最佳实践
1. etcd 资源不要抠得太狠
- 独占高性能磁盘优先
- 预留足够 CPU 和内存
- 避免控制面节点混跑重负载业务
2. 监控这几个关键指标
建议至少纳入告警:
- apiserver 请求延迟、5xx
- etcd leader changes
- etcd fsync duration
- node not ready
- pod pending 数量
- CoreDNS 延迟和错误率
3. 控制故障切换阈值,不要一味调太激进
有些团队为了“秒切”,把各种超时调得很低,结果网络轻微抖动就频繁误判。
高可用不是只看快,还要看稳定。
4. 尽量做跨节点、跨可用区部署
如果业务确实关键,建议至少做到:
- 副本跨节点
- 核心服务跨 zone
- 存储与网络方案支持跨故障域恢复
一份建议的验证清单
上线前,我建议至少做下面这些演练。只要你真做过一遍,心里会踏实很多。
- 关停单个 worker,确认业务副本自动恢复
- drain 单个 worker,确认 PDB 不会阻塞维护
- 停掉单个 apiserver,确认 kubectl 基本无感
- 停掉单个 controller-manager / scheduler,确认 leader 可切换
- 停掉单个 etcd 成员,确认 quorum 正常
- 模拟 VIP/LB 后端摘除,确认健康检查生效
- 检查证书剩余有效期
- 验证 etcd 快照可恢复
- 验证监控与告警是否真正触发
总结
Kubernetes 的高可用,真正难的不是“搭一个三节点控制面”,而是把这些链路补齐:
- API 入口高可用:VIP / LB + 健康检查
- 控制组件高可用:多实例 + leader election
- 状态存储高可用:etcd 奇数节点 + 多数派
- 业务副本高可用:反亲和、拓扑分散、健康检查、PDB
- 排障与演练能力:明确定位路径,提前做故障演习
如果你让我给一套最实用的落地建议,我会这样总结:
- 生产集群至少 3 control plane,不要把 etcd 做成 2 节点。
- 前置做 LB 健康检查,不然多 apiserver 没意义。
- 业务层必须加 readiness、反亲和、拓扑分散。
- 把 etcd 当成重点保护对象,优先保障磁盘和网络质量。
- 定期演练故障切换,不演练的高可用,基本等于没验证。
最后说句很实在的话:
高可用不是“永不故障”,而是故障来了你知道会怎么坏、坏到什么程度、多久能恢复。
只要这三件事你心里有数,这套 Kubernetes 集群就算真的具备生产级韧性了。