云原生05-从手动扩缩容到Auto Scaling:K8s HPA/KEDA/VPA怎么选?调度器不工作?可能是这5个参数没配置对
CSDN多平台一键发布功能开通链接
https://mp.csdn.net/vip?utm_source=weitingfu
你是否遇到过CPU天天跑不满、一扩容就出问题、Pod被调度到同一个节点导致局部过载的尴尬?Kubernetes的调度器其实很聪明,只是你没告诉它想要的姿势。本文将手把手教你玩转K8s调度策略。
目录
- 调度器:K8s的"智能管家"
- 调度流程揭秘:从predicates到binding
- DefaultPreemption:高优先级Pod的"插队"艺术
- 资源配额三剑客:LimitRange、ResourceQuota、PDB
- 自动扩缩容:HPA、VPA、KEDA怎么选?
- 实战案例:从10%到80%的资源利用率
- 总结与思考
1. 调度器:K8s的"智能管家"
想象一下,你是一个大型公寓的物业经理。每天有几十个租客(Pod)要入住,而你的公寓楼(集群)有上百个房间(节点)。每个租客有不同的需求:有的要朝南的房间(节点亲和性),有的不要和吵闹的邻居住一起(反亲和性),有的需要特定的家具(资源需求)。
Kubernetes调度器就是那个物业经理。
但问题是——如果你不给它明确的规则,它就会像刚入职的新人一样,随机分配房间。结果?有的楼层挤爆了,有的楼层空荡荡。CPU天天跑不满,一扩容就出问题,Pod被调度到同一个节点导致局部过载…
💡效率技巧:调度器不是不聪明,是你没告诉它"想要的姿势"。
2. 调度流程揭秘:从predicates到binding
K8s调度器的工作流程可以用三步概括:
┌─────────────────────────────────────────────────────────────────┐ │ K8s 调度流程架构图 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │ │ │ 待调度 │───▶│ Predicates │───▶│ Priorities │ │ │ │ Pod │ │ (前置过滤) │ │ (优先级排序) │ │ │ └─────────────┘ └──────┬──────┘ └────────┬────────┘ │ │ │ │ │ │ ▼ ▼ │ │ ┌─────────────────┐ ┌──────────────┐ │ │ │ 过滤掉不合 │ │ 为每个节点 │ │ │ │ 适的节点 │ │ 打分排序 │ │ │ │ (硬约束检查) │ │ (软约束优化)│ │ │ └─────────────────┘ └──────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────┐ │ │ │ Select + Bind │ │ │ │ (选择并绑定节点) │ │ │ └─────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────┘2.1 Predicates:前置过滤(硬约束)
Predicates就像相亲时的"硬性条件"——不符合的直接pass,没得商量。
常见的predicates包括:
| Predicate | 作用 | 示例 |
|---|---|---|
PodFitsResources | 检查节点资源是否充足 | CPU/内存是否够 |
PodFitsHost | 检查是否指定了特定节点 | nodeName约束 |
PodFitsHostPorts | 检查端口是否冲突 | 80端口是否被占用 |
PodMatchNodeSelector | 检查节点标签匹配 | disktype=ssd |
NoDiskConflict | 检查存储卷冲突 | 同一个PVC不能被多个Pod挂载 |
# 示例:使用nodeSelector进行硬约束 apiVersion: v1 kind: Pod metadata: name: nginx-ssd spec: nodeSelector: disktype: ssd # 硬性要求:必须在SSD节点上运行 containers: - name: nginx image: nginx⚠️避坑警告:如果所有节点都不满足predicates,Pod会一直处于Pending状态。这时候别怪调度器,去检查你的约束条件是不是写得太苛刻了。
2.2 Priorities:优先级排序(软约束)
如果说predicates是"硬性条件",priorities就是"加分项"。调度器会给每个通过过滤的节点打分,选择得分最高的那个。
常见的priorities包括:
| Priority | 作用 | 权重 |
|---|---|---|
LeastRequestedPriority | 优先选择资源空闲多的节点 | 默认启用 |
BalancedResourceAllocation | 平衡CPU和内存使用 | 默认启用 |
ServiceSpreadingPriority | 分散同一Service的Pod | 避免单点故障 |
ImageLocalityPriority | 优先选择已有镜像的节点 | 减少拉取时间 |
NodeAffinityPriority | 根据节点亲和性打分 | 软约束 |
# 示例:使用preferredDuringScheduling进行软约束 apiVersion: v1 kind: Pod metadata: name: nginx-preferred spec: affinity: nodeAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 preference: matchExpressions: - key: disktype operator: In values: - ssd # 软约束:优先选择SSD节点,但不是必须的 containers: - name: nginx image: nginx💡效率技巧:软约束的weight范围是1-100,数值越大优先级越高。如果你有多个软约束,合理分配权重可以让调度器做出更智能的选择。
2.3 Select + Bind:选择并绑定
最后一步,调度器会选择得分最高的节点,然后调用API将Pod绑定到该节点上。这个过程是原子性的,确保不会出现多个Pod被调度到同一个节点导致资源超售的情况。
🎭幽默时间:调度器选节点的过程,就像你在淘宝上买东西——先筛选出符合条件的(predicates),然后按销量/评分排序(priorities),最后点击"立即购买"(bind)。只不过调度器不会手抖点错,也不会半夜冲动消费。
3. DefaultPreemption:高优先级Pod的"插队"艺术
想象这样一个场景:你是一个VIP会员,去餐厅吃饭发现满座了。这时候服务员走过来,礼貌地请一位普通会员让出座位——这就是抢占(Preemption)。
在K8s中,高优先级的Pod可以"抢占"低优先级Pod的资源。
3.1 抢占机制工作原理
┌─────────────────────────────────────────────────────────────────┐ │ 抢占机制流程图 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────┐ │ │ │ 高优先级Pod │ │ │ │ 无法调度 │ │ │ └──────┬──────┘ │ │ │ │ │ ▼ │ │ ┌─────────────┐ ┌─────────────────┐ ┌─────────────┐ │ │ │ 查找可被 │───▶│ 选择牺牲者 │───▶│ 驱逐低优 │ │ │ │ 抢占的节点 │ │ (最低优先级) │ │ 先级Pod │ │ │ └─────────────┘ └─────────────────┘ └──────┬──────┘ │ │ │ │ │ ▼ │ │ ┌─────────────┐ │ │ │ 调度高优先 │ │ │ │ 级Pod到节点 │ │ │ └─────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────┘3.2 配置PriorityClass
# 定义高优先级类 apiVersion: scheduling.k8s.io/v1 kind: PriorityClass metadata: name: high-priority value: 1000000 # 优先级数值,越大优先级越高 globalDefault: false # 不是默认优先级 description: "用于关键业务应用" --- # 定义低优先级类 apiVersion: scheduling.k8s.io/v1 kind: PriorityClass metadata: name: low-priority value: 1000 globalDefault: false description: "用于开发测试环境"# 使用PriorityClass apiVersion: v1 kind: Pod metadata: name: critical-app spec: priorityClassName: high-priority # 指定高优先级 containers: - name: app image: critical-app:latest resources: requests: memory: "4Gi" cpu: "2"⚠️避坑警告:抢占会导致低优先级Pod被驱逐,可能引发服务中断。生产环境中请谨慎使用,并确保低优先级应用有完善的优雅退出机制。
3.3 抢占策略配置
# kube-scheduler配置 apiVersion: kubescheduler.config.k8s.io/v1 kind: KubeSchedulerConfiguration profiles: - schedulerName: default-scheduler plugins: preemption: enabled: - name: DefaultPreemption pluginConfig: - name: DefaultPreemption args: minCandidateNodesPercentage: 10 # 至少检查10%的节点 minCandidateNodesAbsolute: 100 # 至少检查100个节点💡效率技巧:合理设置minCandidateNodesPercentage和minCandidateNodesAbsolute可以在调度性能和抢占效果之间取得平衡。如果集群很大,可以适当降低百分比以减少计算开销。
4. 资源配额三剑客:LimitRange、ResourceQuota、PDB
如果说调度器是"物业经理",那资源配额就是"小区管理规定"。没有规矩,不成方圆。
4.1 LimitRange:默认资源限制
LimitRange用于设置命名空间内Pod/容器的默认资源请求和限制。
apiVersion: v1 kind: LimitRange metadata: name: cpu-memory-limits namespace: production spec: limits: # 容器级别限制 - default: cpu: "1000m" memory: "512Mi" defaultRequest: cpu: "100m" memory: "128Mi" type: Container # Pod级别限制 - max: cpu: "2000m" memory: "1Gi" min: cpu: "50m" memory: "64Mi" type: Pod| 字段 | 作用 |
|---|---|
default | 容器未指定limit时的默认值 |
defaultRequest | 容器未指定request时的默认值 |
max | 资源上限(硬限制) |
min | 资源下限 |
⚠️避坑警告:如果不设置LimitRange,用户可能会创建没有资源限制的Pod,导致节点资源被耗尽,影响其他应用。
4.2 ResourceQuota:命名空间资源配额
ResourceQuota用于限制命名空间可以使用的资源总量。
apiVersion: v1 kind: ResourceQuota metadata: name: team-quota namespace: team-a spec: hard: # 计算资源 requests.cpu: "20" requests.memory: 50Gi limits.cpu: "40" limits.memory: 100Gi # 对象数量限制 pods: "50" services: "10" secrets: "20" configmaps: "20" persistentvolumeclaims: "10" # 存储资源 requests.storage: 500Gi standard.storageclass.storage.k8s.io/requests.storage: 300Gi查看配额使用情况:
$ kubectl describe quota team-quota -n team-a Name: team-quota Namespace: team-a Resource Used Hard -------- ---- ---- configmaps 3 20 limits.cpu 8500m 40 limits.memory 20Gi 100Gi pods 12 50 requests.cpu 3200m 20 requests.memory 8Gi 50Gi secrets 5 20 services 3 10🎭幽默时间:ResourceQuota就像你妈妈给你的零花钱限额——你可以随便花,但超过限额就别想再要。不同的是,K8s不会听你撒娇。
4.3 PodDisruptionBudget:优雅中断预算
PDB用于控制在自愿中断(如节点维护、升级)期间,同时不可用的Pod数量。
apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: app-pdb namespace: production spec: minAvailable: 2 # 至少保留2个Pod可用 # 或者使用 maxUnavailable: 1 表示最多允许1个不可用 selector: matchLabels: app: critical-service# 使用maxUnavailable的示例 apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: web-pdb spec: maxUnavailable: 25% # 最多25%的Pod不可用 selector: matchLabels: app: web-server💡效率技巧:对于关键业务,建议设置minAvailable;对于可以容忍部分中断的服务,使用maxUnavailable更灵活。
5. 自动扩缩容:HPA、VPA、KEDA怎么选?
手动扩缩容?那是上个世纪的玩法。现代云原生应用需要自动扩缩容。
5.1 三种扩容方式对比
┌─────────────────────────────────────────────────────────────────┐ │ 自动扩缩容方案对比 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ HPA (Horizontal) VPA (Vertical) KEDA │ │ ┌─────────────┐ ┌─────────────┐ ┌───────────┐ │ │ │ 增加Pod │ │ 增加单Pod │ │ 基于事件 │ │ │ │ 数量 │ │ 资源配额 │ │ 驱动 │ │ │ └──────┬──────┘ └──────┬──────┘ └─────┬─────┘ │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ 适合:无状态应用 适合:有状态应用 适合:事件驱动│ │ 响应:< 30秒 响应:~3分钟 响应:< 30秒 │ │ 场景:Web服务 场景:数据库 场景:消息队列│ │ │ └─────────────────────────────────────────────────────────────────┘| 特性 | HPA | VPA | KEDA |
|---|---|---|---|
| 扩容维度 | 水平(Pod数量) | 垂直(Pod资源) | 水平(基于事件) |
| 响应时间 | < 30秒 | ~3分钟 | < 30秒 |
| 适用场景 | 无状态Web服务 | 有状态应用、数据库 | 事件驱动架构 |
| 指标来源 | CPU/内存/自定义 | 资源实际使用 | Kafka/RabbitMQ/SQS等 |
| 是否开源 | 内置 | 开源组件 | 开源项目 |
5.2 HPA:水平Pod自动扩缩容
apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: web-hpa spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: web-app minReplicas: 3 # 最少保留3个Pod maxReplicas: 50 # 最多扩展到50个Pod metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70 # CPU平均使用率70%时扩容 - type: Resource resource: name: memory target: type: Utilization averageUtilization: 80 # 内存平均使用率80%时扩容 behavior: # 扩缩容行为配置 scaleUp: stabilizationWindowSeconds: 60 # 扩容前观察60秒 policies: - type: Percent value: 100 periodSeconds: 60 # 每分钟最多扩容100% scaleDown: stabilizationWindowSeconds: 300 # 缩容前观察5分钟 policies: - type: Percent value: 10 periodSeconds: 60 # 每分钟最多缩容10%💡效率技巧:stabilizationWindowSeconds可以防止因指标抖动导致的频繁扩缩容。扩容可以激进一点(60秒),缩容要保守一点(300秒)。
5.3 VPA:垂直Pod自动扩缩容
VPA会分析Pod的实际资源使用,自动调整CPU和内存的request/limit。
apiVersion: autoscaling.k8s.io/v1 kind: VerticalPodAutoscaler metadata: name: db-vpa spec: targetRef: apiVersion: apps/v1 kind: StatefulSet name: postgres updatePolicy: updateMode: "Auto" # 自动更新模式 # 其他模式:Off(仅建议)、Initial(仅初始)、Recreate(重建更新) resourcePolicy: containerPolicies: - containerName: postgres minAllowed: cpu: 50m memory: 256Mi maxAllowed: cpu: 4 memory: 8Gi controlledResources: ["cpu", "memory"]⚠️避坑警告:VPA和HPA不能同时使用CPU/内存指标,否则会冲突。如果必须同时使用,建议HPA使用自定义指标,VPA管理资源分配。
5.4 KEDA:事件驱动的自动扩缩容
KEDA(Kubernetes Event-driven Autoscaling)支持基于各种事件源的弹性扩容。
# KEDA + Kafka 示例 apiVersion: keda.sh/v1alpha1 kind: ScaledObject metadata: name: kafka-scaled-app namespace: default spec: scaleTargetRef: name: consumer-app pollingInterval: 10 # 检查事件频率(秒) cooldownPeriod: 300 # 缩容冷却时间(秒) minReplicaCount: 0 # 可以缩容到0 maxReplicaCount: 100 # 最大副本数 triggers: - type: kafka metadata: bootstrapServers: kafka-cluster:9092 consumerGroup: my-group topic: orders lagThreshold: "100" # 积压超过100条时扩容 activationLagThreshold: "10" # 激活阈值支持的事件源包括:
- 消息队列:Kafka、RabbitMQ、AWS SQS、Azure Service Bus、Google Pub/Sub
- 数据库:PostgreSQL、MySQL、MongoDB
- 云服务:AWS CloudWatch、Azure Monitor、GCP Stackdriver
- 其他:Prometheus、Cron、GitHub Webhook、Liiklus等
🎭幽默时间:KEDA就像一个消息灵通的"黄牛党"——哪里有需求(消息积压),它就立刻安排人手(扩容Pod)。没活干的时候,它也不会白养着人(缩容到0)。
6. 实战案例:从10%到80%的资源利用率
某电商平台的Kubernetes集群长期面临资源利用率低的问题:
| 指标 | 优化前 | 优化后 |
|---|---|---|
| CPU平均利用率 | 10% | 78% |
| 内存平均利用率 | 15% | 72% |
| 节点数量 | 50台 | 12台 |
| 月度云成本 | ¥50万 | ¥12万 |
6.1 问题诊断
# 查看节点资源使用情况 $ kubectl top nodes NAME CPU(cores) CPU% MEMORY(bytes) MEMORY% node-01 250m 6% 4Gi 12% node-02 300m 7% 5Gi 15% ... # 查看Pod资源请求vs实际使用 $ kubectl describe node node-01 | grep -A 5 "Allocated resources" Allocated resources: (Total limits may be over 100 percent, i.e., overcommitted.) Resource Requests Limits -------- -------- ------ cpu 3500m (87%) 8000m (200%) memory 8Gi (25%) 16Gi (50%)问题发现:
- Pod的resource request设置过大(请求了3500m,实际只用了250m)
- 没有使用HPA,所有服务都是固定副本数
- 调度策略不合理,资源分布不均匀
6.2 优化方案
步骤1:使用VPA优化资源请求
apiVersion: autoscaling.k8s.io/v1 kind: VerticalPodAutoscaler metadata: name: app-vpa spec: targetRef: apiVersion: apps/v1 kind: Deployment name: web-app updatePolicy: updateMode: "Auto"步骤2:配置HPA自动扩缩容
apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: web-hpa spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: web-app minReplicas: 2 maxReplicas: 20 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70步骤3:优化调度策略
# 使用Pod反亲和性分散Pod apiVersion: apps/v1 kind: Deployment metadata: name: web-app spec: replicas: 3 selector: matchLabels: app: web template: metadata: labels: app: web spec: affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchExpressions: - key: app operator: In values: - web topologyKey: kubernetes.io/hostname containers: - name: web image: web-app:latest resources: requests: cpu: 100m memory: 128Mi limits: cpu: 500m memory: 512Mi步骤4:设置资源配额防止滥用
apiVersion: v1 kind: ResourceQuota metadata: name: team-quota spec: hard: requests.cpu: "20" requests.memory: 40Gi limits.cpu: "40" limits.memory: 80Gi6.3 效果验证
# 优化后查看节点资源使用 $ kubectl top nodes NAME CPU(cores) CPU% MEMORY(bytes) MEMORY% node-01 3200m 80% 28Gi 70% node-02 3000m 75% 29Gi 72% ... # HPA状态 $ kubectl get hpa NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE web-hpa Deployment/web-app 68%/70% 2 20 8 7d💡效率技巧:优化资源利用率不是一蹴而就的,建议先用VPA分析一段时间,获取真实的资源使用数据,再设置合理的HPA阈值。
7. 总结与思考
7.1 核心要点回顾
- 调度流程:predicates(硬约束过滤)→ priorities(软约束排序)→ bind(绑定)
- 抢占机制:高优先级Pod可以驱逐低优先级Pod,但要谨慎使用
- 资源配额:LimitRange(默认限制)+ ResourceQuota(总量限制)+ PDB(中断预算)
- 自动扩缩容:HPA(水平)+ VPA(垂直)+ KEDA(事件驱动),按需选择
7.2 选择建议
| 场景 | 推荐方案 |
|---|---|
| 无状态Web服务 | HPA + 合理的resource request |
| 有状态应用(数据库等) | VPA + PDB |
| 消息队列消费者 | KEDA |
| 混合场景 | HPA(自定义指标)+ VPA(仅初始模式) |
文末三件套
1. 【源码获取】
关注此系列获取后续更新,后台回复’scheduler’获取本文完整代码示例和配置文件。
2. 【思考题】
你的应用需要水平扩容还是垂直扩容?
- 如果是无状态应用,建议优先考虑HPA水平扩容
- 如果是有状态应用(如数据库),VPA垂直扩容更合适
- 如果是事件驱动架构,KEDA是最佳选择
欢迎在评论区分享你的选择和理由!
3. 【系列预告】
云原生系列文章持续更新中:
- GitOps:声明式持续交付实践
- IaC:基础设施即代码
- 安全扫描:容器镜像安全最佳实践
标签:Scheduler, HPA, VPA, KEDA, AutoScaling, 资源调度, 扩缩容
如果觉得本文对你有帮助,欢迎点赞、收藏、转发!有任何问题可以在评论区留言,我会一一回复。
CSDN多平台一键发布功能开通链接
https://mp.csdn.net/vip?utm_source=weitingfu
