背景与问题
很多团队在把 Kubernetes 用到生产环境后,最先暴露出来的问题往往不是业务 Pod,而是控制平面本身不够稳。
常见场景我见过不少:
- 单个 master 节点宕机,整个集群无法调度
- kube-apiserver 看起来还活着,但
kubectl请求间歇性超时 - etcd 有节点掉线后,控制面“半死不活”
- 前面挂了一个负载均衡,但健康检查配置不对,故障切换形同虚设
- 证书、时钟、DNS、VIP 漂移等细节问题,导致排查非常痛苦
这类问题麻烦的点在于:控制平面故障通常不是“全挂”,而是“部分可用”。
也就是说,最难排查的是“偶发失败、局部失败、切换不彻底”。
本文我会从 troubleshooting 的角度来写,不只讲“标准架构长什么样”,更重点讲:
- 高可用控制平面的核心设计原则
- 故障是怎么切换的
- 出问题后应该从哪条路径定位
- 有哪些可以直接跑起来的实战配置
本文默认读者已经知道 Kubernetes 基本组件:kube-apiserver、controller-manager、scheduler、etcd。
背景架构:高可用控制平面到底在保什么
Kubernetes 控制平面的高可用,核心不是“多起几个组件”这么简单,而是保证这几件事:
- API Server 至少始终有一个实例可用
- etcd 始终保持法定多数(quorum)
- controller-manager / scheduler 通过 leader election 保证只有一个主动工作者
- 客户端始终通过稳定入口访问控制平面,而不是绑死单节点
可以先看一个典型拓扑。
flowchart TB
U[kubectl / CI / Operators] --> LB[Load Balancer / VIP]
LB --> A1[kube-apiserver-1]
LB --> A2[kube-apiserver-2]
LB --> A3[kube-apiserver-3]
A1 --> E1[(etcd-1)]
A2 --> E2[(etcd-2)]
A3 --> E3[(etcd-3)]
CM1[kube-controller-manager-1] --> LB
CM2[kube-controller-manager-2] --> LB
CM3[kube-controller-manager-3] --> LB
S1[kube-scheduler-1] --> LB
S2[kube-scheduler-2] --> LB
S3[kube-scheduler-3] --> LB
这里有两个很容易被忽视的点:
1. API Server 是“无状态近似服务”,但 etcd 不是
kube-apiserver 实例可以多副本部署,前面挂负载均衡即可。
但 etcd 是强一致分布式存储,它不是“副本越多越好”。
生产里常见推荐是 3 节点或 5 节点 etcd 集群:
- 3 节点:容忍 1 个故障
- 5 节点:容忍 2 个故障
- 2 节点:不推荐,没有正常 quorum 意义
- 4 节点:通常也不划算,写入成本更高,容错能力不比 3 节点强太多
2. 控制器和调度器高可用靠的是 leader election
kube-controller-manager 和 kube-scheduler 可以多实例跑,但同一时刻通常只会有一个 leader 真正执行业务逻辑,其余是 standby。
所以它们的故障切换,更多是:
- 老 leader 失联
- lease 过期
- 新 leader 抢占成功
- 继续工作
这就决定了:
你看到的“切换延迟”,很多时候不是负载均衡问题,而是 leader election 参数和 apiserver 可达性问题。
核心原理
这一部分不讲太虚,我只抓和排障最相关的三个机制。
1. API Server 高可用:入口稳定 + 后端多活
客户端不应该直接连某一个 control-plane 节点,而应该连接一个统一入口,比如:
- 硬件 SLB
- HAProxy + Keepalived
- 云厂商内网 CLB / NLB
- kube-vip
如果客户端 kubeconfig 中写死了某个 IP,那么即使你搭了三台 master,也不算真正高可用。
请求链路
sequenceDiagram
participant Client as kubectl/client
participant LB as LB/VIP
participant API1 as kube-apiserver-1
participant API2 as kube-apiserver-2
participant ETCD as etcd cluster
Client->>LB: HTTPS request
LB->>API1: Forward request
API1->>ETCD: Read/Write state
ETCD-->>API1: Response
API1-->>LB: API result
LB-->>Client: Return response
Note over LB,API2: If API1 unhealthy, traffic switches to API2/API3
切换是否成功,取决于三层
- LB 能否识别后端健康
- 后端 API Server 是否真的可用
- API Server 到 etcd 的路径是否正常
很多排障误区在于:
LB 认为 apiserver 健康,但 apiserver 连 etcd 超时,此时 /readyz 和简单 TCP 探测结果可能完全不同。
2. etcd 高可用:依赖 quorum,不是依赖“节点在线数”
etcd 是 Raft 协议,判断是否可写不是看“还剩几台活着”,而是看是否保有多数派。
etcd 状态切换简图
stateDiagram-v2
[*] --> LeaderElected
LeaderElected --> HealthyQuorum: majority alive
HealthyQuorum --> Degraded: one member lost
Degraded --> HealthyQuorum: member recovered
Degraded --> NoQuorum: majority lost
NoQuorum --> ReadOnlyOrFail: API write fails / cluster unstable
ReadOnlyOrFail --> [*]
3 节点 etcd 集群中:
- 3 台都活:正常
- 挂 1 台:还能工作
- 再挂 1 台:多数派丢失,基本不可写
因此,控制平面很多“无法创建资源”的根因,其实是 etcd 失去 quorum,而不是 apiserver 进程挂了。
3. leader election:控制器和调度器的“接力棒”
controller-manager 和 scheduler 会在 Kubernetes 中创建 lease / endpoint / configmap 锁(现代版本通常是 Lease)。
切换过程大致是:
- 当前 leader 持续 renew lease
- leader 宕机或网络中断
- renew 超时
- 其他副本竞争 lease
- 新 leader 接管
常见相关参数:
--leader-elect=true--leader-elect-lease-duration--leader-elect-renew-deadline--leader-elect-retry-period
如果参数太保守,切换慢;太激进,网络抖动时容易频繁切主。
现象复现:如何模拟控制平面故障切换
如果你想在测试环境确认设计是否靠谱,建议真的做一遍故障演练。
别只看“节点 Ready”,要看业务操作是否连续可用。
这里给一个最小化演练路径:
- 三节点 control-plane
- 前置 HAProxy 或 kube-vip
kubectl指向 VIP- 持续执行 API 请求
- 人为停掉某个 apiserver 或直接关闭 control-plane 节点
- 观察请求中断时间和恢复时间
实战代码(可运行)
下面用 HAProxy + Keepalived + kubeadm 风格控制平面 举一个可直接改造的例子。
如果你在云上,也可以把 HAProxy/Keepalived 换成云 LB,但排障思路一样。
1. HAProxy 配置:为 kube-apiserver 提供统一入口
文件:/etc/haproxy/haproxy.cfg
global
log /dev/log local0
log /dev/log local1 notice
daemon
maxconn 4000
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
balance roundrobin
option tcp-check
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
加载并验证:
sudo haproxy -c -f /etc/haproxy/haproxy.cfg
sudo systemctl enable haproxy --now
sudo systemctl status haproxy
这里用的是 TCP 检查,简单但不够“懂业务”。
如果你要更严格,建议结合/readyz做上层探测,或者使用支持 HTTPS health check 的 LB。
2. Keepalived 配置:给 HAProxy 提供 VIP
主节点配置 /etc/keepalived/keepalived.conf:
vrrp_script chk_haproxy {
script "pidof haproxy"
interval 2
weight 20
}
vrrp_instance VI_1 {
state MASTER
interface eth0
virtual_router_id 51
priority 120
advert_int 1
authentication {
auth_type PASS
auth_pass 12345678
}
virtual_ipaddress {
10.0.0.100/24
}
track_script {
chk_haproxy
}
}
备节点只需要改这几个值:
state BACKUPpriority 110/100- 其他保持一致
启动:
sudo systemctl enable keepalived --now
ip addr show eth0
如果看到 10.0.0.100 漂在某台机器上,说明 VIP 正常。
3. kubeconfig 指向 VIP,而不是单个节点
验证当前 kubeconfig:
grep server ~/.kube/config
理想状态类似:
server: https://10.0.0.100:6443
如果这里写的是某个具体 control-plane IP,比如 10.0.0.11:6443,那么你的“高可用”大概率只是表面高可用。
4. 持续压测 API 可用性
准备一个简单脚本,持续请求 apiserver。
文件:watch-api.sh
#!/usr/bin/env bash
set -euo pipefail
while true; do
ts=$(date '+%F %T')
if kubectl get --raw=/readyz >/dev/null 2>&1; then
echo "$ts API ready"
else
echo "$ts API failed"
fi
sleep 1
done
执行:
chmod +x watch-api.sh
./watch-api.sh
5. 模拟单个 apiserver 宕机
如果 apiserver 是静态 Pod,可以在对应 control-plane 节点上临时移走 manifest:
sudo mv /etc/kubernetes/manifests/kube-apiserver.yaml /root/
sleep 20
sudo mv /root/kube-apiserver.yaml /etc/kubernetes/manifests/
或者直接停节点网络/关机做更真实演练。
观察前面的 watch-api.sh 输出。
一个健康的高可用控制平面,应该允许短暂抖动,但不应长时间不可用。
6. 检查 etcd 集群健康
在任意 etcd 节点执行:
export 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 status --write-out=table
查看健康性:
export 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
如果这里已经超时或报证书错误,那 apiserver 的异常基本就有方向了。
7. 查看 leader election 是否正常
看 scheduler 与 controller-manager 的租约:
kubectl -n kube-system get lease
查看详细信息:
kubectl -n kube-system describe lease kube-scheduler
kubectl -n kube-system describe lease kube-controller-manager
如果 lease 更新卡住、holderIdentity 频繁变化,通常意味着:
- apiserver 不稳定
- 节点时钟有偏差
- 网络抖动严重
- election 参数过于激进
定位路径:控制平面故障怎么一步一步查
这是我比较推荐的排障顺序。
别一上来就看日志翻半天,先按依赖链路查。
排查总路径
flowchart TD
A[kubectl 超时/失败] --> B{VIP/LB 是否可达}
B -- 否 --> C[检查 VIP 漂移/HAProxy/Keepalived/LB 配置]
B -- 是 --> D{6443 后端是否健康}
D -- 否 --> E[检查 kube-apiserver 进程/静态 Pod/证书]
D -- 是 --> F{apiserver 到 etcd 是否正常}
F -- 否 --> G[检查 etcd quorum/磁盘/网络/证书]
F -- 是 --> H{是否是 leader election 或局部控制器异常}
H -- 是 --> I[检查 lease、时钟、网络抖动]
H -- 否 --> J[进一步查 DNS / CNI / admission webhook]
常见坑与排查
下面这些坑,真的是高频。
坑 1:LB 健康检查太“浅”,把坏掉的 apiserver 当成健康
现象
telnet VIP 6443能通kubectl get pods偶发超时- HAProxy 显示后端都在线
- apiserver 日志里反复出现 etcd 超时
根因
TCP 端口通,不代表 API 真正 ready。
一个 apiserver 进程在监听 6443,但它连不上 etcd、admission 卡死、内部队列阻塞时,客户端仍然会感知失败。
排查
kubectl get --raw=/livez?verbose
kubectl get --raw=/readyz?verbose
如果通过 VIP 不稳定,也可以在每个控制平面节点本地查:
curl -k https://127.0.0.1:6443/readyz
止血方案
- 让 LB 尽可能基于 readiness 做探测
- 降低坏实例留在后端池中的时间
- 短期内手动摘除异常 apiserver 节点
坑 2:etcd 没有 quorum,但表面上还有节点活着
现象
kubectl get有时还能读kubectl apply、创建 Pod、更新 Deployment 失败- apiserver 日志报:
etcdserver: request timed outcontext deadline exceededleader changed
排查
etcdctl endpoint health
etcdctl endpoint status --write-out=table
再看成员:
etcdctl member list --write-out=table
止血方案
- 先恢复多数派节点网络/主机
- 不要在未确认情况下贸然
member remove - 如果必须重建,先做快照
备份示例:
export 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 \
snapshot save /backup/etcd-$(date +%F-%H%M%S).db
坑 3:kubeconfig 仍然指向单节点 IP
现象
- 你以为自己做了 HA
- 但某个 master 一挂,运维机上的
kubectl就直接废了 - 节点间组件似乎没问题,只有人类操作入口挂了
排查
grep server ~/.kube/config
以及检查集群内组件使用的 kubeconfig:
grep server /etc/kubernetes/*.conf
止血方案
统一改成 VIP / LB 域名。
坑 4:证书 SAN 不包含 VIP 或 LB 域名
现象
- 用节点 IP 访问正常
- 换成 VIP 或域名后 TLS 校验失败
典型报错:
x509: certificate is valid for 10.0.0.11, 10.0.0.12, not 10.0.0.100
排查
openssl x509 -in /etc/kubernetes/pki/apiserver.crt -noout -text | grep -A1 "Subject Alternative Name"
止血方案
在初始化或证书轮换时把 VIP / LB 域名加入 SAN。
如果是 kubeadm,可以检查对应配置中的 controlPlaneEndpoint 和 certSANs。
坑 5:leader election 抖动,scheduler/controller-manager 频繁切主
现象
- Pod 调度延迟明显
- 控制器行为偶发异常
kubectl -n kube-system describe lease ...看到 holderIdentity 频繁变化
排查
看组件日志:
journalctl -u kubelet -f
crictl ps | grep kube-scheduler
crictl logs <scheduler-container-id>
crictl logs <controller-manager-container-id>
同时检查:
- 节点时间是否同步
- apiserver RT 是否明显抖动
- 网络丢包是否存在
止血方案
- 确保 NTP/chrony 正常
- 不要把 election 参数调得过短
- 优先解决 apiserver / etcd 基础稳定性
坑 6:VIP 漂移正常,但 ARP/网络设备缓存导致访问黑洞
现象
- Keepalived 已经切主
- 新主节点上也拿到了 VIP
- 但部分客户端仍然访问旧主,短时间超时
排查
在客户端检查 ARP:
ip neigh
arp -an
在新主节点抓包:
sudo tcpdump -i eth0 arp or port 6443
止血方案
- 调整
garp_master_delay、发送 gratuitous ARP - 检查交换机/云网络对 ARP 的处理
- 云环境优先使用托管 LB,而不是自建二层漂移
安全/性能最佳实践
高可用不是只关心“能不能切”,还要关心“切的时候会不会更危险、更慢”。
安全方面
1. 不要把 6443 暴露到公网
控制平面入口应限制来源:
- 办公网段
- 堡垒机
- CI/CD 固定出口
- 节点内网
至少加上:
- 安全组 / 防火墙白名单
- 双向 TLS
- 审计日志
2. etcd 必须启用 TLS,且尽量独立网络面
etcd 是集群大脑的数据源,裸奔风险极高。
建议:
- client/peer 全部启用 TLS
- etcd 端口只开放给控制平面
- 定期证书轮换
3. 定期做 etcd 快照备份,并演练恢复
只做备份、不演练恢复,等于没做。
建议至少确认:
- 快照生成成功
- 快照文件可读
- 恢复流程文档可执行
- 恢复后 apiserver 能重新连上
性能方面
1. etcd 使用低延迟磁盘
如果 etcd 放在普通慢盘上,控制平面的各种“玄学卡顿”会非常多。
尤其写延迟一高,apiserver、controller-manager 都会连锁反应。
2. 控制平面节点避免混部重负载业务
如果 control-plane 节点同时跑大量高 CPU / IO 业务,会直接影响:
- apiserver RT
- etcd fsync 延迟
- scheduler/controller-manager 切主稳定性
3. LB 超时配置不要太激进
太短会误摘正常实例,太长则故障切换不及时。
一个实用思路是:
- connect timeout 较短
- health check 周期较短
- fall/rise 有一定容忍
4. 观察这些指标
如果你接了 Prometheus,重点看:
apiserver_request_duration_secondsetcd_disk_wal_fsync_duration_secondsetcd_server_has_leaderetcd_server_leader_changes_seen_totalleader_election_master_statusprocess_resident_memory_bytes
这些指标比“Pod Running 不 Running”更能提前暴露问题。
一套我更推荐的验证清单
做完 HA 控制平面后,至少验证下面几项:
# 1. 客户端入口是否走 VIP/LB
grep server ~/.kube/config
# 2. apiserver readiness 是否正常
kubectl get --raw=/readyz?verbose
# 3. etcd 是否全成员健康
ETCDCTL_API=3 etcdctl endpoint health
# 4. leader election 是否稳定
kubectl -n kube-system get lease
# 5. 单节点故障时 API 是否可用
./watch-api.sh
# 6. 单个 etcd 故障时是否仍可写
kubectl create ns ha-test-$(date +%s)
# 7. 恢复后是否自动回归健康
kubectl get nodes
kubectl get componentstatuses || true
提醒一下:
componentstatuses在新版本里已经不推荐作为主要判断依据,别太依赖它。
边界条件与取舍
高可用控制平面也不是万能的,几个边界要说清楚:
1. 三控制平面不等于跨机房容灾
如果三台节点都在同一个机柜、同一交换域,遇到网络域级故障还是会一起出问题。
2. 多活入口不等于业务零中断
控制平面切换成功,不代表所有业务请求完全无感。
短时间内可能出现:
- 新建 Pod 延迟
- HPA 决策延迟
- operator reconcile 延迟
3. etcd 跨高延迟部署要非常谨慎
Raft 对时延敏感,跨地域强行拉 etcd,往往稳定性和性能都不好。
大多数场景下,控制平面高可用优先做同城/同可用区低延迟架构。
总结
Kubernetes 高可用控制平面的关键,不是“把 master 从 1 台变 3 台”,而是把这条链路真正打通:
- 统一稳定入口:VIP / LB
- 多副本 API Server
- 保持 quorum 的 etcd
- 稳定的 leader election
- 可观测、可演练、可恢复
如果你现在正在排查控制平面故障,我建议按这个顺序来:
- 先看客户端是不是连的 VIP/LB
- 再看 LB 是否正确摘除坏 apiserver
- 再确认 apiserver 自身 readiness
- 接着查 etcd quorum 和延迟
- 最后看 scheduler/controller-manager 的 lease 和切主行为
一句更落地的建议:
不要只做“部署高可用”,一定要做“故障演练高可用”。
因为真正暴露问题的,往往不是架构图,而是下面这些细节:
- kubeconfig 写死单节点
- 证书 SAN 漏了 VIP
- etcd 盘太慢
- LB 检查太浅
- Keepalived 漂移了但网络没收敛
- leader election 在抖动环境下频繁切主
如果你的集群规模不大,优先把 3 控制平面 + 3 etcd + 稳定 LB/VIP + 定期演练 做扎实,已经能覆盖大多数生产场景。
再往上的复杂设计,应该建立在监控、备份、恢复流程都成熟的前提下。