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

《Kubernetes 集群架构实战:从控制平面高可用到工作节点弹性扩缩容的设计与落地》

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

Kubernetes 集群架构实战:从控制平面高可用到工作节点弹性扩缩容的设计与落地

Kubernetes 用久了,很多团队都会从“能跑”走到“敢不敢扛生产”。
我见过不少集群,前期只是单控制平面加几个工作节点,测试环境还行,一旦上生产,问题就集中爆发:控制面挂了没人敢动、节点扩容靠手工、资源打满后业务抖动、etcd 延迟高导致整个 API 卡住。

这篇文章我换一个更偏“架构落地”的角度来讲:不是只说 Kubernetes 组件是什么,而是从“怎么把一个集群设计成稳定、可扩、可维护”的目标出发,把控制平面高可用和工作节点弹性扩缩容串成一套方案。


背景与问题

一个可用于生产的 Kubernetes 集群,通常会遇到三类问题:

  1. 控制平面单点

    • 单 master 宕机后,已有 Pod 可能继续跑,但调度、变更、扩缩容、发布都会受影响。
    • 证书、配置、etcd 数据如果没有规范管理,恢复成本很高。
  2. 工作节点弹性不足

    • 业务峰值来了,Pod Pending,但节点没法自动扩。
    • 低峰期节点又闲着,资源浪费。
    • 扩容快,缩容难,尤其担心误伤有状态或关键业务。
  3. 架构设计与运维脱节

    • 只看到了 kube-apiserver、scheduler、controller-manager,却没有把负载均衡、etcd、CNI、监控、自动化纳入整体。
    • 集群看起来“组件齐全”,但缺少容量估算、故障边界和排障路径。

所以,生产级 Kubernetes 架构要解决的不是“装起来”,而是下面这几个目标:

  • 控制平面任一节点故障后,集群仍可管理
  • 工作节点能随业务负载自动扩缩
  • 关键组件具备健康检查、监控和故障恢复能力
  • 设计上能接受边界:不是无限高可用,而是在成本和复杂度之间做平衡

方案对比与取舍分析

在进入细节前,先把常见方案摆清楚。

控制平面高可用的主流方案

方案特点优点缺点适用场景
堆叠式 etcd(stacked etcd)etcd 与控制平面同机部署架构简单,部署成本低故障域耦合较高中小规模集群
外部 etcdetcd 独立部署控制平面与存储解耦,可靠性更好运维复杂度更高中大型生产集群
单 LB + 多控制平面通过 VIP/LB 访问 API Server使用方便LB 自身需高可用常见标准方案
云厂商托管控制平面控制面由云提供稳定、维护成本低可控性有限、成本偏高云上优先

工作节点扩缩容方案

方案作用对象常用组件说明
HPAPod 副本数metrics-server / Prometheus Adapter根据 CPU、内存或自定义指标扩 Pod
VPAPod 资源请求Vertical Pod Autoscaler自动建议或调整 requests/limits
Cluster Autoscaler节点数Cluster AutoscalerPod 放不下时扩节点,节点空闲时缩节点
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,扩容是无效的

工作链路

  1. HPA 发现指标升高,增加 Deployment 副本
  2. 调度器尝试调度新 Pod
  3. 如果节点资源不足,Pod 进入 Pending
  4. Cluster Autoscaler 发现“有 Pod 因资源不足无法调度”
  5. 自动增加节点组实例
  6. 新节点加入集群
  7. 调度器把 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 先想扩,节点又没余量,最终全靠节点扩容救火,链路就慢了。


实战代码(可运行)

下面我给一套“最小可用”的实战配置,重点演示:

  1. 工作负载部署
  2. HPA 自动扩容
  3. 使用节点组标签与亲和性隔离业务池
  4. 验证扩容链路

说明:
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_seconds
  • apiserver_request_duration_seconds
  • apiserver_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

自动扩容是为了救火,自动缩容是为了省钱。
省钱不能以放大故障为代价。


一套推荐落地路径

如果你准备把现有集群升级成“生产可用架构”,我建议按下面顺序推进:

  1. 先做控制平面高可用

    • 3 控制平面
    • 统一 API 入口
    • etcd 备份与恢复演练
  2. 再做节点池分层

    • 系统池、业务池、专用池拆开
    • 加标签、污点、亲和性
  3. 然后做 Pod 级弹性

    • 安装 metrics-server
    • 给核心 Deployment 配 requests/limits
    • 引入 HPA
  4. 最后做节点级弹性

    • 接入 Cluster Autoscaler
    • 控制最大最小节点数
    • 验证扩容、缩容、驱逐边界
  5. 补齐监控与演练

    • 监控 apiserver、etcd、scheduler、autoscaler
    • 做单控制面故障、节点池扩缩、etcd 恢复演练

这个顺序很重要。
很多团队一开始就上自动缩容,结果基础资源建模都没做好,最后不是没省到钱,而是故障更多。


总结

Kubernetes 集群架构要做到“可生产”,核心不是堆更多组件,而是抓住两条主线:

  • 控制平面高可用:保证集群在单点故障下仍可管理
  • 工作节点弹性扩缩容:保证业务在负载波动下既能扛住,也不会浪费太多资源

如果你要一个务实的结论,我会这样建议:

  • 中型生产集群,优先采用 3 控制平面 + LB/VIP + 3 节点 etcd
  • 节点池至少拆成 系统池 + 业务弹性池
  • 自动扩缩容用 HPA + Cluster Autoscaler 组合
  • 缩容要比扩容更谨慎,关键业务和有状态服务不要轻易放进激进弹性池
  • 所有设计最终都要通过监控与故障演练验证,而不是停留在架构图上

最后一句很现实:
高可用不是“永不出错”,而是“出错后系统仍然能工作,团队也知道怎么恢复”。
这才是 Kubernetes 集群架构设计真正的落点。


分享到:

上一篇
《Spring Boot 中基于 Spring Cache + Redis 的多级缓存实战:提升接口性能与缓存一致性治理》
下一篇
《Node.js 中基于 Worker Threads 与消息队列的高并发任务处理实战-200》