背景与问题
很多团队在做 Kubernetes 高可用时,第一反应是:把 API Server 多起几个、副本放到不同机器上,不就高可用了?
真到线上,问题往往没这么简单。我见过几类非常典型的故障:
kubectl间歇性超时,但节点和 Pod 似乎都还活着- 某台 master 宕机后,集群“没完全挂”,但调度和控制器反应明显变慢
- etcd 明明是 3 节点,结果一次网络抖动后整个控制平面都开始报错
- 前端 LB 还在转发流量,但转发到的是一个“活着但不可用”的 kube-apiserver
- controller-manager / scheduler leader 切换过慢,业务感知到较长时间的发布卡顿
这类问题的共同点是:控制平面的高可用,不只是“多副本”,而是“端到端故障切换链路”的稳定性和收敛速度。
本文我会从 troubleshooting 的角度来讲,不只讲“应该怎么搭”,更重点讲:
- 控制平面各组件为什么会影响切换速度
- 故障发生时,应该按什么路径定位
- 哪些参数和架构细节,决定了你是“秒级恢复”还是“十几分钟抖动”
背景架构:问题通常出在哪
一个典型的 Kubernetes 高可用控制平面,通常包含:
- 多个
kube-apiserver - 多个
kube-controller-manager - 多个
kube-scheduler - 一个奇数节点的
etcd集群 - 一个 VIP 或外部负载均衡器,统一暴露 API Server 入口
很多人把“高可用”的注意力都放在 API Server,其实真正影响全局的是下面这条链:
客户端 / kubelet / controller → LB / VIP → kube-apiserver → etcd → controller-manager / scheduler leader election
只要其中某个环节“假活着”,整个系统就会表现出非常诡异的半故障状态。
flowchart LR
A[ kubectl / kubelet / operators ] --> B[LB / VIP]
B --> C1[kube-apiserver-1]
B --> C2[kube-apiserver-2]
B --> C3[kube-apiserver-3]
C1 --> D[(etcd-1)]
C2 --> E[(etcd-2)]
C3 --> F[(etcd-3)]
G[kube-controller-manager x N] --> C1
G --> C2
G --> C3
H[kube-scheduler x N] --> C1
H --> C2
H --> C3
核心原理
1. 控制平面的“高可用”到底在保什么
控制平面高可用,目标不是所有组件都永远在线,而是:
- API 可访问
- 状态存储可达且一致
- 核心控制循环持续运行
- 主节点故障后,能在可接受时间内完成切换
对应到组件层面:
kube-apiserver:无状态,可横向扩展kube-controller-manager:多实例运行,但同一时刻通常只有一个 leader 真正执行关键控制循环kube-scheduler:同样依赖 leader electionetcd:强一致存储,要求法定多数(quorum)
这里最容易被误解的是:
API Server 多副本 ≠ 控制平面整体高可用。
如果 etcd quorum 丢了,API Server 再多也只是统一报错。
如果 controller-manager leader 切换慢,API Server 看起来活着,但服务副本补偿、节点状态处理会明显延迟。
2. etcd quorum 是控制平面的生死线
对于 3 节点 etcd:
- 存活 2 个:可用
- 只剩 1 个:不可写,通常整体不可用
对于 5 节点 etcd:
- 存活 3 个:可用
- 只剩 2 个:不可用
所以 etcd 不是“节点越多越稳”,而是要结合网络质量、磁盘性能和运维复杂度。
中等规模集群里,3 节点 etcd 往往是最均衡的选择。如果没有非常明确的容量和容灾要求,上来就 5 节点,反而更容易因为网络抖动放大问题。
stateDiagram-v2
[*] --> Healthy
Healthy --> Degraded: 失去 1 个 etcd 节点
Degraded --> Healthy: 节点恢复
Degraded --> Unavailable: 再失去 1 个节点\n(失去 quorum)
Unavailable --> Recovering: 人工恢复成员/网络
Recovering --> Healthy
3. leader election 决定“故障切换体感”
scheduler 和 controller-manager 都依赖 leader election。核心参数通常有:
--leader-elect=true--leader-elect-lease-duration--leader-elect-renew-deadline--leader-elect-retry-period
这三个时间参数决定了 leader 异常后,多快会被认定失效并切换。
一个常见经验值是:
lease-duration: 15srenew-deadline: 10sretry-period: 2s
但这不是越小越好。
如果你的网络时延大、API Server 或 etcd 有抖动,把时间调得太激进,可能导致误判 leader 失效,频繁脑裂式抖动。
如果调得太保守,切换又会慢,业务发布和控制动作会有明显停顿。
4. LB 健康检查不对,会制造“假高可用”
这是我最常见的坑之一。
很多负载均衡器只检查:
- 6443 端口是否能连通
但 kube-apiserver “端口活着”不等于“服务可用”。例如:
- apiserver 进程还在,但请求线程池卡死
- apiserver 到 etcd 访问异常
- apiserver 自身 readyz 失败,但 TCP 端口仍接受连接
所以控制平面前的 LB,应该优先检查应用层健康接口,例如:
/livez/readyz
其中,对外流量入口更适合看 /readyz。
现象复现
下面我用一个偏实战的方式,模拟几个常见故障场景。即使你不在生产环境直接做,也可以在测试集群里演练。
场景 1:单个 API Server 节点故障
现象:
kubectl get pods偶发超时- 大部分请求仍能成功
- 如果 LB 没摘掉坏节点,超时会间歇出现
复现方式:
- 部署 3 个控制平面节点
- 通过 LB 统一暴露 6443
- 停掉一台机器上的
kube-apiserver或直接断网 - 连续执行
kubectl get ns
场景 2:etcd 单节点卡顿而非彻底宕机
现象:
- API Server 不是直接报错,而是响应变慢
- 控制器延迟增加
- watch 请求积压
复现方式:
- 给某个 etcd 节点加磁盘 IO 压力
- 或限制网络带宽 / 注入高时延
- 观察 apiserver 与 etcd 的延迟指标
这种情况比“直接挂掉”更难排查,因为看起来所有进程都在。
场景 3:leader 切换慢
现象:
- Pod 故障后补副本明显变慢
- 节点 NotReady 后驱逐延迟
- scheduler 短时间停止调度
复现方式:
- 观察当前 leader
- 杀掉 leader 所在节点上的 controller-manager 或 scheduler
- 记录新 leader 接管时间
sequenceDiagram
participant C as Client/kubelet
participant L as LB/VIP
participant A1 as API Server(old)
participant A2 as API Server(new)
participant CM1 as Controller Manager(old leader)
participant CM2 as Controller Manager(new leader)
participant E as etcd
C->>L: 发起 API 请求
L->>A1: 转发到旧实例
A1-->>L: 健康异常/超时
L->>A2: 切换到新实例
A2->>E: 读写集群状态
CM1--xE: leader 租约续约失败
CM2->>E: 抢占 leader 成功
A2-->>C: 请求恢复正常
实战代码(可运行)
下面给一套最小可运行的排障与验证脚本,帮助你在高可用控制平面上做“健康检查 + leader 观测 + 故障切换验证”。
1. 用 HAProxy 为 API Server 做就绪探测
如果你用自建 LB,HAProxy 是个很常见的选择。重点不是它本身,而是检查 /readyz。
global
log stdout format raw local0
maxconn 2000
defaults
log global
mode tcp
timeout connect 5s
timeout client 50s
timeout server 50s
frontend k8s_api_frontend
bind *:6443
default_backend k8s_api_backend
backend k8s_api_backend
mode tcp
option tcp-check
tcp-check connect port 6443
tcp-check send-binary 504f5354202f72656164797a3f766572626f73653d20485454502f312e310d0a486f73743a206b756265726e657465730d0a436f6e6e656374696f6e3a20636c6f73650d0a0d0a
tcp-check expect string ok
server cp1 10.0.0.11:6443 check
server cp2 10.0.0.12:6443 check
server cp3 10.0.0.13:6443 check
说明:
这里使用 tcp-check 发送 HTTP 请求探测/readyz。生产上也可以直接使用支持 HTTP/HTTPS 健康检查的 LB,配置会更直观。
2. 检查控制平面组件健康状态
下面这个脚本会:
- 检查 API Server
/readyz - 查看 controller-manager 和 scheduler 的 leader
- 验证节点状态
- 输出基础排障信息
#!/usr/bin/env bash
set -euo pipefail
API_ENDPOINT="${1:-https://127.0.0.1:6443}"
echo "===> 1. 检查 API Server readyz"
kubectl get --raw='/readyz?verbose' | sed 's/\[+\]/PASS/g' || true
echo
echo "===> 2. 查看 kube-system 核心组件"
kubectl -n kube-system get pods -o wide
echo
echo "===> 3. 查看节点状态"
kubectl get nodes -o wide
echo
echo "===> 4. 查看 scheduler leader"
kubectl -n kube-system get lease kube-scheduler -o yaml | grep -E 'holderIdentity|renewTime' || true
echo
echo "===> 5. 查看 controller-manager leader"
kubectl -n kube-system get lease kube-controller-manager -o yaml | grep -E 'holderIdentity|renewTime' || true
echo
echo "===> 6. 最近 20 条异常事件"
kubectl get events -A --sort-by='.lastTimestamp' | tail -n 20 || true
保存为 check-control-plane.sh,赋予执行权限:
chmod +x check-control-plane.sh
./check-control-plane.sh
3. 模拟 API Server 故障并观测切换时间
这个脚本通过持续访问集群 API,统计成功/失败情况。你可以在执行时手动停掉某个控制平面节点上的 apiserver 或断开网络。
#!/usr/bin/env bash
set -euo pipefail
COUNT="${1:-60}"
echo "Start probing Kubernetes API..."
for i in $(seq 1 "$COUNT"); do
TS=$(date '+%H:%M:%S')
if kubectl version --request-timeout='2s' >/dev/null 2>&1; then
echo "[$TS] OK"
else
echo "[$TS] FAIL"
fi
sleep 1
done
保存为 probe-apiserver.sh:
chmod +x probe-apiserver.sh
./probe-apiserver.sh 120
如果切换配置合理,你应该只看到少量失败,而不是长时间连续失败。
4. 查看 etcd 健康与延迟
如果你的 etcd 运行在控制平面节点上,且安装了 etcdctl,可以直接这样检查:
export ETCDCTL_API=3
export ETCDCTL_CACERT=/etc/kubernetes/pki/etcd/ca.crt
export ETCDCTL_CERT=/etc/kubernetes/pki/etcd/healthcheck-client.crt
export ETCDCTL_KEY=/etc/kubernetes/pki/etcd/healthcheck-client.key
etcdctl --endpoints=https://10.0.0.11:2379,https://10.0.0.12:2379,https://10.0.0.13:2379 endpoint health
etcdctl --endpoints=https://10.0.0.11:2379,https://10.0.0.12:2379,https://10.0.0.13:2379 endpoint status -w table
重点看:
- 是否所有 endpoint 都健康
RAFT TERM是否频繁变化- leader 是否频繁漂移
- 延迟是否明显不一致
定位路径:出故障时先看哪里
我建议排查时按“从外到内”的顺序,不要一上来就钻 etcd 日志。
第一步:确认入口是否稳定
先验证用户侧是不是所有请求都失败:
kubectl get --raw='/readyz?verbose'
kubectl get nodes
kubectl get pods -A
如果是偶发性失败,先怀疑:
- LB 健康检查不正确
- 某个 API Server 实例半故障
- VIP 漂移异常
如果是持续性失败,再看:
- etcd quorum 是否丢失
- 证书、网络、DNS 是否异常
- 控制平面节点是否同时受影响
第二步:区分“API 问题”还是“控制器问题”
这一步很关键。因为两者业务体感不同。
API 问题的典型现象
kubectl超时- admission webhook 全部变慢
- kubelet 上报异常
- 大量
apiserver相关错误日志
控制器问题的典型现象
- 新建 Pod 长时间 Pending
- Deployment 扩缩容迟缓
- 节点故障后副本补偿慢
- HPA 计算不及时
如果 API 正常但控制动作变慢,优先看:
kubectl -n kube-system get lease
kubectl -n kube-system get pods -l component=kube-controller-manager -o wide
kubectl -n kube-system get pods -l component=kube-scheduler -o wide
第三步:确认 etcd 是否是根因
很多控制平面问题,最终都能追到 etcd:
- 磁盘写延迟高
- 网络抖动
- 成员状态不一致
- snapshot / compaction 不合理
- 资源打满
常用检查项:
etcdctl endpoint health
etcdctl endpoint status -w table
journalctl -u etcd -n 200 --no-pager
如果你发现:
- 某个成员经常超时
- leader 频繁切换
- fd / IOPS / fsync 延迟高
那先别急着调 Kubernetes 参数,先把 etcd 基础稳定性解决掉。
常见坑与排查
坑 1:LB 只做 TCP 探活
现象
- 6443 端口通
- 但请求偶发超时
- 切换不彻底
原因
LB 没识别到“应用不可用但进程还活着”的 API Server。
排查
curl -k https://<apiserver-ip>:6443/livez
curl -k https://<apiserver-ip>:6443/readyz
止血方案
- 将健康检查从 TCP 改为
/readyz - 降低不健康节点摘除时间
- 但不要把阈值调得过于激进,避免短抖动触发误摘
坑 2:etcd 和其他高负载组件混部
现象
- 白天高峰期控制平面抖动更明显
- etcd fsync latency 偏高
- API Server RT 抖动
原因
etcd 对磁盘和网络延迟非常敏感,和高 IO 业务混跑很容易出问题。
排查
看节点资源和磁盘情况:
iostat -x 1 5
top
vmstat 1 5
止血方案
- etcd 独占更稳定的磁盘
- 避免与日志、镜像、业务数据盘强竞争
- 给控制平面节点做资源保留
坑 3:leader election 参数照抄别人
现象
- leader 经常漂移
- 或者 leader 切换特别慢
原因
不同机房、不同网络时延、不同 etcd 性能下,参数不能机械复制。
排查
看 Lease 更新是否连续、是否频繁变更:
kubectl -n kube-system get lease kube-scheduler -w
kubectl -n kube-system get lease kube-controller-manager -w
止血方案
- 网络稳定、节点同城低延迟:可适度缩短切换时间
- 跨可用区部署、有抖动:适当放宽参数,优先稳定
坑 4:3 控制平面节点跨 3 个可用区,但网络质量差
现象
- 理论上容灾更强,实际上 leader 经常漂
- etcd 延迟增大
- 发布偶发卡住
原因
高可用不是“拉得越散越好”。
etcd 是强一致系统,跨 AZ 带来的网络时延会直接影响写入和选主。
排查思路
- 对比 AZ 间 RTT
- 看 etcd 提交延迟
- 看 leader 是否倾向固定在某个区域
止血方案
- 如果网络不是稳定低延迟,优先同城低时延部署
- 真要跨 AZ,先验证 etcd 和 apiserver 的延迟预算是否可接受
坑 5:故障切换只测“宕机”,不测“半故障”
现象
- 演练时断电都能过
- 线上遇到网络抖动、磁盘卡顿、进程假死就出问题
原因
“硬故障”容易被检测,“软故障”更接近真实生产事故。
止血方案
演练要覆盖:
- 节点断网
- 端口可达但 readyz 失败
- etcd 磁盘时延升高
- API Server CPU 打满
- leader 所在节点网络抖动
安全/性能最佳实践
安全方面
1. 控制平面证书和访问入口要分层管理
建议至少区分:
- 集群内部组件访问 API 的证书
- 运维人员外部访问 kubeconfig
- LB/VIP 暴露入口的证书策略
避免图省事把一个高权限 admin kubeconfig 到处发。
2. etcd 必须启用 TLS,并限制访问面
etcd 是控制平面的数据库,暴露面越小越好。
建议:
- 仅控制平面节点可访问 2379/2380
- 启用双向 TLS
- 不允许业务节点直接访问 etcd
3. RBAC 与审计日志不要忽略
很多团队把高可用做好了,却没保留足够的审计信息。出故障时只能猜。
建议保留:
- API Server audit log
- etcd 关键事件日志
- 控制器与调度器异常日志
这样故障定位才有证据链。
性能方面
1. API Server 前的连接复用和超时要合理
LB 配置建议关注:
- 后端连接超时
- 空闲连接回收
- 失败重试策略
超时太长,会拖慢故障摘除;太短,又可能放大瞬时抖动。
2. etcd 磁盘性能比 CPU 更关键
对 etcd 来说:
- 低延迟磁盘
- 稳定 fsync
- 可预测的 IOPS
通常比“多给几个核”更有效。
3. 控制平面节点要做资源预留
给系统和关键组件预留资源,例如:
system-reservedkube-reserved
否则业务或系统后台任务抢占资源时,最先抖的往往就是控制平面。
4. 故障切换优化要看“端到端时间”
建议你实际记录以下指标:
- API Server 实例失效到 LB 摘除时间
- controller-manager leader 切换时间
- scheduler leader 切换时间
- etcd leader 漂移次数
- 业务恢复时间(如 Deployment 补副本时间)
不要只看单点指标,要看从故障发生到业务恢复的整段路径。
一套可执行的优化建议
如果你现在要把一个 Kubernetes 集群控制平面从“能跑”优化到“故障切换更稳”,我建议按这个顺序做:
-
先保证 etcd 稳定
- 3 节点优先
- 独立稳定磁盘
- 控制网络抖动
-
再修正 API Server 前的 LB 健康检查
- 不只测端口
- 优先基于
/readyz
-
验证 controller-manager / scheduler 的 leader election
- 不要盲调参数
- 先测当前切换时延
-
补齐半故障演练
- 宕机、断网、卡顿都要测
- 形成固定演练脚本
-
建立最小观测面
- readyz
- lease 变化
- etcd endpoint health/status
- 关键日志采集
如果你的团队人力有限,这个顺序非常重要。
我自己的经验是:etcd 稳定性 + 正确的 LB 健康检查,通常就能解决 70% 以上“看起来像控制平面高可用失败”的问题。
总结
Kubernetes 高可用控制平面的难点,不在于把组件“多起几份”,而在于让整条故障切换链路真正可靠:
kube-apiserver要能被正确摘除和切换etcd要保证 quorum 与低延迟controller-manager和scheduler要在稳定前提下完成 leader 切换- 演练不能只测“彻底宕机”,更要测“半故障”
如果你只记住一句话,我建议记这个:
控制平面高可用,核心不是副本数,而是“健康检查、存储一致性、选主切换”三件事是否协同工作。
最后给一个边界条件建议:
- 中小规模集群:3 控制平面节点 + 3 etcd + 正确 LB 探活,通常足够
- 跨 AZ 部署:先测网络时延和 etcd 稳定性,再谈更激进的切换优化
- 追求秒级恢复:一定做半故障演练,否则参数再漂亮也不代表线上真稳
如果你准备开始排查自己集群的控制平面,最先做的不是改参数,而是跑一遍本文里的检查脚本,确认问题到底在入口、API、leader election,还是 etcd。这样排障才不会绕远路。