Kubernetes 集群架构实战:从控制平面高可用到工作节点弹性扩缩容的设计与落地
Kubernetes 用久了,很多团队都会从“能跑”走到“跑稳、跑省、跑得动”。前期单控制平面、固定节点数的集群往往够用,但业务一旦开始承载核心流量,问题就会集中暴露:控制平面单点、etcd 抖动、升级窗口紧张、工作节点资源浪费,或者高峰期扩容跟不上。
这篇文章我换一个更偏“落地设计”的角度来讲,不只是堆概念,而是把控制平面高可用和工作节点弹性扩缩容放进同一个架构视角里看:它们并不是两套孤立机制,而是一个面向稳定性、容量和成本的整体系统。
背景与问题
在中型以上 Kubernetes 集群里,常见问题通常集中在这几类:
-
控制平面单点
kube-apiserver所在节点故障后,整个集群管理面不可用- 虽然已有业务 Pod 可能继续跑,但调度、扩容、发布、控制器收敛都会受影响
-
etcd 可靠性不足
- 把 etcd 和控制组件混布后,节点资源竞争明显
- 磁盘延迟高时,etcd 写入超时,进一步放大控制平面不稳定
-
工作节点扩缩容滞后
- HPA 已经把副本数拉高了,但节点不够,Pod 一直 Pending
- 反过来,低峰时机器还在空转,成本居高不下
-
架构设计割裂
- 控制平面按“高可用”搭了,工作节点却没有自动扩容机制
- 或者节点能自动扩,但 Pod 的资源请求配置混乱,导致扩容决策失真
如果把这些问题拆开处理,往往会头痛医头、脚痛医脚;更好的办法是从架构层统一设计。
方案目标与设计原则
一个比较实用的目标可以定义为:
- 控制平面无单点
- 工作节点可按负载弹性扩缩
- etcd 稳定优先于一切
- 扩容链路可观测、可回滚
- 业务层有边界,不把基础设施压垮
我一般会用下面几个原则来约束方案:
1. 控制平面优先保证一致性和可恢复性
控制平面不是追求极限吞吐,而是追求在故障时还能继续管理集群。
所以对于 etcd、API Server、Controller Manager、Scheduler,更看重:
- 多副本
- 明确的故障域分布
- 稳定的网络和存储
- 升级过程中的最小扰动
2. 弹性扩缩容不是“开个组件”就完了
扩缩容至少涉及三层:
- HPA:扩 Pod
- Cluster Autoscaler / Karpenter:扩节点
- VPA/资源治理:让请求值更合理
少任何一层,弹性都可能失真。
3. 先让调度“说真话”
很多集群扩容失败,不是 Autoscaler 不工作,而是:
- Pod 没设置
resources.requests - 节点污点/亲和性限制过强
- PDB、拓扑约束、存储绑定把调度卡死
也就是说,节点扩容系统只能根据调度器暴露出来的“未满足需求”做判断。调度条件如果本身不合理,扩容就会变成误扩或不扩。
核心原理
这一部分把控制平面 HA 和工作节点弹性放到同一张图里看。
整体架构图
flowchart TB
U[用户/CI/CD/kubectl] --> LB[控制平面负载均衡 VIP/LB]
LB --> API1[kube-apiserver CP1]
LB --> API2[kube-apiserver CP2]
LB --> API3[kube-apiserver CP3]
API1 --> ETCD1[(etcd-1)]
API2 --> ETCD2[(etcd-2)]
API3 --> ETCD3[(etcd-3)]
API1 --> CM1[controller-manager]
API2 --> CM2[controller-manager]
API3 --> CM3[controller-manager]
API1 --> SCH1[scheduler]
API2 --> SCH2[scheduler]
API3 --> SCH3[scheduler]
CM1 --> HPA[HPA]
HPA --> DEP[Deployment/ReplicaSet]
DEP --> PODS[Pods]
PODS --> SCHED[调度器选择节点]
SCHED --> N1[Worker Node Group A]
SCHED --> N2[Worker Node Group B]
CA[Cluster Autoscaler] --> N1
CA --> N2
CA --> API1
这张图里有两个关键链路:
- 管理面链路:用户请求进入 LB,再到多个
kube-apiserver,最终依赖 etcd 保持状态一致 - 弹性链路:HPA 增加 Pod 副本,若节点不足则由 Cluster Autoscaler 增加节点
一、控制平面高可用原理
控制平面高可用,核心是三件事:
1. API Server 多实例 + 前置负载均衡
kube-apiserver 是无状态的,天然适合多实例部署。
常见做法:
- 3 台控制平面节点
- 前面挂一个 TCP LB 或 Keepalived + HAProxy 的 VIP
- 所有组件、运维入口都通过统一地址访问 API Server
这样任意一台控制节点故障,不会直接导致管理面中断。
2. etcd 多节点奇数仲裁
etcd 是强一致 KV 存储,Kubernetes 所有状态最终都落在这里。
高可用设计要点:
- 使用 3 或 5 个成员
- 保证奇数节点
- 尽量跨故障域分布
- 使用低延迟 SSD
- 定期快照备份
这里有个经典误区:很多人觉得 4 节点比 3 节点更高可用。实际上 etcd 看的是仲裁数,4 节点容错能力并不比 3 节点更强,复杂度还更高。
3. Controller Manager 与 Scheduler 通过选主工作
kube-controller-manager 和 kube-scheduler 虽然可以多实例运行,但实际是通过 Leader Election 保证同一时刻只有一个主实例在主动工作。
这意味着:
- 多实例带来故障切换能力
- 不会因为多个实例同时控制而产生混乱
二、工作节点弹性扩缩容原理
工作节点弹性不是直接“看 CPU 就加机器”,而是一个链式过程。
sequenceDiagram
participant User as 流量/业务负载
participant HPA as HPA
participant RS as Deployment/ReplicaSet
participant Scheduler as Scheduler
participant CA as Cluster Autoscaler
participant Cloud as 云厂商节点组/ASG
User->>HPA: 指标升高
HPA->>RS: 增加副本数
RS->>Scheduler: 创建新 Pod
Scheduler-->>RS: 节点资源不足,Pod Pending
CA->>Scheduler: 发现不可调度 Pod
CA->>Cloud: 扩容节点组
Cloud-->>CA: 新节点启动
CA-->>Scheduler: 节点加入集群
Scheduler->>RS: Pod 调度成功
1. HPA 负责 Pod 级弹性
HPA 根据 CPU、内存或自定义指标调整副本数。
但 HPA 有一个前提:Pod 的资源请求和指标必须可信。
如果 requests.cpu 配得过低,HPA 会很容易触发大规模扩容;配得过高,又会让调度器误判资源不足。
2. Cluster Autoscaler 负责节点级弹性
Cluster Autoscaler 并不直接根据业务指标工作,它主要看:
- 是否存在 Pending 且可通过加节点解决的 Pod
- 哪个节点组最适合承接这些 Pod
- 哪些节点长期低利用率、Pod 可安全驱逐,从而缩容
所以它解决的是“调度容量不够”的问题,而不是“业务吞吐不够”的问题。
3. 调度约束决定扩容能否成功
如果 Pod 带有以下条件,扩容可能仍然失败:
- 严格
nodeSelector - 太强的
nodeAffinity - 不合理的
taints/tolerations - 本地存储卷或特定拓扑绑定
- PDB 阻止缩容腾挪
我见过一个很典型的坑:团队开了 Cluster Autoscaler,但应用指定了只跑在某个标签节点组,而 Autoscaler 实际只管理另一个节点组。结果表面看“自动扩容失效”,本质上是扩错池子。
方案对比与取舍分析
控制平面部署方案对比
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 单控制平面 | 简单、成本低 | 单点明显 | 测试环境、小规模临时集群 |
| 3 控制平面 + 堆叠 etcd | 部署相对简单,HA 足够 | 资源竞争风险存在 | 大多数中型生产集群 |
| 3 控制平面 + 独立 etcd 集群 | 稳定性更强,职责清晰 | 成本更高,运维更复杂 | 核心生产平台、大型集群 |
如果团队规模不大、集群数量不少,我通常建议优先选择:
- 3 控制平面
- 堆叠 etcd 或独立 etcd(二选一)
- 前置 LB
- 自动化安装与证书管理
不要一上来就追求“最复杂、最强大”的形态,先确保团队能维护。
节点弹性方案对比
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 固定节点池 | 可预测、简单 | 成本高、峰值响应差 | 小规模稳定业务 |
| HPA only | 应用副本可弹性 | 节点不足时无解 | 已有较大余量的集群 |
| HPA + Cluster Autoscaler | 主流方案,兼顾成本与弹性 | 依赖合理资源治理 | 通用生产环境 |
| HPA + Karpenter | 节点供应更灵活,启动效率更高 | 生态与治理方式需适配 | 云上、快速迭代场景 |
容量估算:别把高可用做成“资源争抢”
做 HA 设计时,容量估算很重要。经验上可以从两个层面考虑。
控制平面容量关注点
控制平面主要看:
- API QPS / Burst
- 对象数量(Pod、ConfigMap、Secret、CRD)
- 控制器数量
- etcd 磁盘 IOPS 与延迟
- admission webhook 的时延
粗略建议:
- 中型生产集群起步可考虑 3 个控制平面节点
- 单节点至少保留稳定 CPU/内存余量
- etcd 所在磁盘延迟尽量低,重点盯
fsync与 WAL 延迟
工作节点容量关注点
重点看:
- 峰值业务负载增长速度
- 节点从创建到 Ready 的耗时
- 单节点可承载 Pod 数
- 镜像拉取耗时
- DaemonSet 固定开销
一个常被忽略的点是:
扩容速度不是 Autoscaler 决定的,而是“云主机启动 + kubelet 注册 + 网络/存储插件初始化 + 镜像拉取”共同决定的。
所以如果你的业务峰值在 1 分钟内陡增,而节点准备时间要 3 分钟,就需要:
- 保留 warm pool
- 或提前预扩
- 或提高基础节点数
实战代码(可运行)
下面用一组最小可用示例,把“应用副本扩容 + 节点组弹性”串起来。
这组配置可以直接用于已有指标服务的 Kubernetes 集群中。
说明:
- 假设集群中已安装
metrics-server- 假设云环境已接入
Cluster Autoscaler- 示例应用使用
nginx
1. 一个带资源请求的 Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-demo
namespace: default
spec:
replicas: 2
selector:
matchLabels:
app: web-demo
template:
metadata:
labels:
app: web-demo
spec:
containers:
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80
resources:
requests:
cpu: "200m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "256Mi"
---
apiVersion: v1
kind: Service
metadata:
name: web-demo
namespace: default
spec:
selector:
app: web-demo
ports:
- port: 80
targetPort: 80
type: ClusterIP
应用:
kubectl apply -f web-demo.yaml
kubectl get pod -o wide
2. 为 Deployment 配置 HPA
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: web-demo-hpa
namespace: default
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: web-demo
minReplicas: 2
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60
应用:
kubectl apply -f web-demo-hpa.yaml
kubectl get hpa -w
3. 用压测 Pod 制造负载
apiVersion: v1
kind: Pod
metadata:
name: load-generator
namespace: default
spec:
restartPolicy: Never
containers:
- name: busybox
image: busybox:1.36
command:
- /bin/sh
- -c
- >
while true;
do
wget -q -O- http://web-demo.default.svc.cluster.local;
done
应用并观察:
kubectl apply -f load-generator.yaml
kubectl top pod
kubectl get hpa -w
如果 CPU 使用率持续升高,HPA 会开始增加 web-demo 的副本数。
4. 观察 Pod Pending 触发节点扩容
当集群现有节点资源不足时,会出现部分 Pod 无法调度。此时可以查看:
kubectl get pod
kubectl describe pod <pending-pod-name>
如果事件中有类似内容:
0/3 nodes are available: insufficient cpupod didn't trigger scale-up或triggered scale-up
说明已经进入 Autoscaler 决策链路。
5. 一个简化版 Cluster Autoscaler 部署示例
注意:不同云厂商参数差异很大,下面是通用结构示例,便于理解组件运行方式。生产环境请按云平台文档填写节点组参数、IAM 权限和自动发现配置。
apiVersion: v1
kind: ServiceAccount
metadata:
name: cluster-autoscaler
namespace: kube-system
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: cluster-autoscaler
namespace: kube-system
spec:
replicas: 1
selector:
matchLabels:
app: cluster-autoscaler
template:
metadata:
labels:
app: cluster-autoscaler
spec:
serviceAccountName: cluster-autoscaler
containers:
- name: cluster-autoscaler
image: registry.k8s.io/autoscaling/cluster-autoscaler:v1.27.3
command:
- ./cluster-autoscaler
- --cloud-provider=aws
- --namespace=kube-system
- --node-group-auto-discovery=asg:tag=k8s.io/cluster-autoscaler/enabled,k8s.io/cluster-autoscaler/demo-cluster
- --balance-similar-node-groups
- --skip-nodes-with-system-pods=false
- --expander=least-waste
- --stderrthreshold=info
- --v=4
resources:
requests:
cpu: 100m
memory: 300Mi
limits:
cpu: 200m
memory: 500Mi
应用:
kubectl apply -f cluster-autoscaler.yaml
kubectl -n kube-system logs -f deploy/cluster-autoscaler
6. 一个可执行的排查脚本
下面这个脚本可以快速查看:
- Pending Pod
- HPA 状态
- 节点资源概况
- Cluster Autoscaler 日志关键字
#!/usr/bin/env bash
set -euo pipefail
echo "=== Pending Pods ==="
kubectl get pods -A --field-selector=status.phase=Pending || true
echo
echo "=== HPA Status ==="
kubectl get hpa -A || true
echo
echo "=== Node Allocatable ==="
kubectl describe nodes | egrep "Name:|Allocatable:|cpu:|memory:" || true
echo
echo "=== Recent Cluster Autoscaler Logs ==="
kubectl -n kube-system logs deploy/cluster-autoscaler --tail=200 | \
egrep "scale.up|No expansion options|pod didn't trigger scale-up|Upcoming|Scale-down" || true
保存为 check-autoscaling.sh 后执行:
chmod +x check-autoscaling.sh
./check-autoscaling.sh
控制平面高可用的落地建议
如果你准备从单控制平面迁移到高可用架构,我建议按这个顺序推进:
-
先规范接入地址
- 为 API Server 提供统一 LB/VIP 地址
- 业务、运维工具、节点注册都使用统一 endpoint
-
补齐 3 控制平面
- 至少保证 3 个控制节点
- 跨可用区部署时关注网络延迟
-
评估 etcd 形态
- 集群规模中等:可先用堆叠 etcd
- 控制面负载高、变更频繁:考虑独立 etcd
-
把升级和备份流程自动化
- etcd 周期性快照
- 控制平面滚动升级演练
- 明确证书轮换和灾备恢复流程
常见坑与排查
这一节是实战里最常见、也最容易误判的问题。
坑 1:HPA 没反应
现象
kubectl get hpa一直没有扩容TARGETS显示<unknown>
排查
kubectl top pod
kubectl get apiservice | grep metrics
kubectl -n kube-system get pod | grep metrics-server
原因
- 没装
metrics-server - 指标采集异常
- Pod 没设置
resources.requests
处理
- 确认 metrics API 可用
- 为容器设置 CPU/内存 request
坑 2:Pod Pending,但 Cluster Autoscaler 不扩容
现象
- Pod 长时间 Pending
- Autoscaler 日志没有实际 scale-up
排查
kubectl describe pod <pod-name>
kubectl -n kube-system logs deploy/cluster-autoscaler --tail=300
重点看这些信息:
Insufficient cpu/memorypod didn't trigger scale-upNo expansion options
常见原因
- Pod 受 nodeSelector/nodeAffinity 限制
- 节点组没有对应标签
- Autoscaler 没管理该节点组
- 请求资源大于节点规格
- PVC 或拓扑约束不可满足
坑 3:节点能扩,业务还是抖
现象
- 节点已经拉起
- Pod 调度也成功了
- 但业务延迟依然明显
原因
- 新节点启动慢
- 镜像太大,拉取耗时长
- 应用预热慢
- HPA 的采样窗口滞后
处理建议
- 关键应用保留基础冗余副本
- 优化镜像体积
- 引入预热策略
- 不要把最小副本设得过低
我自己踩过的一个坑就是:
应用启动探针比较保守,Pod 实际 2 分钟才 Ready,但 HPA 已经扩出来了。看上去副本增加了,实际承接流量的实例并没有及时增加。
坑 4:控制平面看似高可用,实际上被 etcd 拖垮
现象
- API Server 间歇性超时
- 控制器响应慢
- 大量 leader 切换
排查
- 查看 etcd 延迟、磁盘性能、成员健康状态
- 检查控制平面节点是否 CPU/IO 打满
- 检查 admission webhook 是否过慢
处理
- etcd 使用更稳定的 SSD
- 降低控制平面与业务抢占
- 排查慢 webhook
- 避免大批量对象瞬时写入
安全/性能最佳实践
控制平面 HA 和弹性扩缩容都不是纯“功能题”,安全和性能最好一起设计。
安全最佳实践
1. 控制平面入口最小暴露
- API Server 仅开放给管理网络或可信来源
- 优先走内网 LB
- 开启审计日志
2. etcd 加密与访问隔离
- etcd 只允许控制平面访问
- 使用 TLS 双向认证
- 对 Secret 启用静态加密
3. Autoscaler 权限最小化
- Cluster Autoscaler 使用最小 IAM/RBAC 权限
- 只允许管理明确的节点组
- 避免通配式高权限策略
4. 节点加入流程可信
- 节点自举证书要可控
- 节点镜像基线统一
- kubelet 配置标准化
性能最佳实践
1. 给系统组件预留资源
- 控制平面节点不要混跑重业务
- worker 节点为 kubelet、网络插件、日志代理预留系统资源
2. 资源请求设置要真实
- request 不是随便填的“占位符”
- 建议基于监控数据逐步校准
- 用历史分位值做参考,而不是凭感觉估
3. 为关键业务设计多层弹性
- HPA 扩 Pod
- CA 扩节点
- 关键服务保留基础容量
- 配合 PDB 防止缩容过猛
4. 缩容要保守
缩容比扩容更容易出事故。建议:
- 设置足够长的缩容冷却时间
- 避免业务高峰时自动缩容
- 对有连接状态的服务谨慎驱逐
一个推荐的落地架构模板
对于多数中级团队,我更推荐下面这个模板:
flowchart LR
subgraph CP[控制平面]
LB[内网 LB/VIP]
CP1[CP1]
CP2[CP2]
CP3[CP3]
ETCD[(3节点 etcd)]
LB --> CP1
LB --> CP2
LB --> CP3
CP1 --> ETCD
CP2 --> ETCD
CP3 --> ETCD
end
subgraph NP[工作节点池]
NG1[通用节点池]
NG2[计算型节点池]
NG3[系统保留节点池]
end
subgraph AS[弹性系统]
HPA[HPA]
CA[Cluster Autoscaler]
MON[metrics-server/Prometheus]
end
MON --> HPA
HPA --> NG1
HPA --> NG2
CA --> NG1
CA --> NG2
这个模板的好处是:
- 控制平面职责清晰
- 工作节点池按用途拆分
- 系统组件与业务组件可以分层治理
- 后续扩展 GPU 池、批处理池也不难
总结
如果只记住一句话,我希望是这句:
Kubernetes 集群高可用的关键,不只是“控制平面有几个节点”,而是控制平面稳定性、调度真实性和节点弹性链路是否能协同工作。
落地时可以按下面的优先级执行:
-
先补控制平面 HA
- 3 控制平面
- 统一 LB/VIP
- etcd 备份与健康检查
-
再打通弹性链路
- HPA
- metrics-server
- Cluster Autoscaler
- 合理的节点组划分
-
最后做资源治理
- 给 Pod 设置真实 request/limit
- 检查调度约束
- 优化镜像、启动时间和缩容策略
边界条件也要明确:
- 如果你的业务流量极其平稳,自动扩缩容的收益可能没那么大
- 如果节点启动时间很长,单靠即时扩容很难解决突发峰值
- 如果团队还不具备 etcd 和控制平面排障能力,先把自动化和监控补齐,再追求更复杂的架构
真正实用的 Kubernetes 架构,不是参数堆得多,而是故障来了能扛住,高峰来了能跟上,低峰时还能省钱。这才是高可用和弹性扩缩容在生产环境里真正的价值。