Kubernetes 集群架构实战:从控制平面高可用到工作节点弹性扩缩容的设计与落地
Kubernetes 用久了,很多团队都会从“能跑”走到“敢不敢扛生产”。
我见过不少集群,前期只是单控制平面加几个工作节点,测试环境还行,一旦上生产,问题就集中爆发:控制面挂了没人敢动、节点扩容靠手工、资源打满后业务抖动、etcd 延迟高导致整个 API 卡住。
这篇文章我换一个更偏“架构落地”的角度来讲:不是只说 Kubernetes 组件是什么,而是从“怎么把一个集群设计成稳定、可扩、可维护”的目标出发,把控制平面高可用和工作节点弹性扩缩容串成一套方案。
背景与问题
一个可用于生产的 Kubernetes 集群,通常会遇到三类问题:
-
控制平面单点
- 单 master 宕机后,已有 Pod 可能继续跑,但调度、变更、扩缩容、发布都会受影响。
- 证书、配置、etcd 数据如果没有规范管理,恢复成本很高。
-
工作节点弹性不足
- 业务峰值来了,Pod Pending,但节点没法自动扩。
- 低峰期节点又闲着,资源浪费。
- 扩容快,缩容难,尤其担心误伤有状态或关键业务。
-
架构设计与运维脱节
- 只看到了 kube-apiserver、scheduler、controller-manager,却没有把负载均衡、etcd、CNI、监控、自动化纳入整体。
- 集群看起来“组件齐全”,但缺少容量估算、故障边界和排障路径。
所以,生产级 Kubernetes 架构要解决的不是“装起来”,而是下面这几个目标:
- 控制平面任一节点故障后,集群仍可管理
- 工作节点能随业务负载自动扩缩
- 关键组件具备健康检查、监控和故障恢复能力
- 设计上能接受边界:不是无限高可用,而是在成本和复杂度之间做平衡
方案对比与取舍分析
在进入细节前,先把常见方案摆清楚。
控制平面高可用的主流方案
| 方案 | 特点 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 堆叠式 etcd(stacked etcd) | etcd 与控制平面同机部署 | 架构简单,部署成本低 | 故障域耦合较高 | 中小规模集群 |
| 外部 etcd | etcd 独立部署 | 控制平面与存储解耦,可靠性更好 | 运维复杂度更高 | 中大型生产集群 |
| 单 LB + 多控制平面 | 通过 VIP/LB 访问 API Server | 使用方便 | LB 自身需高可用 | 常见标准方案 |
| 云厂商托管控制平面 | 控制面由云提供 | 稳定、维护成本低 | 可控性有限、成本偏高 | 云上优先 |
工作节点扩缩容方案
| 方案 | 作用对象 | 常用组件 | 说明 |
|---|---|---|---|
| HPA | Pod 副本数 | metrics-server / Prometheus Adapter | 根据 CPU、内存或自定义指标扩 Pod |
| VPA | Pod 资源请求 | Vertical Pod Autoscaler | 自动建议或调整 requests/limits |
| Cluster Autoscaler | 节点数 | Cluster Autoscaler | Pod 放不下时扩节点,节点空闲时缩节点 |
| Karpenter 等新方案 | 节点供给 | Karpenter | 更灵活地按工作负载创建节点 |
一句话概括:
- HPA 解决“Pod 不够”
- Cluster Autoscaler 解决“节点不够”
- 两者搭配,才是完整弹性
核心原理
这一节重点讲两个核心:控制平面如何做到高可用,以及工作节点如何实现弹性扩缩容。
1. 控制平面高可用原理
Kubernetes 控制平面主要包含:
- kube-apiserver
- kube-scheduler
- kube-controller-manager
- etcd
其中真正的“中枢”是两层:
- API Server:所有控制入口
- etcd:集群状态存储
如果 API Server 只部署一个实例,那么它就是事实上的单点;如果 etcd 只有单节点,那么控制面的“脑子”仍然是单点。
推荐拓扑
- 3 个控制平面节点
- 前面放一个四层负载均衡器或 VIP
- etcd 采用 3 节点奇数仲裁
flowchart TB
U[运维人员 / CI/CD / kubectl] --> LB[LB / VIP]
LB --> CP1[Control Plane 1<br/>apiserver scheduler controller-manager etcd]
LB --> CP2[Control Plane 2<br/>apiserver scheduler controller-manager etcd]
LB --> CP3[Control Plane 3<br/>apiserver scheduler controller-manager etcd]
CP1 --- ETCD[(etcd quorum)]
CP2 --- ETCD
CP3 --- ETCD
CP1 --> W1[Worker 1]
CP2 --> W2[Worker 2]
CP3 --> W3[Worker N]
为什么通常是 3 个控制平面节点
因为 etcd 基于 Raft,需要多数派仲裁。
- 1 节点:允许故障 0
- 2 节点:允许故障 0,没意义
- 3 节点:允许故障 1
- 5 节点:允许故障 2,但写入延迟和运维成本提高
所以绝大多数中型生产环境,3 节点是性价比最高的选择。
2. 节点弹性扩缩容原理
很多人第一次接触自动扩缩容,会误以为 HPA 就够了。实际上,HPA 只能增加 Pod 副本,如果集群没有足够节点承载这些 Pod,扩容是无效的。
工作链路
- HPA 发现指标升高,增加 Deployment 副本
- 调度器尝试调度新 Pod
- 如果节点资源不足,Pod 进入 Pending
- Cluster Autoscaler 发现“有 Pod 因资源不足无法调度”
- 自动增加节点组实例
- 新节点加入集群
- 调度器把 Pending Pod 放到新节点上
sequenceDiagram
participant M as Metrics
participant H as HPA
participant A as API Server
participant S as Scheduler
participant C as Cluster Autoscaler
participant N as Node Group
M->>H: CPU/自定义指标升高
H->>A: 调整 Deployment replicas
A->>S: 创建待调度 Pod
S-->>A: 资源不足,Pod Pending
C->>A: 发现不可调度 Pod
C->>N: 扩容节点组
N-->>A: 新节点注册
S->>A: 将 Pod 调度到新节点
缩容为什么比扩容更难
因为缩容会碰到这些问题:
- 节点上有不可驱逐 Pod
- PodDisruptionBudget 限制驱逐
- 本地存储或 DaemonSet 占用
- 节点虽然低负载,但上面跑着关键业务
所以“自动缩容”一定要加边界:
- 设置合理的 scale-down delay
- 对关键工作负载打标签,避免随意驱逐
- 把有状态服务和无状态服务放到不同节点池
3. 分层架构建议
一个更稳妥的生产设计,一般会把节点池分层:
- 控制平面节点池:固定,不参与业务调度
- 系统节点池:跑 CoreDNS、Ingress、监控、日志等
- 业务节点池:承载普通应用,可自动扩缩
- 专用节点池:GPU、大内存、高 IO、批处理等
flowchart LR
subgraph ControlPlane
CP[3 x Control Plane]
end
subgraph SystemPool
SYS[系统组件节点池]
end
subgraph AppPool
APP[业务弹性节点池]
end
subgraph SpecialPool
GPU[GPU/大内存/专用节点池]
end
CP --> SYS
CP --> APP
CP --> GPU
这种分层的好处很直接:
- 系统组件不容易被业务挤占
- 弹性伸缩只作用于业务池,风险收敛
- 特殊资源不污染通用节点池
容量估算
高可用和弹性不是“感觉差不多就行”,要有一个粗略估算模型。
控制平面容量
中型集群可以从下面几个维度估:
- 节点数
- Pod 总数
- 每秒 API 请求量
- 控制器与 operator 数量
- CRD 对象规模
一个经验值,不是绝对标准:
- 50~100 节点规模:3 控制平面基本够用
- etcd 磁盘必须使用低延迟 SSD
- apiserver、etcd 监控比 CPU 更要紧,重点看:
- etcd fsync 延迟
- apiserver 请求延迟
- watch 数量
- admission webhook 响应时间
工作节点容量
建议至少预留:
- CPU 预留 10%~20%
- 内存预留 15%~25%
- 每个节点给 kubelet、systemd、CNI、日志代理留出系统资源
如果你的 HPA 目标 CPU 是 70%,而节点常态已经跑到 80%,那自动扩容一定会迟钝。
原因很简单:Pod 先想扩,节点又没余量,最终全靠节点扩容救火,链路就慢了。
实战代码(可运行)
下面我给一套“最小可用”的实战配置,重点演示:
- 工作负载部署
- HPA 自动扩容
- 使用节点组标签与亲和性隔离业务池
- 验证扩容链路
说明:
Cluster Autoscaler 本身通常依赖具体云厂商节点组,配置方式因环境不同而异。
为了保证示例可运行,这里先给出通用 Kubernetes 资源;只要你的集群已经安装metrics-server,并且节点池具备自动扩容能力,就能看到完整链路。
1. 创建专用业务节点标签
先给业务弹性节点打标签:
kubectl label node worker-1 node-pool=app --overwrite
kubectl label node worker-2 node-pool=app --overwrite
如果是云上节点组,通常应在节点池模板层面自动注入标签,而不是手工执行。
2. 部署一个会消耗 CPU 的应用
apiVersion: apps/v1
kind: Deployment
metadata:
name: cpu-demo
namespace: default
spec:
replicas: 2
selector:
matchLabels:
app: cpu-demo
template:
metadata:
labels:
app: cpu-demo
spec:
nodeSelector:
node-pool: app
containers:
- name: cpu-demo
image: busybox:1.35
command:
- /bin/sh
- -c
- |
while true; do
i=0
while [ $i -lt 200000 ]; do
i=$((i+1))
done
done
resources:
requests:
cpu: "200m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "256Mi"
---
apiVersion: v1
kind: Service
metadata:
name: cpu-demo
namespace: default
spec:
selector:
app: cpu-demo
ports:
- port: 80
targetPort: 80
应用配置:
kubectl apply -f cpu-demo.yaml
3. 配置 HPA
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: cpu-demo-hpa
namespace: default
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: cpu-demo
minReplicas: 2
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60
应用配置:
kubectl apply -f cpu-demo-hpa.yaml
4. 查看 HPA 状态
kubectl get hpa -w
如果 metrics-server 正常,你会看到类似输出:
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
cpu-demo-hpa Deployment/cpu-demo 85%/60% 2 20 4 2m
5. 观察 Pod 是否进入 Pending
当副本继续增加,而现有节点放不下时:
kubectl get pods -o wide
kubectl describe pod <pending-pod-name>
你通常会看到:
0/2 nodes are available: 2 Insufficient cpu.
这就是 Cluster Autoscaler 触发节点扩容的前提信号。
6. 一个可运行的压测脚本
如果你的应用是真实 Web 服务,可以使用下面脚本压测。这里给一个通用 shell 版本:
#!/usr/bin/env bash
set -e
TARGET_URL=${1:-http://cpu-demo.default.svc.cluster.local}
CONCURRENCY=${2:-20}
echo "Start load test: $TARGET_URL with concurrency=$CONCURRENCY"
for i in $(seq 1 $CONCURRENCY); do
(
while true; do
wget -q -O- "$TARGET_URL" >/dev/null 2>&1 || true
done
) &
done
wait
保存为 load.sh 后执行:
bash load.sh http://your-service-url 30
7. 控制平面高可用初始化示例
如果你使用 kubeadm,下面是一份常见的高可用初始化配置示例。
这份配置重点在于:通过 controlPlaneEndpoint 统一入口,并指定 podSubnet。
apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
kubernetesVersion: v1.28.0
controlPlaneEndpoint: "10.0.0.100:6443"
networking:
podSubnet: "10.244.0.0/16"
apiServer:
certSANs:
- "10.0.0.100"
- "k8s-api.internal"
controllerManager: {}
scheduler: {}
etcd:
local:
dataDir: /var/lib/etcd
---
apiVersion: kubeadm.k8s.io/v1beta3
kind: InitConfiguration
nodeRegistration:
criSocket: unix:///run/containerd/containerd.sock
初始化首个控制平面:
kubeadm init --config kubeadm-ha.yaml --upload-certs
加入其他控制平面节点时,一般使用:
kubeadm join 10.0.0.100:6443 \
--token <token> \
--discovery-token-ca-cert-hash sha256:<hash> \
--control-plane \
--certificate-key <certificate-key>
工作节点加入:
kubeadm join 10.0.0.100:6443 \
--token <token> \
--discovery-token-ca-cert-hash sha256:<hash>
常见坑与排查
这部分我尽量写得“像在现场”,因为很多问题文档里一句带过,真排起来很费时间。
1. HPA 不生效
现象
kubectl get hpa一直显示<unknown>- 副本不增长
排查路径
先看 metrics-server:
kubectl top nodes
kubectl top pods
如果这里都拿不到数据,再看 metrics-server 状态:
kubectl get pods -n kube-system | grep metrics-server
kubectl logs -n kube-system deploy/metrics-server
常见原因
- metrics-server 没安装
- kubelet 证书校验问题
- 集群网络策略阻断 metrics 抓取
- Pod 没设置
resources.requests
我当时就踩过一个很典型的坑:Deployment 配了 limits,没配 requests,结果 HPA 指标计算很怪,扩容完全不符合预期。
2. Pod Pending,但节点就是不扩
现象
- Pod 长时间 Pending
- Cluster Autoscaler 没有新增节点
排查路径
先 describe Pod:
kubectl describe pod <pod-name>
再看 autoscaler 日志:
kubectl logs -n kube-system deploy/cluster-autoscaler
常见原因
- Pending 不是因为资源不足,而是因为:
- 节点亲和性不满足
- taint/toleration 不匹配
- PVC 没绑定
- 节点组达到最大实例数
- 云平台权限不足,无法创建实例
- 节点模板与 Pod 资源请求不匹配
如果日志里出现类似:
max node group size reached
那就不是调度问题,而是节点池容量上限问题。
3. 控制平面看似高可用,其实 API 入口是单点
现象
- 明明 3 个 master,但 VIP/LB 故障后整个集群不可管理
排查建议
检查 API 统一入口是否真正高可用:
- LB 是否双实例或托管
- 健康检查是否针对
/readyz - 后端是否只注册了存活的 apiserver
例如:
curl -k https://10.0.0.100:6443/readyz
应该返回 ok。
4. etcd 正常运行,但控制面依然卡
现象
kubectl get pods很慢- 控制器处理延迟高
- etcd 没明显宕机
常见原因
- etcd 磁盘延迟高
- admission webhook 太慢
- API Server watch 压力过大
- CRD 数量多,对象规模过大
重点看哪些指标
etcd_disk_wal_fsync_duration_secondsapiserver_request_duration_secondsapiserver_admission_webhook_duration_seconds
很多时候,问题不是“节点不够”,而是控制面的 I/O 和请求链路被拖慢了。
5. 缩容误伤业务
现象
- 节点被回收时,服务抖动
- 有状态业务重建耗时很长
排查与修正
重点看:
- 是否配置了 PodDisruptionBudget
- 是否使用了本地存储
- 是否把数据库、消息队列放进了弹性节点池
建议:
- 有状态组件尽量单独节点池
- 无状态业务才作为自动缩容主战场
- 对关键服务设置更保守的驱逐策略
安全/性能最佳实践
这一节我把“高可用”和“弹性”真正能落地的几个点收束一下。
安全最佳实践
1. 控制平面隔离
- 控制平面节点禁止部署普通业务 Pod
- 使用
taint限制调度 - API Server 仅暴露给需要访问的网段
示例:
kubectl taint nodes master-1 node-role.kubernetes.io/control-plane=:NoSchedule
2. etcd 加密与备份
- 开启静态加密存储 Secrets
- 定时做 etcd 快照
- 快照异地存储,并定期演练恢复
etcd 快照示例:
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-snapshot.db
3. 最小权限
- Cluster Autoscaler 使用最小 IAM/云权限
- 控制器、运维脚本都使用独立 ServiceAccount
- 避免直接给
cluster-admin
性能最佳实践
1. 为系统组件预留资源
建议设置:
- kubelet
system-reserved - kubelet
kube-reserved - eviction 阈值
否则节点看上去还有资源,实际上系统服务早被挤压了。
2. 正确设置 requests/limits
如果业务完全不写 requests:
- 调度器无法做出合理调度
- HPA 指标会失真
- Cluster Autoscaler 也无法准确判断需要什么节点
3. 节点池分层与污点容忍
- 系统组件池:稳定优先
- 业务池:弹性优先
- 特种池:资源匹配优先
这一点对成本和稳定性都很关键。
4. 缩容要保守
建议设置:
- 缩容冷却时间
- 最小节点数
- 非业务低峰才允许 aggressive scale-down
自动扩容是为了救火,自动缩容是为了省钱。
省钱不能以放大故障为代价。
一套推荐落地路径
如果你准备把现有集群升级成“生产可用架构”,我建议按下面顺序推进:
-
先做控制平面高可用
- 3 控制平面
- 统一 API 入口
- etcd 备份与恢复演练
-
再做节点池分层
- 系统池、业务池、专用池拆开
- 加标签、污点、亲和性
-
然后做 Pod 级弹性
- 安装 metrics-server
- 给核心 Deployment 配 requests/limits
- 引入 HPA
-
最后做节点级弹性
- 接入 Cluster Autoscaler
- 控制最大最小节点数
- 验证扩容、缩容、驱逐边界
-
补齐监控与演练
- 监控 apiserver、etcd、scheduler、autoscaler
- 做单控制面故障、节点池扩缩、etcd 恢复演练
这个顺序很重要。
很多团队一开始就上自动缩容,结果基础资源建模都没做好,最后不是没省到钱,而是故障更多。
总结
Kubernetes 集群架构要做到“可生产”,核心不是堆更多组件,而是抓住两条主线:
- 控制平面高可用:保证集群在单点故障下仍可管理
- 工作节点弹性扩缩容:保证业务在负载波动下既能扛住,也不会浪费太多资源
如果你要一个务实的结论,我会这样建议:
- 中型生产集群,优先采用 3 控制平面 + LB/VIP + 3 节点 etcd
- 节点池至少拆成 系统池 + 业务弹性池
- 自动扩缩容用 HPA + Cluster Autoscaler 组合
- 缩容要比扩容更谨慎,关键业务和有状态服务不要轻易放进激进弹性池
- 所有设计最终都要通过监控与故障演练验证,而不是停留在架构图上
最后一句很现实:
高可用不是“永不出错”,而是“出错后系统仍然能工作,团队也知道怎么恢复”。
这才是 Kubernetes 集群架构设计真正的落点。