Kubernetes 集群架构实战:从控制平面高可用到工作负载弹性扩缩的设计与落地
Kubernetes 用久了,大家很容易把关注点放在“怎么把应用跑起来”,但真正进到生产环境,问题会马上变成另一个层面:
- 控制平面挂一个节点,集群还能不能继续管?
- API Server 抖一下,发布是不是全卡住?
- 业务流量突然翻倍,Pod 扩不扩得起来?
- 扩起来以后,节点资源够不够?
- 控制平面高可用和工作负载弹性,到底应该怎么配合?
这篇文章不打算只讲概念,而是从一个可落地的集群架构视角,把“控制平面高可用”与“工作负载弹性扩缩”串起来。你可以把它理解成:如何设计一个既不容易挂、又能扛住波峰的 Kubernetes 集群。
背景与问题
很多团队在搭 Kubernetes 初期,通常会经历下面几个阶段:
-
单控制平面起步
- 快,省机器,适合测试环境
- 但一旦 master 宕机,虽然已有 Pod 可能继续运行,整个集群管理能力会明显受影响
-
增加工作节点
- 解决“能跑更多服务”的问题
- 但没有解决“集群核心组件可靠性”的问题
-
开始做 HPA
- 业务能按 CPU 或内存扩容
- 但如果节点本身资源不够,HPA 只会让 Pod Pending
-
补上 Cluster Autoscaler
- 节点能自动扩
- 但如果控制平面本身不稳,扩容决策和调度过程也会变得脆弱
所以生产集群的真实问题,不是“高可用”和“弹性扩缩”二选一,而是:
控制平面保证“能管”,工作负载弹性保证“能扛”,两者一起构成完整的生产架构。
先给结论:一个实用的生产架构长什么样
如果你让我给中型业务团队一个比较稳妥的建议,我通常会推荐下面这种形态:
- 3 个控制平面节点
- 部署
kube-apiserver、kube-controller-manager、kube-scheduler etcd使用 3 节点或外部托管
- 部署
- API Server 前置负载均衡
- 如 HAProxy、Keepalived,或云厂商 SLB/NLB
- 多工作节点池
- 按业务类型分池:通用、计算密集、内存密集、系统组件专用
- 工作负载弹性
- Pod 级别:HPA
- 节点级别:Cluster Autoscaler
- 基础可观测性
- Metrics Server、Prometheus、Alertmanager、日志系统
- 调度与隔离
- requests/limits、PDB、亲和性、污点与容忍、优先级
这个结构的核心思想是:
- 控制平面高可用负责“控制链路不中断”
- 弹性扩缩负责“业务容量自动适配”
- 资源治理负责“扩缩过程中不把自己搞挂”
核心原理
这一节我们把几个关键部件串起来。
1. 控制平面高可用的本质
Kubernetes 控制平面核心由这些组件构成:
kube-apiserver:所有请求入口kube-controller-manager:执行控制器逻辑kube-scheduler:给 Pod 选节点etcd:保存集群状态
其中真正最敏感的是两类:
- API Server
- etcd
因为:
- 没有 API Server,集群对象不能正常读写
- 没有 etcd,多数状态无法持久化和一致维护
2. etcd 为什么通常是奇数节点
etcd 基于 Raft,一致性依赖多数派。
3 节点可容忍 1 个故障,5 节点可容忍 2 个故障。
很多人一开始会觉得“4 台不是比 3 台更稳吗”,其实不是这么算的:
- 3 节点,多数派是 2
- 4 节点,多数派是 3
也就是说,4 节点并不会比 3 节点多容忍一个故障,却会增加通信复杂度。所以 etcd 常见建议是 3 节点或 5 节点。
3. 控制平面高可用的数据流
flowchart LR
User[运维/CI/CD/控制器请求] --> LB[API LB / VIP]
LB --> APIS1[kube-apiserver-1]
LB --> APIS2[kube-apiserver-2]
LB --> APIS3[kube-apiserver-3]
APIS1 --> ETCD[(etcd 集群)]
APIS2 --> ETCD
APIS3 --> ETCD
APIS1 --> CM1[kube-controller-manager]
APIS2 --> CM2[kube-controller-manager]
APIS3 --> CM3[kube-controller-manager]
APIS1 --> SCH1[kube-scheduler]
APIS2 --> SCH2[kube-scheduler]
APIS3 --> SCH3[kube-scheduler]
Worker1[Worker Node 1] --> LB
Worker2[Worker Node 2] --> LB
WorkerN[Worker Node N] --> LB
注意一个容易误解的点:
controller-manager和scheduler可以多副本部署- 但它们通常通过**leader election(主选举)**保证同一时刻只有一个实例在主导工作
- 所以高可用不是所有实例都一起干活,而是主备切换快、服务不中断
4. 弹性扩缩的真实链路
在 Kubernetes 里,弹性扩缩并不只有 HPA。
完整链路通常是:
- 业务流量增长
- Pod CPU/内存/自定义指标升高
- HPA 调整 Deployment 副本数
- 新 Pod 创建
- 调度器尝试调度
- 如果节点资源不足,Pod Pending
- Cluster Autoscaler 发现有 Pod 因资源不足无法调度
- 扩容节点池
- 新节点加入集群
- Pending Pod 被重新调度
这是一条典型的“Pod 扩缩 + 节点扩缩联动”链路。
sequenceDiagram
participant U as 用户流量
participant H as HPA
participant D as Deployment
participant S as Scheduler
participant C as Cluster Autoscaler
participant N as Node Group
U->>H: 指标升高
H->>D: 增加 replicas
D->>S: 创建新 Pod
S-->>D: 资源不足,Pod Pending
S->>C: 暴露不可调度 Pod
C->>N: 扩容节点
N-->>S: 新节点 Ready
S->>D: 重新调度 Pod
5. 为什么只做 HPA 不够
我在一些团队里见过很典型的情况:
HPA 配得很漂亮,但没有节点自动扩容。结果高峰期副本从 4 个扩到 12 个,最后 6 个 Pod Pending,业务还是抖。
原因很直接:
- HPA 解决的是**“想多跑几个 Pod”**
- Cluster Autoscaler 解决的是**“有没有地方让这些 Pod 跑”**
两者缺一不可。
方案对比与取舍分析
方案一:单控制平面 + 多工作节点
优点:
- 架构简单
- 成本最低
- 搭建和维护压力小
缺点:
- 控制平面是单点
- 适合测试、开发、小规模内部业务
- 不适合关键生产系统
方案二:多控制平面 + 堆叠式 etcd
也就是控制平面节点同时运行 etcd。
优点:
- 架构相对简单
- kubeadm 支持较成熟
- 对中小规模生产环境很常见
缺点:
- 控制平面和 etcd 资源耦合
- 排障时需要同时考虑控制组件和存储一致性
方案三:多控制平面 + 外部 etcd
优点:
- etcd 与控制平面解耦
- 更利于独立维护和容量规划
- 更适合较大规模集群
缺点:
- 部署复杂度更高
- 运维门槛明显提升
我的建议
如果是中级团队、目标是稳定落地,而不是炫技:
- 中小型生产集群:优先考虑
3 控制平面 + 堆叠式 etcd - 多集群/较大规模/合规要求高:考虑外部 etcd 或托管控制平面
- 云上优先:如果预算允许,优先使用托管 Kubernetes 控制平面,把精力放在节点池、调度和弹性治理
容量估算:不要等到高峰才发现设计偏小
架构设计里最容易被忽略的是容量预估。一个简单实用的估算方式如下。
1. 工作负载容量估算
假设你的服务单 Pod requests:
- CPU:
500m - Memory:
512Mi
目标稳态副本数 20,高峰预估翻 2 倍,即 40 副本。
那么总 requests 大致为:
- CPU:
40 * 500m = 20 vCPU - Memory:
40 * 512Mi ≈ 20Gi
再考虑:
- 系统组件开销
- 节点碎片化
- 滚动发布额外副本
- 单节点故障冗余
实际通常至少要预留 20%~30% Buffer。
2. 控制平面容量估算
控制平面更关注:
- API QPS
- 对象数量(Pod、ConfigMap、Secret、Endpoint)
- 控制器压力
- etcd IOPS 与延迟
如果你有以下特征,需要特别关注控制平面:
- 命名空间多
- 短生命周期 Job 多
- 大量 CRD/Operator
- 发布频繁
- HPA / CA 动态变化频繁
控制平面高可用不是只看“挂不挂”,还要看忙的时候抖不抖。
实战代码(可运行)
下面我给一套能直接落地验证的最小实践:
使用 Deployment + Service + HPA,并补上 requests/limits,便于演示弹性扩缩。
前提:集群已安装 Metrics Server。
如果你在本地实验环境(如 kind/minikube)测试,先确认kubectl top pod有数据。
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: web-demo
image: nginx:1.25
ports:
- containerPort: 80
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 300m
memory: 256Mi
2. 暴露 Service
apiVersion: v1
kind: Service
metadata:
name: web-demo
namespace: default
spec:
selector:
app: web-demo
ports:
- port: 80
targetPort: 80
type: ClusterIP
3. 配置 HPA
这里基于 CPU 利用率扩缩。
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: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60
4. 一次性应用
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
kubectl apply -f hpa.yaml
5. 查看效果
kubectl get deploy,svc,hpa
kubectl top pod
kubectl describe hpa web-demo-hpa
6. 制造压力验证 HPA
我们启动一个临时压测 Pod,持续请求业务服务。
kubectl run load-generator \
--image=busybox:1.35 \
--restart=Never \
-- /bin/sh -c "while true; do wget -q -O- http://web-demo; done"
然后观察:
kubectl get hpa web-demo-hpa -w
kubectl get pod -w
如果指标采集正常,几分钟内会看到副本数上升。
补一个更贴近生产的 Deployment 写法
只做 HPA 还不够,生产里至少建议加上:
readinessProbelivenessProberollingUpdatePodDisruptionBudgettopologySpreadConstraints或反亲和性
下面给一个增强版 Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-demo
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 1
selector:
matchLabels:
app: api-demo
template:
metadata:
labels:
app: api-demo
spec:
containers:
- name: api-demo
image: nginx:1.25
ports:
- containerPort: 80
resources:
requests:
cpu: 200m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 3
periodSeconds: 5
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 10
periodSeconds: 10
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: api-demo
对应的 PDB:
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: api-demo-pdb
spec:
minAvailable: 2
selector:
matchLabels:
app: api-demo
这两个配置很关键,因为:
- 高可用不是只有控制平面要高可用
- 业务工作负载自己也要具备“节点维护时不全挂”的能力
控制平面高可用的部署要点
如果你使用 kubeadm 部署高可用控制平面,重点关注这几件事。
1. API Server 入口必须稳定
所有节点上的 kubelet、kubectl、控制器访问 API Server 时,不应该直接写死某一个 master IP。
应该统一走:
- VIP
- 负载均衡域名
- 云负载均衡地址
否则你虽然部署了三个控制平面,但客户端都只打到其中一个,还是有单点风险。
2. 证书 SAN 要包含统一入口
很多高可用接入失败,不是 Kubernetes 本身的问题,而是证书里没把 LB 地址或域名加入 SAN。表现通常像这样:
Unable to connect to the server: x509: certificate is valid for ...
3. etcd 延迟比你想象中更重要
etcd 不需要很高 CPU,但对:
- 磁盘延迟
- fsync 性能
- 网络稳定性
非常敏感。
我踩过的一个坑是把 etcd 放在普通云盘上,平时看着没问题,业务发布高峰时 API Server 延迟明显飙升,最后追到根因是存储抖动。
常见坑与排查
下面这部分是生产里非常常见的故障场景,我尽量用“现象 → 原因 → 怎么查”的方式讲。
坑一:HPA 不生效
现象:
kubectl get hpa一直不扩TARGETS显示<unknown>
常见原因:
- Metrics Server 未安装
- Metrics Server 采集异常
- 容器未配置 requests,导致 HPA 无法正确计算利用率
排查命令:
kubectl top pod
kubectl get apiservices
kubectl describe hpa web-demo-hpa
kubectl logs -n kube-system deploy/metrics-server
经验建议:
- 基于 CPU/内存做 HPA 时,务必设置
resources.requests - 没有 requests,很多“利用率”判断都不可靠
坑二:HPA 扩容了,但 Pod 一直 Pending
现象:
- HPA 副本数已经增加
- 新 Pod 状态是
Pending
常见原因:
- 节点资源不足
- requests 设置过大
- 节点有污点,Pod 没有 toleration
- 亲和/反亲和规则太严格
排查命令:
kubectl get pod
kubectl describe pod <pod-name>
kubectl get nodes
kubectl describe node <node-name>
重点看事件里有没有这些字样:
Insufficient cpuInsufficient memorynode(s) had taintnode(s) didn't match Pod's node affinity
如果你已经接了 Cluster Autoscaler,还要继续看:
kubectl logs -n kube-system deployment/cluster-autoscaler
坑三:控制平面看起来是高可用,实际上 API 还是单点
现象:
- 有 3 台 master
- 但停掉其中一台后,大量组件访问异常
常见原因:
- kubeconfig 里仍写某台 master 的固定地址
- kubelet 启动参数直连某个控制平面节点
- 前置 LB 没有健康检查或健康检查不准确
排查思路:
- 查所有 kubeconfig 中的
server - 查 kubelet 实际连接的 API 地址
- 模拟下线某个控制平面节点
- 观察:
kubectl get nodes- 新 Pod 创建
- leader 切换
- ServiceAccount token 校验
- Webhook 调用
坑四:etcd 正常,但 API Server 很慢
现象:
- 集群不是完全不可用
- 但
kubectl get pod -A很慢 - 发布时卡顿明显
可能原因:
- etcd 存储延迟高
- 集群对象量过大
- watch 压力大
- API Server QPS/并发限制过低
- CRD 控制器过多
建议排查:
kubectl get --raw='/readyz?verbose'
kubectl get --raw='/livez?verbose'
kubectl top pod -n kube-system
如果你有 Prometheus,建议重点看:
- apiserver request duration
- etcd request duration
- etcd disk fsync duration
- apiserver inflight requests
坑五:滚动升级时可用副本被打没了
现象:
- 升级 Deployment 时业务闪断
- 节点维护时服务副本骤减
原因:
- 没有 PDB
maxUnavailable设置过大- Readiness Probe 不准确
- 副本本来就太少,还分布在同一节点
建议:
- 核心服务副本数至少 2 起步
- 配 PDB
- 配反亲和或拓扑分散
- Readiness 不要写成“进程启动就算健康”
安全/性能最佳实践
高可用和弹性不只是“能不能用”,还包括“用得是否稳、是否安全”。
1. 控制平面最佳实践
安全方面
- API Server 统一走 TLS
- 证书轮转要有计划
- 关闭不必要的匿名访问
- RBAC 最小权限
- 审计日志开启并留存
性能方面
- etcd 使用低延迟磁盘
- 控制平面节点与 etcd 网络尽量稳定、低抖动
- 避免在控制平面节点混跑重业务
- 大集群中合理调整 API QPS/Burst
2. 工作负载弹性最佳实践
资源治理
- 每个业务容器都配置
requests/limits - 不要把 requests 配得离谱,否则调度浪费严重
- 关键业务优先设置
PriorityClass
扩缩策略
- HPA 不要只看 CPU,必要时接入 QPS、队列长度、自定义指标
- 为 HPA 设置合理的
minReplicas - 避免扩缩阈值过于敏感,减少抖动
可用性保障
- 多副本
- PDB
- 反亲和 / 拓扑分散
- 优雅终止:
terminationGracePeriodSeconds - 配合 Ingress / Service 的连接摘除机制
3. 多节点池是非常值回票价的设计
把所有节点做成一个池,前期简单,后期很容易混乱。更推荐:
system-pool:系统组件general-pool:常规业务cpu-pool:计算密集mem-pool:内存密集spot-pool:可中断低成本任务
这样做的价值在于:
- 扩容更有针对性
- 成本更可控
- 故障域更清晰
- 调度策略更容易解释
flowchart TD
A[业务请求增长] --> B{HPA 是否触发}
B -->|是| C[增加 Pod 副本]
C --> D{节点资源是否足够}
D -->|足够| E[调度成功]
D -->|不足| F[Cluster Autoscaler 扩节点池]
F --> G[新节点加入]
G --> E
E --> H[业务容量提升]
I[控制平面节点故障] --> J{API LB 健康检查}
J -->|摘除异常节点| K[切换到健康 API Server]
K --> L[控制链路保持可用]
一套推荐落地清单
如果你正在搭一个新生产集群,我建议按这个顺序推进,不容易乱。
第一步:先把控制平面做稳
- 3 控制平面节点
- API Server 前置 LB/VIP
- etcd 3 节点
- 定期备份 etcd
- 验证单节点故障切换
第二步:把监控补齐
- Metrics Server
- Prometheus + Alertmanager
- 控制平面关键指标看板
- etcd 延迟与容量告警
第三步:业务工作负载规范化
- requests/limits
- readiness/liveness
- PDB
- 副本数下限
- 节点分布策略
第四步:再上弹性扩缩
- HPA 先从 CPU 开始
- 核心业务逐步接入更贴近业务的指标
- 再开启 Cluster Autoscaler
- 做压测验证扩缩链路闭环
第五步:故障演练
至少做这几类:
- 停 1 台控制平面节点
- 停 1 台工作节点
- 模拟节点池资源打满
- 验证 HPA 扩容
- 验证 CA 扩节点
- 验证发布期间 PDB 生效
很多团队其实不是架构没设计,而是没演练。纸面高可用和真实高可用,差的往往就是这一步。
总结
回到文章开头那个核心问题:
Kubernetes 集群怎么从“能跑”走到“稳、弹、可控”?
答案可以浓缩成三句话:
- 控制平面高可用,解决的是集群“有没有大脑”的问题
- 工作负载弹性扩缩,解决的是业务“能不能扛住波峰”的问题
- 资源治理与调度策略,决定了高可用和弹性是否真的能落地
如果你现在要开始做,我建议优先级这样排:
- 先做
3 控制平面 + 稳定 API 入口 - 再做
requests/limits + 可观测性 - 然后做
HPA + Cluster Autoscaler - 最后补上
PDB、拓扑分散、节点池分层
边界条件也很明确:
- 小规模测试环境 没必要一开始就上复杂高可用
- 关键生产环境 不要只做 HPA,不做节点扩容和故障演练
- 大规模复杂场景 优先考虑托管控制平面,减少自维护成本
真正成熟的 Kubernetes 集群,不是组件堆得多,而是每一层设计都能回答一个问题:
控制面故障时还能不能管?
流量暴涨时还能不能扩?
扩了之后会不会把自己拖垮?
如果这三个问题你都能给出明确、可验证的答案,那这套集群架构就已经相当靠谱了。