K8S中GPU资源请求与限制
以下均为个人理解,如果有错误,请各位达人不吝批评指正!
需要提前说一句:
对于 GPU 这类可扩展资源 (Extended Resources),requests 和 limits 的行为与 CPU/内存截然不同。在绝大多数情况下,requests 和 limits 必须相等,否则 Pod 将无法创建。
接下来,简单讲一下GPU资源请求与限制
1. 基础原理:为什么 GPU 的 requests 和 limits 必须相等?
要理解这一点,我们必须回到 Kubernetes 资源模型设计的初衷。
- 对于 CPU/内存 (可压缩/可限制资源):
- requests: 是调度时的保证。kube-scheduler 用它来寻找有足够资源的节点。kubelet 确保节点上所有 Pod 的 requests 总和不超过节点的 allocatable。这是 Pod 最低限度的保障。
- limits: 是运行时的上限。kubelet 通过 Cgroups (Linux 控制组) 强制执行这个限制。CPU 超限会被“节流”(throttled),内存超限会被“杀死”(OOMKilled)。
- requests <= limits 的设计,允许了资源的“可突发性”(Burstable),即 Pod 在需要时可以尝试使用超过其 requests 但不超过 limits 的资源。
- 对于 GPU (不可压缩资源):
- 核心差异: kubelet 和底层的容器运行时 (containerd/Docker) 没有原生的机制去“限制”或“节流”GPU 的使用。GPU 不是一个可以像 CPU 时间片那样被轻易切分和限制的资源。一张 GPU 卡一旦分配给一个容器,容器内的程序就可以使用其 100% 的算力和显存,kubelet 无法对此进行约束。
- 设计决策: 基于这个底层限制,Kubernetes 做出了一个务实的设计决策:不支持 GPU 资源的“突发”。为了避免语义上的混淆和无法履行的“承诺”,API Server 在准入阶段就强制要求:
For extended resources, limit must be equal to request.
-
- 结论: 你必须同时写 requests 和 limits,并且它们的值必须完全一样。只写其中一个,或者两者不等,API Server 都会拒绝你的 Pod 创建请求。
正确的基础示例:
apiVersion: v1
kind: Pod
metadata:
name: cuda-pod
spec:
containers:
- name: my-container
image: nvidia/cuda:11.8.0-base-ubuntu22.04
resources:
limits:
nvidia.com/gpu: \"1\" # < 请求 1 个 GPU
requests:
nvidia.com/gpu: \"1\" # < 限制也必须是 1
2. 最新技术方案:从“整数卡”到“精细化请求”
随着技术的发展,我们请求的早已不只是一张“整数卡”。下面我们看看在不同的技术方案下,requests 和 limits 的具体应用。
方案一:基于时间片的 GPU 共享 (如 a GPU)
在这种软件虚拟化方案下,我们请求的不再是 nvidia.com/gpu,而是由 Device Plugin 定义的虚拟化资源。
- 请求的是什么: 你请求的是一个“使用权”和一份“显存配额”。
- 底层原理:
- Device Plugin 向上层 kubelet 上报的是自定义资源,如 aliyun.com/gpu-mem,其单位可以是显存的 GiB 或 MiB。
- 用户在 Pod Spec 中请求这些自定义资源。同样,requests 和 limits 必须相等。
- kubelet 在分配时,会调用 Device Plugin 的 Allocate 接口。Device Plugin 此时会记录下这个容器的显存配额,并通过 LD_PRELOAD 注入一个动态库,在运行时强制执行这个显存限制。
- 示例:
resources:
limits:
aliyun.com/gpu-mem: \"4\" # 请求 4GiB 的显存配额和相应的算力时间片
requests:
aliyun.com/gpu-mem: \"4\" # 同样必须相等
方案二:NVIDIA MIG (硬件级虚拟化)
这是目前实现精细化请求最可靠的方式,因为它有硬件级的保障。
- 请求的是什么: 你请求的是一个特定规格的、物理隔离的 GPU 实例 (GI)。
- 底层原理:
- 管理员使用 nvidia-smi 将物理 GPU 划分为不同的 MIG Profile,例如 1g.5gb 或 3g.20gb。
- NVIDIA 的 Device Plugin 会自动发现这些 GI,并上报为不同的可扩展资源,如 nvidia.com/mig-1g.5gb: 7。
- 用户在 Pod Spec 中可以直接请求这些具体的 MIG 设备。requests 和 limits 依然必须相等。
- 示例:
resources:
limits:
nvidia.com/mig-1g.5gb: \"1\" # 精确请求一个 1g.5gb 规格的 MIG 实例
requests:
nvidia.com/mig-1g.5gb: \"1\" # 必须相等
专家提示: 使用 MIG 时,你请求的资源名称是一个具体的“型号”,而不是一个泛指的 gpu。
方案三:动态资源分配 (DRA) - 未来方向
DRA 彻底改变了资源请求的模式,使其更加灵活和富有表现力。
- 请求的是什么: 不再直接在 Pod 的 resources 中请求,而是通过一个**ResourceClaim 对象**来声明你的需求。
- 底层原理:
- 定义 (ResourceClass): 管理员定义一个 ResourceClass,描述一类 GPU 资源的特性和参数,例如 name: mig-a100。
- 声明 (ResourceClaim): 用户创建一个 ResourceClaim,引用 ResourceClass 并填写具体的参数。
# ResourceClaim.yaml
apiVersion: resource.k8s.io/v1alpha2
kind: ResourceClaim
metadata:
name: my-gpu-claim
spec:
resourceClassName: mig-a100
parameters: # < 这是核心,可以传递任意参数
migProfile: \"1g.5gb\"
memory: \">=4Gi\"
-
- 在 Pod 中引用: 在 Pod Spec 中,通过 resourceClaims 字段引用这个声明。
# Pod.yaml
spec:
resourceClaims:
- name: gpu # 在容器内引用的名称
source:
resourceClaimName: my-gpu-claim # 引用上面创建的 Claim
containers:
- name: my-container
resources: # 注意这里的变化
claims:
- name: gpu # 引用在 Pod 层面定义的 claim
- 优势: 这种模式将“需要什么”(What) 与“分配哪个”(Which)彻底解耦。Pod 只需声明需求,而底层的 DRA 驱动会负责去寻找和分配一个满足需求的具体设备。requests 和 limits 的概念在这里被更富表现力的 ResourceClaim 所取代。
靠谱的实践
- 铁律: 在使用传统的 Device Plugin 模型时(无论是整卡、GPU Sharing 还是 MIG),GPU 资源的 requests 和 limits 必须相等。这是由 Kubernetes 对不可压缩资源的设计所决定的。
- 语义清晰:
- 请求 nvidia.com/gpu: 1 意味着:“我需要一张完整的、未被 MIG 分割的物理 GPU 卡的独占使用权”。
- 请求 aliyun.com/gpu-mem: N 意味着:“我需要在一张共享的 GPU 卡上获得 N GiB 的显存配额和相应比例的时间片算力”。
- 请求 nvidia.com/mig-1g.5gb: 1 意味着:“我需要一个硬件隔离的、规格为 1g.5gb 的 MIG 实例的独占使用权”。
- 未来方向: 积极关注并逐步采纳 DRA (动态资源分配)。尽管它目前还处于 Alpha/Beta 阶段,但它代表了 Kubernetes 异构资源管理的未来。它通过 ResourceClaim 提供了远超 requests/limits 的表达能力,是解决复杂硬件资源请求的根本之道。