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

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

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

背景与问题

很多团队在把 Kubernetes 用到生产环境后,最先暴露出来的问题往往不是业务 Pod,而是控制平面本身不够稳

常见场景我见过不少:

  • 单个 master 节点宕机,整个集群无法调度
  • kube-apiserver 看起来还活着,但 kubectl 请求间歇性超时
  • etcd 有节点掉线后,控制面“半死不活”
  • 前面挂了一个负载均衡,但健康检查配置不对,故障切换形同虚设
  • 证书、时钟、DNS、VIP 漂移等细节问题,导致排查非常痛苦

这类问题麻烦的点在于:控制平面故障通常不是“全挂”,而是“部分可用”
也就是说,最难排查的是“偶发失败、局部失败、切换不彻底”。

本文我会从 troubleshooting 的角度来写,不只讲“标准架构长什么样”,更重点讲:

  1. 高可用控制平面的核心设计原则
  2. 故障是怎么切换的
  3. 出问题后应该从哪条路径定位
  4. 有哪些可以直接跑起来的实战配置

本文默认读者已经知道 Kubernetes 基本组件:kube-apiservercontroller-managerscheduleretcd


背景架构:高可用控制平面到底在保什么

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-managerkube-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-managerscheduler 会在 Kubernetes 中创建 lease / endpoint / configmap 锁(现代版本通常是 Lease)。

切换过程大致是:

  1. 当前 leader 持续 renew lease
  2. leader 宕机或网络中断
  3. renew 超时
  4. 其他副本竞争 lease
  5. 新 leader 接管

常见相关参数:

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

如果参数太保守,切换慢;太激进,网络抖动时容易频繁切主。


现象复现:如何模拟控制平面故障切换

如果你想在测试环境确认设计是否靠谱,建议真的做一遍故障演练。
别只看“节点 Ready”,要看业务操作是否连续可用

这里给一个最小化演练路径:

  1. 三节点 control-plane
  2. 前置 HAProxy 或 kube-vip
  3. kubectl 指向 VIP
  4. 持续执行 API 请求
  5. 人为停掉某个 apiserver 或直接关闭 control-plane 节点
  6. 观察请求中断时间和恢复时间

实战代码(可运行)

下面用 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 BACKUP
  • priority 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 out
    • context deadline exceeded
    • leader 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,可以检查对应配置中的 controlPlaneEndpointcertSANs


坑 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_seconds
  • etcd_disk_wal_fsync_duration_seconds
  • etcd_server_has_leader
  • etcd_server_leader_changes_seen_total
  • leader_election_master_status
  • process_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
  • 可观测、可演练、可恢复

如果你现在正在排查控制平面故障,我建议按这个顺序来:

  1. 先看客户端是不是连的 VIP/LB
  2. 再看 LB 是否正确摘除坏 apiserver
  3. 再确认 apiserver 自身 readiness
  4. 接着查 etcd quorum 和延迟
  5. 最后看 scheduler/controller-manager 的 lease 和切主行为

一句更落地的建议:
不要只做“部署高可用”,一定要做“故障演练高可用”。

因为真正暴露问题的,往往不是架构图,而是下面这些细节:

  • kubeconfig 写死单节点
  • 证书 SAN 漏了 VIP
  • etcd 盘太慢
  • LB 检查太浅
  • Keepalived 漂移了但网络没收敛
  • leader election 在抖动环境下频繁切主

如果你的集群规模不大,优先把 3 控制平面 + 3 etcd + 稳定 LB/VIP + 定期演练 做扎实,已经能覆盖大多数生产场景。
再往上的复杂设计,应该建立在监控、备份、恢复流程都成熟的前提下。


分享到:

上一篇
《Web逆向实战:从请求链路分析到签名参数复现的中级方法论》
下一篇
《从源码到实践:基于 OpenTelemetry 开源项目搭建可观测性链路的落地指南》