背景与问题
很多团队一开始搭 Kubernetes,都默认“能跑就行”:1 个 control plane、1 套 etcd、API Server 前面随便挂个地址。开发测试环境这么做问题不大,但一旦进入生产,故障就会非常“直接”:
- 单个控制平面节点宕机,
kubectl全部超时 - etcd 磁盘打满或网络抖动,整个集群状态写不进去
- kube-apiserver 实例虽然有多个,但客户端没有统一入口,切换靠人工
- controller-manager / scheduler 看似多副本,实际上没有正确 leader election,切换不稳定
- 误以为“worker 节点还在跑业务,就算高可用”,结果扩缩容、发布、证书轮换全卡住
这类问题有个共性:数据面还能勉强活,控制面已经失去可靠性。
这篇文章不从“理想架构图”出发,而是从故障排查和止血的角度,带你把 Kubernetes 高可用的关键设计串起来:
- 控制平面怎么做冗余
- etcd 为什么是 HA 设计里的核心
- 故障切换链路应该怎么设计
- 出问题时怎么快速判断是 LB、API Server、etcd,还是选主异常
如果你已经有一定 Kubernetes 使用经验,但还没完整搭过一套真正抗故障的控制平面,这篇会比较适合。
背景中的典型故障现象
先看几类线上最常见的“报警语气很像,但根因完全不同”的问题。
现象 1:kubectl 偶发超时
kubectl get nodes
Unable to connect to the server: dial tcp 10.0.0.10:6443: i/o timeout
可能原因:
- 负载均衡 VIP 或四层代理异常
- 某个 API Server 实例假活着,健康检查没摘除
- 控制平面节点网络丢包
- 防火墙误封 6443
现象 2:kubectl 能查不能改
kubectl get pods -A
# 正常
kubectl apply -f deploy.yaml
# 卡住或报:
etcdserver: request timed out
这类问题很像 API Server 故障,但很多时候根因在 etcd 写入异常:
- etcd leader 所在节点 I/O 延迟高
- WAL 所在磁盘性能差
- etcd quorum 被破坏
- 时钟漂移导致选主频繁
现象 3:控制平面节点宕机后,集群“半死不活”
常见表现:
- 业务 Pod 继续跑
- 新 Pod 调度不上去
- HPA、Job、CronJob 行为异常
- 控制器更新状态滞后
这通常意味着:
- kube-scheduler / kube-controller-manager 没有平稳切主
- API Server 虽然存活,但后端 etcd 不稳定
- 某些静态 Pod 已经退出但没有被及时拉起
核心原理
要真正理解 Kubernetes 高可用,最重要的是先分清楚:谁是无状态,谁是有状态,谁依赖多数派。
1. 控制平面的 HA,不只是“多起几个组件”
Kubernetes 控制平面主要包括:
kube-apiserverkube-controller-managerkube-scheduleretcd
其中:
kube-apiserver:无状态,可水平扩展kube-controller-manager:多实例部署,但同一时刻通常只有 leader 工作kube-scheduler:同样依赖 leader electionetcd:强一致状态存储,依赖 quorum,多数派存活才可写
所以高可用设计不是“每个组件都做双机”这么简单,而是:
- API Server 多实例 + 统一入口
- controller-manager / scheduler 正确启用选主
- etcd 保证奇数节点、多数派、低延迟网络和稳定磁盘
- 各层健康检查和故障摘除要能串起来
2. 为什么 etcd 是整个 HA 设计的“地基”
很多人刚接触时会把 etcd 当成一个普通数据库,但它的角色更像是:
Kubernetes 的一致性状态账本
所有关键对象——Pod、Node、Lease、ConfigMap、Secret、EndpointSlice——本质上都要落到 etcd。
只要 etcd 出问题,症状就会蔓延到整个控制平面:
- API Server 读写延迟升高
- leader election 不稳定
- Node 心跳更新异常
- 控制器 reconcile 失败
- 新对象创建卡顿
etcd 的几个关键事实
- 推荐使用 3 节点或 5 节点
- 不能用 2 节点做生产 HA,因为 2 节点只要挂 1 个就失去多数派
- 写请求必须由 leader 提交并复制到多数派
- 跨机房部署如果网络延迟高,会直接拉高提交延迟
- 磁盘抖动比 CPU 高更容易引发 etcd 不稳定
3. 故障切换链路的正确理解
一条完整的切换链路通常是:
- 某个 control plane 节点故障
- LB 健康检查失败,摘除该节点的
6443 - 客户端流量切到其他 API Server
- 如果故障节点恰好承载 scheduler / controller-manager leader,则其余实例重新选主
- 如果故障节点还是 etcd 成员,要看是否还保有 quorum
- 若 quorum 还在,集群继续工作;若 quorum 丢失,控制面进入只读甚至不可用
这个链路里,最怕的不是单点故障,而是假健康和脑裂误判。
高可用参考架构
下面给一个比较常见、也比较稳的生产形态:3 个控制平面节点,每个节点本地运行 API Server、scheduler、controller-manager 和 etcd;前面再挂一个统一 VIP 或 LB。
flowchart TD
U[运维/CI/kubectl] --> LB[VIP / Load Balancer:6443]
LB --> CP1[control-plane-1<br/>apiserver<br/>scheduler<br/>controller-manager<br/>etcd]
LB --> CP2[control-plane-2<br/>apiserver<br/>scheduler<br/>controller-manager<br/>etcd]
LB --> CP3[control-plane-3<br/>apiserver<br/>scheduler<br/>controller-manager<br/>etcd]
CP1 <-->|Raft| CP2
CP2 <-->|Raft| CP3
CP1 <-->|Raft| CP3
CP1 --> W1[worker-1]
CP2 --> W2[worker-2]
CP3 --> W3[worker-3]
这个方案的特点:
- API Server 是多副本无状态入口
- scheduler / controller-manager 依赖 leader election
- etcd 为 3 节点嵌入式或同机部署
- 单个控制平面节点故障时,理论上仍可继续服务
但它也有边界:
- 只适合同城低延迟网络
- etcd 对磁盘和时钟要求高
- 不建议把 3 个 etcd 节点跨 3 个远距离机房乱拉开
故障切换时序
下面这张图更能说明“出问题之后,到底谁先反应”。
sequenceDiagram
participant C as Client/kubectl
participant LB as Load Balancer
participant A1 as API Server-1
participant A2 as API Server-2
participant E as etcd Cluster
participant S as Scheduler/Controller Leader Election
C->>LB: 请求 6443
LB->>A1: 转发请求
A1->>E: 读写集群状态
Note over A1: control-plane-1 宕机
LB->>A1: 健康检查失败
LB-->>C: 摘除 A1 后重试
C->>LB: 新请求
LB->>A2: 转发请求
A2->>E: 继续读写
Note over S: 若 leader 位于故障节点
S->>S: 重新选主
S-->>A2: 恢复调度/控制循环
方案设计要点
控制平面冗余
API Server
建议:
- 至少 2 个,生产常见为 3 个
- 前面使用四层 LB 或 VIP
- 健康检查不要只探端口,优先探活
/livez或/readyz
controller-manager / scheduler
要点:
- 多实例部署
- 启用 leader election
- 使用稳定的 Lease 机制
- 不要手动改得过于激进,否则轻微抖动就频繁切主
etcd 容灾
节点数选择
- 3 节点:最常见,容忍 1 节点故障
- 5 节点:适合更高容灾要求,但写放大更明显
- 7 节点:一般不建议,收益小于复杂度
拓扑建议
- 优先同可用区、低延迟网络
- 若跨 AZ,确保 RTT 足够低,且链路稳定
- 不要把 etcd 和高抖动 I/O 业务混跑在同一磁盘
备份与恢复
必须具备:
- 定期
snapshot save - 定期验证
snapshot status - 明确“单节点恢复”和“整簇重建”流程
- 恢复时清楚
initial-cluster、initial-cluster-state、data-dir的关系
实战代码(可运行)
下面给一套偏实战的命令,适合你在自建 kubeadm 集群中排查和验证高可用链路。
假设:
- LB VIP:
10.0.0.10:6443- control plane 节点:
10.0.0.1110.0.0.1210.0.0.13
1. 用 HAProxy 做 API Server 四层负载均衡
HAProxy 配置
global
log /dev/log local0
log /dev/log local1 notice
daemon
maxconn 4096
defaults
log global
mode tcp
option tcplog
timeout connect 5s
timeout client 60s
timeout server 60s
frontend k8s_api_frontend
bind 0.0.0.0:6443
default_backend k8s_api_backend
backend k8s_api_backend
mode tcp
option tcp-check
balance roundrobin
default-server inter 3s fall 3 rise 2
server cp1 10.0.0.11:6443 check
server cp2 10.0.0.12:6443 check
server cp3 10.0.0.13:6443 check
启动 HAProxy:
sudo haproxy -f /etc/haproxy/haproxy.cfg -c
sudo systemctl restart haproxy
sudo systemctl enable haproxy
验证 VIP 是否可达:
nc -vz 10.0.0.10 6443
curl -k https://10.0.0.10:6443/livez
如果你前面是纯四层 LB,
/livez的 HTTP 健康检查通常由更高级别代理完成;四层设备常见做法是 TCP 探测。但在 Kubernetes 控制平面场景下,仅 TCP 成功并不代表 apiserver 真正 ready,这是个经典坑。
2. 查看 kubeadm 高可用初始化配置
apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
kubernetesVersion: v1.28.0
controlPlaneEndpoint: "10.0.0.10:6443"
networking:
podSubnet: "10.244.0.0/16"
apiServer:
certSANs:
- "10.0.0.10"
- "k8s-api.internal"
etcd:
local:
dataDir: /var/lib/etcd
---
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
mode: "ipvs"
初始化首个控制平面节点:
sudo kubeadm init --config kubeadm-ha.yaml --upload-certs
加入后续控制平面节点:
sudo kubeadm join 10.0.0.10:6443 \
--token <token> \
--discovery-token-ca-cert-hash sha256:<hash> \
--control-plane \
--certificate-key <certificate-key>
检查节点:
kubectl get nodes -o wide
kubectl get pods -n kube-system -o wide
3. 检查 etcd 集群健康
在任一控制平面节点上执行:
export ETCDCTL_API=3
etcdctl \
--endpoints=https://127.0.0.1:2379 \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/peer.crt \
--key=/etc/kubernetes/pki/etcd/peer.key \
endpoint health
etcdctl \
--endpoints=https://127.0.0.1:2379 \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/peer.crt \
--key=/etc/kubernetes/pki/etcd/peer.key \
endpoint status --write-out=table
查看成员信息:
etcdctl \
--endpoints=https://127.0.0.1:2379 \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/peer.crt \
--key=/etc/kubernetes/pki/etcd/peer.key \
member list --write-out=table
4. 制作 etcd 快照
export ETCDCTL_API=3
etcdctl snapshot save /backup/etcd-snapshot-$(date +%F-%H%M%S).db \
--endpoints=https://127.0.0.1:2379 \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/peer.crt \
--key=/etc/kubernetes/pki/etcd/peer.key
校验快照:
etcdctl snapshot status /backup/etcd-snapshot-2025-01-01-120000.db --write-out=table
5. 故障切换演练脚本
下面这个脚本持续访问 API Server,同时记录延迟和状态,适合演练“干掉某个 control plane 节点后,VIP 切换是否平稳”。
#!/usr/bin/env bash
set -euo pipefail
API_SERVER="https://10.0.0.10:6443/readyz"
CA="/etc/kubernetes/pki/ca.crt"
for i in $(seq 1 60); do
ts=$(date '+%F %T')
code=$(curl -s -o /tmp/k8s-readyz.out -w "%{http_code}" --cacert "${CA}" "${API_SERVER}" || true)
body=$(tr '\n' ' ' < /tmp/k8s-readyz.out 2>/dev/null || true)
echo "${ts} code=${code} body=${body}"
sleep 2
done
运行:
chmod +x check-apiserver.sh
./check-apiserver.sh
此时你可以在某个控制平面节点上模拟故障,例如:
sudo systemctl stop kubelet
或者更直接一些(实验环境):
sudo shutdown -h now
观察:
- VIP 是否快速切换
kubectl get nodes是否只短暂抖动kube-system中 scheduler / controller-manager leader 是否重新选主
6. 通过 Lease 观察 leader election
kubectl get lease -A
kubectl get lease -n kube-system
kubectl describe lease kube-controller-manager -n kube-system
kubectl describe lease kube-scheduler -n kube-system
如果某个 leader 在宕机后长期不释放,或者 Lease 更新明显滞后,通常说明:
- API Server 到 etcd 写入慢
- 节点时钟问题
- 控制平面压力过高
现象复现、定位路径与止血方案
troubleshooting 文章最怕只讲概念,不讲定位路径。下面给一个我自己常用的排查顺序:先入口,再控制面,再存储,一路缩圈。
场景 1:VIP 可达,但 kubectl 非常慢
定位路径
先测入口:
curl -k https://10.0.0.10:6443/livez
curl -k https://10.0.0.10:6443/readyz?verbose
再分别直连每个 API Server:
for ip in 10.0.0.11 10.0.0.12 10.0.0.13; do
echo "==== $ip ===="
curl -k --connect-timeout 2 https://$ip:6443/livez || true
done
检查 kube-apiserver 日志:
sudo crictl ps | grep kube-apiserver
sudo crictl logs <container-id> | tail -n 100
如果日志中出现:
etcdserver: request timed outfailed to revoke leasestorage backend is unhealthy
那就不要继续在 LB 上打转了,往 etcd 查。
止血方案
- 先从 LB 摘除异常 API Server 实例
- 降低控制平面上的非必要操作,例如大规模 apply / controller 风暴
- 优先确认 etcd leader 所在节点是否磁盘抖动
场景 2:Node 状态频繁 NotReady,但业务没全挂
定位路径
kubectl get nodes
kubectl describe node <node-name>
kubectl get events -A --sort-by=.lastTimestamp | tail -n 50
看 kubelet 侧:
journalctl -u kubelet -n 100 --no-pager
再看 etcd 是否健康:
ETCDCTL_API=3 etcdctl \
--endpoints=https://127.0.0.1:2379 \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/peer.crt \
--key=/etc/kubernetes/pki/etcd/peer.key \
endpoint status --write-out=table
常见根因
- apiserver 与 etcd 之间读写抖动
- Node Lease 更新不及时
- 某个 control plane 节点负载过高
- 时间同步异常
止血方案
- 先修时间同步:
chronyd/systemd-timesyncd - 排查控制平面 CPU steal、磁盘 await、网络丢包
- 确保 etcd 数据盘不是共享慢盘
场景 3:控制平面节点宕机后,集群无法写入
先判断有没有丢 quorum
3 节点 etcd 中挂掉 1 个,理论上仍应可写;如果不可写,通常说明剩余节点里还有隐患。
检查:
ETCDCTL_API=3 etcdctl \
--endpoints=https://10.0.0.11:2379,https://10.0.0.12:2379,https://10.0.0.13:2379 \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/peer.crt \
--key=/etc/kubernetes/pki/etcd/peer.key \
endpoint health
如果只剩 1 个 healthy,说明多数派已经没了。
止血方案
- 优先恢复故障节点网络/磁盘/进程
- 不要在慌乱中随便
member remove - 如果必须恢复自快照,先冻结变更,按整套恢复流程走
- 在未确认数据一致性前,不建议手工拼凑 etcd 成员
常见坑与排查
坑 1:把 2 节点 etcd 当高可用
这是非常典型的误区。
2 节点看起来有冗余,但 etcd 的 quorum 机制决定了:
- 2 个节点里多数派是 2
- 挂 1 个后只剩 1,不满足多数派
- 结果是读可能部分可用,写基本不可用
结论:生产别这么做。
坑 2:LB 只做 TCP 健康检查,导致“假活”
API Server 进程在,TCP 能连,不代表请求一定能处理成功。
我踩过一次:某台 control plane 的 apiserver 实例实际上已经被 etcd 拖慢到超时,但端口还是开的,LB 一直把流量打过去,表现就是“偶发失败且很玄学”。
建议:
- 至少对 API Server 做
readyz检查 - 若前置设备不支持 HTTP 探测,缩短失败摘除时间,并配合上层监控
坑 3:etcd 磁盘和业务混跑
尤其是虚拟化环境里,把 etcd 放在共享存储或低质量云盘上,非常容易出现:
- fsync 延迟高
- leader 频繁切换
- API Server 请求堆积
排查命令:
iostat -x 1 10
vmstat 1 10
dmesg | tail -n 50
重点看:
await%util- 是否有 I/O error、文件系统告警
坑 4:跨机房强行拉 etcd
etcd 不是“容器跑起来就行”的组件,它对网络延迟真的敏感。
跨城市甚至跨区域部署 3 节点 etcd,经常会引发:
- 提交延迟高
- leader 漂移
- 大量超时重试
经验建议:
- etcd 节点之间 RTT 尽量低
- 跨 AZ 可以做,但要充分测延迟
- 跨 Region 不建议做单一 Raft 集群
坑 5:恢复 etcd 时忽略 member 信息
快照恢复不是单纯把文件拷回去。你必须明确:
- 新的数据目录是否已清空
- 这是恢复到新集群还是原集群
initial-cluster是否匹配- 静态 Pod manifest 是否同步调整
这一类错误最容易把恢复事故扩大成二次事故。
安全/性能最佳实践
高可用不只是“不宕机”,还包括可控、可恢复、可观测。
安全最佳实践
1. 控制平面证书统一规划
- API Server 证书 SAN 中要包含 VIP / LB 域名
- 证书轮换流程要做演练
- 不要让节点间手工复制证书变成长期常态
2. etcd 通信全链路 TLS
- client cert 与 peer cert 分开管理
- 最小化证书权限范围
- 定期检查到期时间
检查证书有效期:
openssl x509 -in /etc/kubernetes/pki/apiserver.crt -noout -dates
openssl x509 -in /etc/kubernetes/pki/etcd/peer.crt -noout -dates
3. 限制控制平面访问面
- 6443 只对运维网段、节点网段开放
- etcd 2379/2380 不对业务网段暴露
- 审计日志要开启,方便回溯异常操作
性能最佳实践
1. etcd 用 SSD,本地盘优先
这是收益最大的优化之一。
etcd 对 WAL/fsync 很敏感,磁盘慢,整个控制面都会“闷”。
2. 控制平面与高噪声业务隔离
如果控制平面节点上同时跑了高 I/O、突发 CPU 任务,故障往往不是“挂”,而是“很慢但没完全死”,这种最难排查。
3. 避免对象风暴
例如:
- 大量短生命周期 Job
- 高频更新 ConfigMap/Secret
- 控制器误循环导致大量 patch
这些都会加大 API Server 和 etcd 压力。
4. 监控这些核心指标
建议至少接 Prometheus 监控以下指标:
- API Server 请求延迟、5xx 比例
- etcd leader changes
- etcd fsync duration
- etcd backend commit duration
- scheduler pending pods
- controller-manager workqueue 深度
- Node Lease 更新异常
一张故障判断图
出问题时,先别慌着重启。按下面这个思路判断,通常能少走很多弯路。
flowchart TD
A[kubectl 超时/集群异常] --> B{VIP:6443 是否可达}
B -- 否 --> C[排查 LB/VIP/防火墙/路由]
B -- 是 --> D{单个 apiserver 是否可直连}
D -- 否 --> E[排查节点进程/静态 Pod/kubelet]
D -- 是 --> F{apiserver readyz 是否正常}
F -- 否 --> G[查看 apiserver 日志]
F -- 是 --> H{etcd endpoint health 是否正常}
H -- 否 --> I[排查 quorum/磁盘/网络/时钟]
H -- 是 --> J[检查 scheduler/controller leader election]
J --> K[观察 Lease、控制器日志、调度延迟]
建议的演练清单
如果你已经有一套高可用控制平面,我建议至少做下面这些演练,而不是停留在“架构图看起来没问题”。
演练 1:单个 API Server 进程退出
目标:
- LB 能否正确摘除
- 客户端请求是否仅短暂抖动
演练 2:单个 control plane 节点宕机
目标:
- scheduler / controller-manager 是否重新选主
- 新 Pod 能否继续调度
演练 3:单个 etcd 节点不可用
目标:
- quorum 是否仍正常
- API 写入延迟是否在可接受范围内
演练 4:etcd 快照恢复到测试环境
目标:
- 验证备份可用性
- 熟悉恢复步骤
- 明确恢复时间目标(RTO)
演练 5:证书即将过期处理
目标:
- 确认证书轮换流程
- 避免“控制面全活着但谁也连不上”的尴尬故障
总结
Kubernetes 高可用真正要守住的,不是“组件数量”,而是这三条线:
- 入口线:API Server 多副本 + 正确健康检查 + 统一访问入口
- 控制线:scheduler / controller-manager 正常选主,故障后能平稳切换
- 状态线:etcd 保持 quorum、低延迟、可备份、可恢复
如果只让我给几个最实用的落地建议,我会给这几条:
- 生产环境控制平面优先用 3 节点
- etcd 优先 3 节点奇数部署,不要搞 2 节点“伪 HA”
- API Server 前一定放统一入口,且别只做 TCP 健康检查
- 定期做 etcd snapshot + 恢复演练
- 监控重点盯住 etcd 延迟、leader 变化、API Server readyz、Lease 更新
- 出故障时按 LB → API Server → etcd → leader election 的顺序缩圈排查
最后再强调一个边界条件:
高可用不是无限可用。
如果你只有 3 节点 etcd,就只能容忍 1 个节点故障;如果网络、磁盘、时钟三者同时出问题,再漂亮的架构也救不了现场。所以比“搭出来”更重要的是:你是否真的演练过它在坏掉时会怎么表现。
这一步,决定了你的 Kubernetes 集群是“看起来高可用”,还是“真的扛得住事故”。