4. Kubernetes调度与资源管理:资源请求与限制、服务质量等级、节点亲和性与反亲和性

调度和资源管理,说白了就是Kubernetes的“大脑”和“心脏”。我见过太多集群因为资源没规划好,导致线上服务频繁重启。今天咱们就把这块硬骨头啃下来。

4.1 资源请求与限制(Requests & Limits)

先问个问题:你给Pod分配CPU和内存时,到底该填多少?

Requests 是“最低保障”。Kubernetes调度器看到这个值,就知道至少要留这么多资源给Pod。说白了,这是Pod的“保底工资”。

Limits 是“最高上限”。Pod最多能用这么多,超过就会被限制或杀掉。这是Pod的“天花板”。

我个人习惯这样理解:Requests决定了Pod能不能调度到某个节点上,Limits决定了Pod在节点上能“疯”到什么程度。

核心原则:Requests ≤ Limits。如果只设Limits不设Requests,Kubernetes会默认Requests等于Limits。

来看一个实际例子:

apiVersion: v1
kind: Pod
metadata:
  name: web-server
spec:
  containers:
  - name: nginx
    image: nginx:1.21
    resources:
      requests:
        memory: "256Mi"
        cpu: "250m"
      limits:
        memory: "512Mi"
        cpu: "500m"

这个Pod申请了0.25核CPU和256MB内存,但最多能用0.5核和512MB。我在项目中遇到过,有些团队只设Limits不设Requests,结果调度器以为Pod不需要资源,把一堆Pod塞到同一个节点上,直接OOM。

避坑指南:我曾经踩过一个坑——给Java应用只设了内存Limits没设Requests。结果Pod被调度到内存紧张的节点,JVM启动时直接OOMKilled。后来我强制要求:所有Java应用必须设置Requests,且Requests至少是JVM堆内存的1.5倍。

4.2 服务质量等级(QoS Classes)

Kubernetes根据Requests和Limits的设置,把Pod分成三个等级。节点资源不够时,先杀谁后杀谁,全看这个等级。

QoS等级 设置条件 优先级 典型场景
Guaranteed 所有容器Requests == Limits 最高 数据库、核心API
Burstable 至少一个容器Requests < Limits 中等 Web服务、微服务
BestEffort 完全不设Requests和Limits 最低 批处理、测试任务

Guaranteed:说白了就是“VIP客户”。节点内存不够时,最后一个才杀它。我建议核心服务都用这个等级。

Burstable:这是最常见的等级。Pod平时用Requests的资源,高峰期可以“借”一些空闲资源。但节点紧张时,它会被优先杀掉。

BestEffort:嗯,这个等级就是“流浪汉”。没有任何资源保障,节点一紧张第一个被干掉。我见过有人把生产服务设成BestEffort,结果节点内存一高,Pod全挂了。

个人经验:我习惯把数据库、消息队列这类有状态服务设为Guaranteed。无状态Web服务设为Burstable,Requests设成日常负载的70%,Limits设成峰值的120%。至于BestEffort,我只在开发环境或CI/CD的临时任务中用。

4.3 节点亲和性与反亲和性

调度器默认是“随机分配”的。但实际场景中,我们经常需要“定向分配”。比如:GPU任务必须跑到有GPU的节点上,两个高负载服务不能放在同一个节点上。

4.3.1 节点亲和性(Node Affinity)

节点亲和性分两种:硬亲和性软亲和性

硬亲和性(requiredDuringScheduling):调度器必须满足这个条件,否则Pod不调度。说白了就是“非你不可”。

软亲和性(preferredDuringScheduling):调度器尽量满足,但实在不行也可以调度到其他节点。这是“最好是你”。

来看一个实际配置:

apiVersion: v1
kind: Pod
metadata:
  name: gpu-worker
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: gpu-type
            operator: In
            values:
            - "nvidia-tesla-v100"
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 50
        preference:
          matchExpressions:
          - key: disk-type
            operator: In
            values:
            - "ssd"
  containers:
  - name: worker
    image: tensorflow:2.8

这个Pod必须调度到有V100 GPU的节点上,同时尽量选SSD磁盘的节点。我在项目中遇到过,有个团队把硬亲和性写成了软亲和性,结果GPU任务跑到了CPU节点上,训练速度慢了100倍。

避坑指南:我曾经犯过一个错误——把节点亲和性的key写错了。比如节点标签是"gpu-type",我写成了"gpu_type"。调度器直接忽略了这个规则,Pod调度到了普通节点。后来我养成了习惯:写完后用kubectl describe node确认标签名。

4.3.2 节点反亲和性(Node Anti-Affinity)

反亲和性就是“我不要和你在一起”。典型场景:两个高负载服务不能放在同一个节点上,避免资源争抢。

反亲和性也分硬和软两种:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-server
spec:
  replicas: 3
  template:
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - web-server
            topologyKey: "kubernetes.io/hostname"
      containers:
      - name: nginx
        image: nginx:1.21

这个配置的意思是:同一个Deployment的Pod,不能调度到同一个节点上。说白了就是“打散部署”。

我建议:对于高可用服务,一定要用反亲和性把Pod打散到不同节点。否则一个节点挂了,所有副本全挂。

个人经验:我习惯把反亲和性写成软性的。比如preferredDuringScheduling,权重设成100。这样调度器会尽量打散,但如果节点不够,也能调度到同一个节点上。硬反亲和性在节点数少时可能导致Pod无法调度。

4.4 综合实战建议

最后,分享几个我在生产环境中的经验:

  1. 资源规划要留余量:Requests总和不要超过节点总资源的80%。我见过有人填到95%,结果节点一扩容就出问题。
  2. QoS等级要匹配业务:核心服务用Guaranteed,普通服务用Burstable,临时任务用BestEffort。别搞反了。
  3. 亲和性要写对标签:先kubectl get nodes --show-labels确认标签名,再写配置。我吃过这个亏。
  4. 反亲和性要考虑节点数:如果只有3个节点,副本数却设了5个,硬反亲和性会导致Pod调度失败。

嗯,资源管理这块其实不难,但细节很多。你想想看,一个Requests写错了,可能导致整个集群的Pod调度失败。所以我的建议是:先小规模验证,再大规模推广。