GPU集群作业调度与队列治理实战 - 第2节

资源模型:CPU、内存、GPU、网络带宽等资源的抽象与建模

聊到资源模型,我脑子里第一个蹦出来的词就是「抽象」。说白了,集群里的机器千奇百怪,有的CPU强,有的内存大,有的挂着GPU。调度器怎么统一看待它们?靠的就是资源抽象。

我个人习惯把资源抽象理解成「给机器画一张标准化的资源清单」。每台机器上报自己有多少CPU、多少内存、几块GPU、网卡带宽多大。调度器拿到这张清单,才能决定把任务往哪扔。

CPU资源的抽象

CPU在Kubernetes里用「核」来表示。1核等于一个物理核心或者一个虚拟CPU(vCPU)。但这里有个坑——CPU是可压缩资源。什么意思?你给一个容器分配了1核,它用不满,剩下的CPU可以被其他容器抢走。但如果它想用超过1核,就会被限流。

我在项目中遇到过一个问题:某个Java服务把CPU Request设得很低,Limit设得很高。结果高峰期CPU被打满,服务响应变慢,但没被杀死。因为CPU是可压缩的,系统只会限流,不会OOM Kill。嗯,这其实是个设计选择——让服务降级而不是崩溃。

核心概念:CPU的Requests是「保证量」,Limits是「上限」。调度器根据Requests做决策,运行时根据Limits做限制。

内存资源的抽象

内存就不一样了。它是不可压缩资源。一旦容器申请了2GB内存,这2GB就被独占,别的容器用不了。如果容器试图超过Limit,系统会直接把它杀掉(OOM Kill)。

你想想看,这其实很残酷。CPU超了还能凑合用,内存超了直接没命。所以我建议:内存的Requests和Limits最好设成一样。别给自己挖坑。

资源类型 可压缩? 超限后果 建议
CPU 是 限流(Throttling) Requests可设低,Limit设高
内存 否 OOM Kill Requests = Limits
GPU 否 任务失败 显式声明,不要超分
网络带宽 是(部分) 丢包/限速 结合QoS策略

GPU资源的抽象

GPU的抽象比较特殊。它不像CPU那样可以按毫核来分。一张GPU卡要么给某个任务用,要么不给。Kubernetes通过Extended Resource机制来管理GPU,比如 nvidia.com/gpu: 1 就表示申请一张卡。

我曾经踩过一个坑:两个小任务各申请0.5张GPU,结果调度器直接拒绝了。因为GPU不支持分数。后来我们改用MIG(多实例GPU)技术,把一张A100切分成7个实例,每个实例算一个独立资源。这才解决了细粒度分配的问题。

小技巧:如果你的任务用不满一张GPU,考虑使用MIG或vGPU技术。别硬塞0.5张卡,调度器不认。

网络带宽的抽象

网络带宽的抽象是最容易被忽略的。很多调度系统根本不建模带宽,结果任务跑起来才发现网络成了瓶颈。

我个人习惯把带宽建模成节点属性。比如每台机器标注「上行带宽10Gbps,下行带宽10Gbps」。调度时检查所有任务的总带宽请求是否超过节点上限。当然,这需要配合CNI插件来做流量控制。

为什么会这样?因为网络是共享的。一个任务疯狂发数据,其他任务就得等着。你不建模带宽,调度器就是个瞎子。

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

Requests和Limits是Kubernetes资源管理的核心机制。我刚开始用的时候也觉得不就是设两个数嘛,后来才发现这里面的门道很深。

Requests:调度时的保证

Requests告诉调度器:「这个任务至少需要这么多资源」。调度器会找一台能满足所有Requests总和的节点。比如一个Pod请求2核CPU、4GB内存,那它只会被调度到剩余资源大于等于这个数的节点上。

但注意,Requests不代表实际使用量。你的Pod可能只用了0.1核,但它占着2核的配额。其他Pod不能抢。这就是资源预留的概念。

Limits:运行时的上限

Limits是硬限制。CPU超了会被限流,内存超了会被杀掉。我见过很多团队把Limits设得特别大,以为这样能「弹性伸缩」。结果呢?节点资源被某个Pod吃光,其他Pod全挂。

警告:不要把Limits当成「弹性空间」。它是一道生死线。尤其是内存,超了就杀,没有商量余地。

一个典型的配置示例

apiVersion: v1
kind: Pod
metadata:
  name: demo-pod
spec:
  containers:
  - name: app
    image: nginx
    resources:
      requests:
        memory: "512Mi"
        cpu: "500m"
      limits:
        memory: "1Gi"
        cpu: "1"

这个配置的意思是:保证给这个Pod 0.5核CPU和512MB内存,但允许它最多用到1核CPU和1GB内存。CPU可以超,内存不能超。

资源建模的核心逻辑

说了这么多,我画了一张图来总结资源模型的核心逻辑。你一看就明白了。

资源模型核心逻辑 节点资源池 CPU: 16核 内存: 64GB GPU: 2张 带宽: 10Gbps 调度器 检查 Requests 匹配节点 Pod 资源声明 Requests: 2核/4GB Limits: 4核/8GB GPU: 0 带宽: 1Gbps 关键流程 1. 节点上报资源总量(CPU/内存/GPU/带宽) 2. Pod声明 Requests(调度保证)和 Limits(运行时上限) 3. 调度器根据 Requests 选择满足条件的节点 4. 运行时由 kubelet + cgroup 强制执行 Limits 5. 内存超限 → OOM Kill;CPU超限 → 限流

这张图把整个流程串起来了。节点上报资源,Pod声明需求,调度器做匹配,运行时做限制。每一步都离不开资源模型。

避坑指南

我曾经犯过一个低级错误:给一个批处理任务设了CPU Requests为4核,但集群里所有节点都只有2核。结果任务永远处于Pending状态。调度器不是万能的,它只能在你给的数据上做决策。

还有一次,我把内存Limits设成了节点总内存的80%,结果一个Pod的内存泄漏直接拖垮了整个节点。后来我学乖了:所有Pod的Requests总和不能超过节点资源的80%,留点余量给系统进程和突发情况。

我的建议:刚开始做资源建模时,先统一单位。CPU用毫核(m),内存用MiB或GiB,GPU用整数。别混用单位,否则调度器算出来的结果会让你怀疑人生。

嗯,资源模型这块就聊到这儿。说白了就是两件事:把资源抽象成标准格式,然后用Requests和Limits控制分配与使用。你只要记住CPU可压缩、内存不可压缩、GPU要整数、带宽别忽略,基本就不会出大问题。

想要更多资料,联系可可