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

《Kubernetes 集群高可用架构实战:控制平面冗余、etcd 容灾与故障切换设计》

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

背景与问题

很多团队一开始搭 Kubernetes,都默认“能跑就行”:1 个 control plane、1 套 etcd、API Server 前面随便挂个地址。开发测试环境这么做问题不大,但一旦进入生产,故障就会非常“直接”:

  • 单个控制平面节点宕机,kubectl 全部超时
  • etcd 磁盘打满或网络抖动,整个集群状态写不进去
  • kube-apiserver 实例虽然有多个,但客户端没有统一入口,切换靠人工
  • controller-manager / scheduler 看似多副本,实际上没有正确 leader election,切换不稳定
  • 误以为“worker 节点还在跑业务,就算高可用”,结果扩缩容、发布、证书轮换全卡住

这类问题有个共性:数据面还能勉强活,控制面已经失去可靠性

这篇文章不从“理想架构图”出发,而是从故障排查和止血的角度,带你把 Kubernetes 高可用的关键设计串起来:

  1. 控制平面怎么做冗余
  2. etcd 为什么是 HA 设计里的核心
  3. 故障切换链路应该怎么设计
  4. 出问题时怎么快速判断是 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-apiserver
  • kube-controller-manager
  • kube-scheduler
  • etcd

其中:

  • kube-apiserver无状态,可水平扩展
  • kube-controller-manager:多实例部署,但同一时刻通常只有 leader 工作
  • kube-scheduler:同样依赖 leader election
  • etcd强一致状态存储,依赖 quorum,多数派存活才可写

所以高可用设计不是“每个组件都做双机”这么简单,而是:

  1. API Server 多实例 + 统一入口
  2. controller-manager / scheduler 正确启用选主
  3. etcd 保证奇数节点、多数派、低延迟网络和稳定磁盘
  4. 各层健康检查和故障摘除要能串起来

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. 故障切换链路的正确理解

一条完整的切换链路通常是:

  1. 某个 control plane 节点故障
  2. LB 健康检查失败,摘除该节点的 6443
  3. 客户端流量切到其他 API Server
  4. 如果故障节点恰好承载 scheduler / controller-manager leader,则其余实例重新选主
  5. 如果故障节点还是 etcd 成员,要看是否还保有 quorum
  6. 若 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-clusterinitial-cluster-statedata-dir 的关系

实战代码(可运行)

下面给一套偏实战的命令,适合你在自建 kubeadm 集群中排查和验证高可用链路。

假设:

  • LB VIP:10.0.0.10:6443
  • control plane 节点:
    • 10.0.0.11
    • 10.0.0.12
    • 10.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 out
  • failed to revoke lease
  • storage 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 高可用真正要守住的,不是“组件数量”,而是这三条线:

  1. 入口线:API Server 多副本 + 正确健康检查 + 统一访问入口
  2. 控制线:scheduler / controller-manager 正常选主,故障后能平稳切换
  3. 状态线: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 集群是“看起来高可用”,还是“真的扛得住事故”。


分享到:

上一篇
《Java开发踩坑实战:定位并修复线程池误用导致的接口雪崩问题》
下一篇
《从源码到部署:用开源可观测性项目 OpenTelemetry 构建中型微服务链路追踪实践》