随着机器学习平台扩展以服务多个团队和项目,对GPU等宝贵资源的竞争变得不可避免。生产系统不能按先来先服务的原则运行。它需要机制来强制隔离、确保公平的资源访问并优先处理重要工作负载。Kubernetes提供了一套强大的原语来构建一个完善的多租户环境:命名空间用于隔离,ResourceQuota用于容量管理,PriorityClass用于调度优先权。使用命名空间进行逻辑隔离创建多租户集群的第一步是逻辑分区。Kubernetes Namespace 为资源提供了一个作用域,有效地在您的物理集群内创建了一个虚拟集群。这可以防止一个团队意外干扰另一个团队的工作;例如,两个团队可以各自部署一个名为 resnet-finetune 的训练任务,而不会发生冲突,因为它们存在于不同的命名空间中。除了名称作用域之外,命名空间是安全和访问控制的基本单位。使用基于角色的访问控制(RBAC),您可以按命名空间定义权限。这允许您授予数据科学团队对其自己的开发命名空间的完全控制权,同时给予他们对共享生产命名空间的只读访问权。digraph G { rankdir=TB; splines=ortho; node [shape=box, style="rounded,filled", fontname="sans-serif", color="#495057", fillcolor="#e9ecef"]; edge [color="#868e96"]; subgraph cluster_k8s { label = "Kubernetes 集群"; bgcolor = "#f8f9fa"; fontcolor = "#495057"; fontsize = 14; subgraph cluster_ns_a { label = "命名空间: team-alpha"; bgcolor = "#e7f5ff"; fontcolor = "#1c7ed6"; "Team Alpha User" [shape=oval, fillcolor="#a5d8ff", label="Alpha 团队"]; "Pod A1" [fillcolor="#a5d8ff", label="任务: 文本生成"]; "Pod A2" [fillcolor="#a5d8ff", label="任务: 数据预处理"]; "Team Alpha User" -> {"Pod A1", "Pod A2"} [style=dashed, arrowhead=none]; } subgraph cluster_ns_b { label = "命名空间: team-beta"; bgcolor = "#fff0f6"; fontcolor = "#d6336c"; "Team Beta User" [shape=oval, fillcolor="#fcc2d7", label="Beta 团队"]; "Pod B1" [fillcolor="#fcc2d7", label="任务: 预测"]; "Pod B2" [fillcolor="#fcc2d7", label="任务: 分析"]; "Team Beta User" -> {"Pod B1", "Pod B2"} [style=dashed, arrowhead=none]; } } "Team Alpha User" -> "Team Beta User" [style=invis]; }团队在其各自的命名空间中隔离,防止资源名称冲突并实现细粒度访问控制。考虑一个授予管理常见机器学习工作负载资源权限的 Role。这个在 team-alpha 命名空间中定义的角色,仅适用于该特定命名空间。# role-team-alpha.yaml apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: namespace: team-alpha name: ml-developer rules: - apiGroups: ["", "apps", "batch"] resources: ["pods", "deployments", "jobs", "services"] verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] - apiGroups: [""] resources: ["pods/log"] verbs: ["get", "list"]接着,您使用 RoleBinding 将此角色绑定到特定的用户或组。# rolebinding-team-alpha.yaml apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: team-alpha-binding namespace: team-alpha subjects: - kind: Group name: "data-science-alpha" # 来自您的身份提供者的组名 apiGroup: rbac.authorization.k8s.io roleRef: kind: Role name: ml-developer apiGroup: rbac.authorization.k8s.io通过这种配置,data-science-alpha 组的成员可以在其命名空间中管理任务,但不能查看或影响 team-beta 命名空间中的任何资源。使用配额管理资源消耗命名空间提供了逻辑分离,但它们不能阻止一个团队消耗所有可用的物理资源。team-alpha 中的单个用户可能会无意中请求所有可用的GPU,导致 team-beta 和其他用户无法获得资源。在这种情况下,ResourceQuota 对象变得非常重要。ResourceQuota 对命名空间内所有 Pod 可以消耗的计算和存储资源总量设置了硬性限制。对于机器学习平台,最重要的受限资源是CPU、内存和GPU等专用硬件。这是一个开发命名空间的 ResourceQuota 示例,限制了其总消耗量:# quota-team-alpha.yaml apiVersion: v1 kind: ResourceQuota metadata: name: dev-quota namespace: team-alpha spec: hard: requests.cpu: "20" # 总共请求的最大CPU核心数:20 requests.memory: "100Gi" # 总共请求的最大内存:100 GiB limits.cpu: "40" # 硬性限制的最大CPU核心数:40 limits.memory: "200Gi" # 硬性限制的最大内存:200 GiB requests.nvidia.com/gpu: "4" # 总共请求的最大GPU数量:4 pods: "10" # 同时运行的最大Pod数量:10当 team-alpha 中的用户尝试创建一个会超出这些总限制的 Pod 时,Kubernetes API 服务器将拒绝该请求。这确保了没有单个命名空间能够垄断集群资源,为所有租户保障公平的份额。一个相关对象 LimitRange 可用于在命名空间内对单个 Pod 强制执行资源请求和限制。这是一种良好的实践,可以防止用户创建没有任何资源声明的 Pod,这会导致调度变得困难且效率低下。使用优先级和抢占来优先处理工作负载有了配额,您就实现了公平共享。但并非所有工作负载都具有同等重要性。一个用于生产发布的最终模型训练任务比一个实验性数据分析笔记本更为重要。当集群达到容量时,您需要一种方法来确保高优先级任务能够运行,即使这意味着中断不那么重要的任务。这可以通过 PriorityClass 来完成。PriorityClass 是一个集群范围的对象,它定义了一个命名的优先级级别。Pod 可以通过名称引用 PriorityClass。Kubernetes 调度器以两种方式使用此信息:调度顺序:当多个 Pod 处于等待(挂起)状态时,调度器将尝试首先调度优先级最高的 Pod。抢占:如果一个高优先级 Pod 因资源不足无法调度,调度器可以从节点驱逐(抢占)低优先级 Pod 以腾出空间。让我们为我们的机器学习平台定义几个优先级级别:# priorityclasses.yaml apiVersion: scheduling.k8s.io/v1 kind: PriorityClass metadata: name: critical-prod value: 1000000 globalDefault: false description: "用于关键的生产训练和服务工作负载。" --- apiVersion: scheduling.k8s.io/v1 kind: PriorityClass metadata: name: high-priority-research value: 500000 globalDefault: false description: "用于重要的研究实验和验证运行。" --- apiVersion: scheduling.k8s.io/v1 kind: PriorityClass metadata: name: low-priority-dev value: 100000 globalDefault: true # 如果未指定,则将其设为默认值 description: "用于开发、笔记本和其他非紧急任务。"现在,提交生产训练任务的用户可以在其 Pod 规范中指定 critical-prod 优先级:# prod-training-pod.yaml apiVersion: v1 kind: Pod metadata: name: prod-training-job-1 namespace: team-alpha spec: priorityClassName: critical-prod # <-- 引用优先级类 containers: - name: training-container image: pytorch/pytorch:latest resources: requests: nvidia.com/gpu: 1 limits: nvidia.com/gpu: 1如果集群没有可用的GPU,调度器将识别一个运行低优先级 Pod(例如 low-priority-dev Pod)的节点并驱逐它。被抢占的 Pod 会优雅地终止,从而允许 critical-prod Pod 在其位置进行调度。digraph G { rankdir=TB; graph [fontname="sans-serif", labelloc=t, fontsize=14, fontcolor="#495057"]; node [shape=box, style="rounded,filled", fontname="sans-serif"]; edge [fontname="sans-serif", color="#495057"]; subgraph cluster_before { label = "状态:抢占前"; bgcolor="#f1f3f5"; node [style="filled"]; "Node1_before" [label="节点(1 GPU可用)\n- 运行中:dev-notebook (低优先级)", fillcolor="#d8f5a2", shape=record]; "PendingQueue_before" [label="挂起中的 Pod\n- prod-training (关键优先级)", fillcolor="#ffc9c9", shape=record]; } subgraph cluster_after { label = "状态:抢占后"; bgcolor="#f1f3f5"; node [style="filled"]; "Node1_after" [label="节点(1 GPU可用)\n- 运行中:prod-training (关键优先级)", fillcolor="#ffc9c9", shape=record]; "TerminatedPod" [label="已终止的 Pod\n- dev-notebook (低优先级)", fillcolor="#d8f5a2", shape=record, style="rounded,dashed"]; } "PendingQueue_before" -> "Node1_after" [label="调度器抢占\n'dev-notebook'"]; "Node1_before" -> "TerminatedPod" [style=invis]; }挂起队列中的高优先级 Pod 触发对运行中的低优先级 Pod 的抢占,以释放必要的 GPU 资源。通过结合用于隔离的命名空间、用于公平性的资源配额和用于抢占的优先级类,您可以将一个混乱、资源竞争的集群转变为一个有序高效的多租户机器学习平台。这种结构化方法不仅提高了资源利用率,而且还提供了生产机器学习操作所需的预测性和控制能力。