跳转到内容
123xiao | 无名键客

《集群架构实战:基于 Kubernetes 的高可用控制平面设计与故障切换优化》

字数: 0 阅读时长: 1 分钟

背景与问题

很多团队在做 Kubernetes 高可用时,第一反应是:把 API Server 多起几个、副本放到不同机器上,不就高可用了?

真到线上,问题往往没这么简单。我见过几类非常典型的故障:

  • kubectl 间歇性超时,但节点和 Pod 似乎都还活着
  • 某台 master 宕机后,集群“没完全挂”,但调度和控制器反应明显变慢
  • etcd 明明是 3 节点,结果一次网络抖动后整个控制平面都开始报错
  • 前端 LB 还在转发流量,但转发到的是一个“活着但不可用”的 kube-apiserver
  • controller-manager / scheduler leader 切换过慢,业务感知到较长时间的发布卡顿

这类问题的共同点是:控制平面的高可用,不只是“多副本”,而是“端到端故障切换链路”的稳定性和收敛速度。

本文我会从 troubleshooting 的角度来讲,不只讲“应该怎么搭”,更重点讲:

  1. 控制平面各组件为什么会影响切换速度
  2. 故障发生时,应该按什么路径定位
  3. 哪些参数和架构细节,决定了你是“秒级恢复”还是“十几分钟抖动”

背景架构:问题通常出在哪

一个典型的 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 election
  • etcd:强一致存储,要求法定多数(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 决定“故障切换体感”

schedulercontroller-manager 都依赖 leader election。核心参数通常有:

  • --leader-elect=true
  • --leader-elect-lease-duration
  • --leader-elect-renew-deadline
  • --leader-elect-retry-period

这三个时间参数决定了 leader 异常后,多快会被认定失效并切换。

一个常见经验值是:

  • lease-duration: 15s
  • renew-deadline: 10s
  • retry-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 没摘掉坏节点,超时会间歇出现

复现方式:

  1. 部署 3 个控制平面节点
  2. 通过 LB 统一暴露 6443
  3. 停掉一台机器上的 kube-apiserver 或直接断网
  4. 连续执行 kubectl get ns

场景 2:etcd 单节点卡顿而非彻底宕机

现象:

  • API Server 不是直接报错,而是响应变慢
  • 控制器延迟增加
  • watch 请求积压

复现方式:

  1. 给某个 etcd 节点加磁盘 IO 压力
  2. 或限制网络带宽 / 注入高时延
  3. 观察 apiserver 与 etcd 的延迟指标

这种情况比“直接挂掉”更难排查,因为看起来所有进程都在。


场景 3:leader 切换慢

现象:

  • Pod 故障后补副本明显变慢
  • 节点 NotReady 后驱逐延迟
  • scheduler 短时间停止调度

复现方式:

  1. 观察当前 leader
  2. 杀掉 leader 所在节点上的 controller-manager 或 scheduler
  3. 记录新 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-reserved
  • kube-reserved

否则业务或系统后台任务抢占资源时,最先抖的往往就是控制平面。


4. 故障切换优化要看“端到端时间”

建议你实际记录以下指标:

  • API Server 实例失效到 LB 摘除时间
  • controller-manager leader 切换时间
  • scheduler leader 切换时间
  • etcd leader 漂移次数
  • 业务恢复时间(如 Deployment 补副本时间)

不要只看单点指标,要看从故障发生到业务恢复的整段路径。


一套可执行的优化建议

如果你现在要把一个 Kubernetes 集群控制平面从“能跑”优化到“故障切换更稳”,我建议按这个顺序做:

  1. 先保证 etcd 稳定

    • 3 节点优先
    • 独立稳定磁盘
    • 控制网络抖动
  2. 再修正 API Server 前的 LB 健康检查

    • 不只测端口
    • 优先基于 /readyz
  3. 验证 controller-manager / scheduler 的 leader election

    • 不要盲调参数
    • 先测当前切换时延
  4. 补齐半故障演练

    • 宕机、断网、卡顿都要测
    • 形成固定演练脚本
  5. 建立最小观测面

    • readyz
    • lease 变化
    • etcd endpoint health/status
    • 关键日志采集

如果你的团队人力有限,这个顺序非常重要。
我自己的经验是:etcd 稳定性 + 正确的 LB 健康检查,通常就能解决 70% 以上“看起来像控制平面高可用失败”的问题。


总结

Kubernetes 高可用控制平面的难点,不在于把组件“多起几份”,而在于让整条故障切换链路真正可靠:

  • kube-apiserver 要能被正确摘除和切换
  • etcd 要保证 quorum 与低延迟
  • controller-managerscheduler 要在稳定前提下完成 leader 切换
  • 演练不能只测“彻底宕机”,更要测“半故障”

如果你只记住一句话,我建议记这个:

控制平面高可用,核心不是副本数,而是“健康检查、存储一致性、选主切换”三件事是否协同工作。

最后给一个边界条件建议:

  • 中小规模集群:3 控制平面节点 + 3 etcd + 正确 LB 探活,通常足够
  • 跨 AZ 部署:先测网络时延和 etcd 稳定性,再谈更激进的切换优化
  • 追求秒级恢复:一定做半故障演练,否则参数再漂亮也不代表线上真稳

如果你准备开始排查自己集群的控制平面,最先做的不是改参数,而是跑一遍本文里的检查脚本,确认问题到底在入口、API、leader election,还是 etcd。这样排障才不会绕远路。


分享到:

上一篇
《Docker 多阶段构建与镜像瘦身实战:从构建提速到安全上线的完整优化方案》
下一篇
《大模型应用实战:基于 RAG 构建企业知识库问答系统的架构设计与性能优化》