☰
CubeStudio多机多集群部署与按项目组分算力实操指南
2026/10/23 9:21:36 网站建设 项目流程

先说结论:CubeStudio 从单机版一路扩到多机、多集群、按项目组切分算力,我前后折腾了两周多,中间踩了不少坑。如果你团队规模还停留在“一台 8 卡机器跑所有任务”的阶段,这篇可以先收藏,等算力需求上来再用;如果你已经被老板催着“把算力按项目组切清楚”,那下面的内容基本就是照着抄的实操笔记。

这里先给没接触过的朋友扫个盲。CubeStudio 本质上是一个跑在 Kubernetes 之上的 AI 开发与训练平台,它把算法工程师常用的开发环境、训练任务、数据集管理、算力申请这些事统一收敛到一个入口里。单机模式下,它通常以“all-in-one”的方式部署在一台 GPU 服务器上,K8s 节点就一个,存储、调度、镜像仓库全挤在同一台机器里。这种部署方式在十几人的算法团队里完全够用,因为大家的任务排队等资源就行,慢一点但不会乱。可一旦团队到了几十人、项目组变多、手里又陆续添了新机器,单机模式的瓶颈就不是“慢”这么简单了,而是结构性的撑不住。

我这次实操的目标其实很朴素:把 CubeStudio 从一台机器扩展成多台机器组成的集群,再把手上几个零散的 K8s 集群统一接管进来,最后让不同项目组各自看到自己的算力配额,互不干扰。整篇会按照“架构拆解 -> 多机部署 -> 多集群纳管 -> 资源组分算力 -> 踩坑实录”的顺序写,都是可落地的步骤和配置,不整虚的。

1. 动手之前,先想清楚单机到多机到底在解决什么问题

1.1 最初的单机部署长什么样

我最初部署 CubeStudio 时用的是最省事的方案:一台 2 卡 GPU 服务器,装好 Docker 和 K8s 单节点集群,再把 CubeStudio 的服务端、数据库、对象存储、镜像仓库全部通过 Helm 一次性装上去。这个方案看起来很美好,因为一条命令就能把平台跑起来,团队里几个算法工程师立刻就能开始做实验。

但这种“美好”是假象。单机模式下,K8s 的 Master 组件和 Worker 组件在同一台机器上,CubeStudio 控制面和用户的训练任务也在同一台机器上抢资源。平时任务不多时还好,一旦同时跑三四个大模型训练,这几方就会因为 CPU、内存、磁盘 I/O 互相干扰,最典型的状况是:某个训练任务把 GPU 显存吃满,然后 CubeStudio 的 Web 服务因为内存不足被 OOM Kill,前端页面直接打不开。

所以我的第一个建议是:不要因为“现在能用”就一直单机跑着,要在团队规模扩张之前就规划好拆分路径。拆分的本质不是“多加几台机器”,而是把平台的控制面和算力承载面彻底分离开。

1.2 扩容的真正瓶颈:故障域和数据面

很多人以为扩成多机就是把 K8s 集群加几个节点、把 GPU 机器接入集群就行,实际操作之后才发现真正的瓶颈在两处。

第一处是故障域。单机模式下,机器一挂,平台和所有训练任务全挂。哪怕你后来加了几台机器,如果 CubeStudio 的控制面还部署在某一台固定机器上,那这台机器依然是故障核心。多机部署的第一步,是让控制面能飘到不同机器上,至少要做到“机器挂了任务重启,平台还能活着”。

第二处是数据面。单机模式下,数据都在本机磁盘上,训练任务读取数据集很爽,因为不需要经过网络。可一上多机,存储就成了最大的问题:任务被调度到了别的机器,那它去哪儿找数据集和输出目录?如果继续用每台机器的本地盘,用户在 CubeStudio 里创建的数据集就只能绑定在某台机器上,任务一旦被调度到别的节点就直接“文件不存在”。所以多机部署真正的前置工作是:先把存储抽象出来,让所有节点共享同一套数据面。

1.3 什么时候必须做多机,什么时候可以继续苟着

我自己的判断标准有三条,满足任意一条就该启动多机改造:

  • 平台控制面和训练任务开始互相影响,比如 Web 端经常卡死、任务经常被系统杀掉;
  • 机器磁盘使用率长期超过 80%,数据迁移和备份完全无从下手;
  • 多个项目组开始抢同一台机器的 GPU,且你已经说不清楚“谁的算力用得多、谁在排队”。

如果只是偶尔有同事抱怨训练慢,那可以再等等,同时提前把数据从本地盘迁移到独立的存储服务上。这一步什么时候做都不亏,因为它是多机化最耗时、最容易被忽略的部分。

2. 多机部署实操:从“单机全家桶”拆成“控制面 + 算力面”

2.1 先把存储和数据库从本机挪走

我在正式扩容之前干的第一件事,不是加机器,而是加了一台存储服务器做 NFS,后面又慢慢迁移到了 CephFS。为什么优先搞存储?因为 CubeStudio 里的数据集、预训练模型、训练输出目录,本质上都是文件。只要文件和运行环境解耦,调度器才有自由分配任务的余地。

具体操作上,我给 Kubernetes 集群装了一个 StorageClass,底层用 NFS 提供 PV 动态供给。这样 CubeStudio 里每次创建数据集或工作目录,系统都会自动在共享存储上划出一块空间,而不是在某个节点本地建目录。需要说明的是,NFS 用于小规模场景没问题,但如果你的训练任务经常要读几个 GB 的大文件,NFS 性能会有点吃紧,这时候要么上 CephFS,要么用 JuiceFS,后者对 AI 训练的友好度会更高一些。

数据库也是一样,我早期把 PostgreSQL 跑在 CubeStudio 的同一个命名空间里,单机时没毛病,多机后一旦控制面被调度到别的节点,数据库 IP 就变了,所有服务瞬间失联。后来我把它挪到独立的三节点 PostgreSQL 高可用集群,用 Service 名访问,彻底解除了对具体节点的依赖。

2.2 控制面和工作面的拆分策略

等存储和数据库独立之后,我开始动手拆 CubeStudio 自身的组件。CubeStudio 的架构其实分两层:上层是一堆 Web 服务和工作流引擎,负责用户交互、任务编排;下层是任务执行器,负责实际把训练任务跑到 GPU 节点上。

单机版里这两层是混在一起的。我在多机化时做了一件关键的事:把上层的所有 Deployment 打上node-role.cubestudio.io/control-plane标签,并且让这些服务只调度到具有该标签的节点上。这样,负责跑训练任务的 GPU 节点即使被任务挤爆,也不会影响平台 Web 端的基本可用性。

具体的 Kubernetes 配置其实很常规——通过nodeSelector配合节点标签,把控制面“钉”在专门的机器上。我再给这些控制面节点加上了NoSchedule的污点,防止用户的训练任务被意外调度上去抢资源。

2.3 调度器和任务分发怎么改

多机之后,任务分发由 Kubernetes 默认调度器接管,但默认调度器对 GPU 任务的支持是不够的。我最开始遇到的问题很典型:有 4 台 GPU 机器,任务经常只被堆在其中一两台上,另外几台闲着。原因在于,默认调度器只按“资源够不够”去选节点,不懂“这张卡已经被占用了,那这张卡上的显存不够跑新任务”这种显粒度的约束。

所以我给 CubeStudio 底层接了显存感知的调度插件(调度器扩展),让它在调度训练任务时检查节点 GPU 的已用显存和可用显存,而不是只看整卡数量。这一步做完之后,多机的利用率才真正上去,原本“一台忙死,三台闲死”的局面缓解了不少。

如果你不想动调度器,也可以退而求其次:给不同项目组的任务设置节点亲和性,让它们天然落在不同机器上,但这只是算力切分的雏形,做不到全局最优调度。

2.4 网络打通与镜像仓库准备

多机部署最容易翻车的地方,在我看来是网络。第一件要做的事,是让所有节点的容器网络互通,通常的解决办法是装一个 CNI 插件,我用的是 Calico,并在部署时指定IP_AUTODETECTION_METHOD为对应的网卡正则,否则多网卡机器上很容易选错 IP,导致节点之间路由不通。

另一件容易忽略的事是镜像仓库。单机时所有镜像都在本机,多机后每台节点都需要能拉到镜像。我最开始偷懒用了 Docker Registry,后续发现并发拉取稍高就报错,最后换成了 Harbor,并把 CubeStudio 的镜像、训练任务所需的 PyTorch/TensorFlow 镜像都提前推了上去。建议所有镜像都固定 tag,不要用latest,不然哪天缓存过期,任务会莫名其妙失败。

完成这一步之后,多机部署算是真正打通了,平台控制面稳定跑在专用节点上,训练任务可以自由地调度到任何一台 GPU 机器上,数据也通过共享存储在不同节点间流转。接下来要解决的就是更上层的问题:怎么把一个平台扩展到多个独立集群。

3. 多集群纳管:不是搭“帝国联邦”,是注册 + 代理

3.1 为什么不做集群联邦

团队手上的集群越来越多之后,很自然会想到一个词:集群联邦。我也研究过这套方案,包括各种 Federation 项目,最后都放弃了。原因很简单:联邦架构(一个集群调度到另一个集群)带来的网络开销和状态同步复杂度,在多数公司内部场景里根本没必要,反而容易把故障范围扩大。

更适合 CubeStudio 这类平台的做法是“注册 + 子集群纳管”:主集群负责跑 CubeStudio 的控制面,其他子集群只需要把 Kubernetes API 的访问凭证接入进来。用户在 CubeStudio 里选择一个集群,平台通过子集群的 API 代为下发任务。子集群之间保持网络隔离和权限隔离,主控集群只充当一个“代理和调度中心”的角色。

这个思路对运营团队来说非常友好:新增一个集群不需要重装 CubeStudio,只要给主集群一份可用的 kubeconfig 凭证,我这边做一下配置注册,就能把新集群纳入管理,整个过程大概 20 分钟。

3.2 主控集群和子集群的分工边界

在多集群纳管模式下,我总结出来的一个经验是:把“平台状态”和“计算任务”彻底分开。

平台状态指的是用户、工作流定义、数据集元数据、算力配额这些,它们只保存在主控集群里,全局唯一。计算任务是真正跑在子集群里的,比如训练脚本、开发环境容器。这样做最大的好处是,任何子集群宕机,都不影响用户在 CubeStudio 里查看历史记录、修改配额、创建新任务模板。而如果是联邦方案,子集群状态一抖动,控制面往往也会跟着受牵连。

各子集群内部也不是完全各自为政的。我会在每个子集群里部署一个小型的 agent,负责将自己的节点资源信息、GPU 数量、已用配额定期上报给主控集群。CubeStudio 在给用户展示“可用集群资源”时,读的就是这些上报信息,而不是逐个访问子集群的 API。这个数据有一定延迟,但在实际使用中几秒钟的偏差完全可接受。

3.3 多集群之间的凭据安全和权限收敛

这也是我最想强调的一点:纳管子集群时,千万别直接把子集群的 admin kubeconfig 配进主集群。我早期贪图省事,配过一段时间 admin 凭证,结果哪一天某个操作人员误删了另一个集群的 namespace,出了事故,根源就在这里。

正确做法是在每个子集群上创建一个专用的 ServiceAccount,比如命名为cubestudio-controller,然后给它绑一个只读资源 + 创建 Pod/Deployment 权限的角色。需要的权限大致包括:Pod、Deployment、Service、ConfigMap、ResourceQuota 等常见资源的增删改查,以及查看 Node 信息。最后把该 ServiceAccount 的 token 和集群 API Server 地址一并写入主集群里的 Secret 中,CubeStudio 在运行时直接读取这些 Secret,动态生成 kubeclient。

这样一来,即使主集群被入侵或者误操作,攻击面也被限制在“一个能创建 Pod 的账号”内,而不是彻底掌控子集群。安全性和便利性在这个权衡点上基本可以达到平衡。

3.4 任务调度到哪个集群,由什么决定

多集群纳管之后,下一个问题是:用户创建训练任务时,CubeStudio 应该把任务下发到哪个集群?我最初的做法是让用户手动选择,后来发现这会导致问题:用户永远只选自己最熟悉的那一个集群,其他集群利用率上不去。

后来我改成“平台推荐 + 手动覆盖”的机制:平台根据各子集群上报的空闲 GPU 数量和当前排队任务数,自动推荐一个最空闲的集群作为默认选项,用户仍然可以手动切换。这个改动效果非常明显,因为大多数用户根本没有心思去挑集群,你给他一个合理的默认选项就够了。

真正的“跨集群调度”我没有过度追求,比如让一个任务自动流转到其他集群排队。因为训练任务一般体积都很大,跨集群搬迁镜像和数据的开销远大于排队等待本身。大部分场景下,把任务合理地分配给某个合适集群,远比在集群之间动态迁移任务更有意义。

4. 按项目组分算力:资源组的核心设计

4.1 先明确“按项目组分算力”到底要做什么

“按项目组分算力”这句话听起来很明确,实操时却容易跑偏。团队老板真正想要的往往是这样几种效果:

  • A 项目组申请 4 张卡,那 A 组的任务最多同时占用 4 张卡,不能多占;
  • B 项目组手里有几个“紧急训练任务”,就算 A 组有闲置算力,B 组也不能随意抢占 A 组的卡;
  • 每个项目组管理员能看到自己组的配额和实时用量,心里有数。

这意味着,单纯给用户设登录权限是不够的,必须把“资源组”抽象成平台的一等公民。我在 CubeStudio 里的设计方案是:把集群节点按项目组打上标签,然后让配额机制跟着项目组走,而不是跟着用户走。

4.2 用节点标签和污点把机器“物理分组”

算力切分的底层实现,离不开 Kubernetes 的节点机制。我给每台 GPU 节点打上了cubestudio.io/group标签,比如 A 组的节点是group=a,B 组的节点是group=b,公共的调试节点是group=common。同时,我给节点加了对应的污点,防止没有任何项目组归属的任务被调度上去。

这样的好处是:物理上把不同项目组的算力隔离开了,A 组的任务永远只落在 A 组的节点上,B 组的任务也永远不会占用 A 组的物理卡。坏处是:如果某个组的任务突然激增,而另一个组有空闲算力,这个空闲算力没办法自动让出来。这是一个典型的“资源隔离 vs 利用率”的取舍,我权衡下来的结论是:在团队内部多项目组并行的情况下,隔离优先、弹性其次。追求极致利用率可以后面再做占位抢占机制,但第一版先把边界切开,减少项目组之间的摩擦比省几张卡更重要。

4.3 ResourceQuota 和 PriorityClass 的组合用法

光有节点隔离还不够,还需要让用户在同一个项目组内部也能有序使用。我在每个项目组的命名空间里都创建了 ResourceQuota 和 LimitRange。

ResourceQuota 负责控制命名空间级的总量,比如限制 A 组最多同时使用 4 张 GPU、32 GiB 内存。LimitRange 负责给每个容器设置默认的 CPU 和内存请求值,防止某个用户创建的容器因为没有写 resource 字段而无限占用宿主机资源。

如果只是这样,紧急任务还是会和普通任务挤在一起排队。我加了 PriorityClass 做分级:普通训练任务优先级是普通,紧急训练任务优先级是 high。高优先级任务在调度时会被优先安排,但 ResourceQuota 仍然生效,因为高优先级任务也得受总量约束。这个设计让“紧急”不等于“无限占用”,项目组负责人可以在台账里真实管控每一张卡的分配。

4.4 申请、审批、量化的完整链路

资源组落到运营层面,不只是 Kubernetes 配置,还需要一条清晰的申请链路。我在 CubeStudio 里配了这样的逻辑:用户提交一个“算力申请”,内容包含项目组归属、需要的 GPU 卡数、使用时长、理由;项目组负责人审批通过之后,平台再调用 Kubernetes API 去创建对应的 ResourceQuota。

这套链路实现完之后,我发现一个很意外的收益:各项目组的负责人开始主动关注资源使用率了。因为每一步资源分配都有记录,月底复盘时平台能导出每个项目组的“申请配额、实际使用、GPU 空闲时长”三组数字。这个数据对后续采购 GPU 的决策非常有用,不再靠感觉判断“显卡够不够”。

当然,配额和标签不会永久不变,项目组任务重心调整后需要重新划分,我的做法是定期(比如每个月)和各项目组负责人对齐一次节点归属,再调整标签和污点。Kubernetes 节点标签的修改是动态的,不会中断正在运行的训练任务,但如果直接把某个项目组的节点收回,那这些节点上的存量任务会继续跑到结束,需要提前沟通好时间窗口。

5. 多机、多集群、多资源组部署中踩过的坑

5.1 任务全部卡在 Pending,一台机器都没被调度

我第一次把所有节点接入集群后,训练任务新一建就全卡在 Pending,排查过程非常折磨。后来发现是污点的问题:我给每台节点都加了NoSchedule污点,以为只有带对应容忍度的任务才能上去,但 CubeStudio 的默认任务模板里根本没有容忍度配置,所以所有任务全被拦下了。

解决办法是给所有标准任务模板统一加上对所有节点污点的容忍。但是“容忍所有污点”这一点本身风险很高,它会让任务可能被调度到任何节点上。我最终的做法是给任务模板增加一个默认的“调度策略”,让平台根据项目组自动填充对应的节点亲和性和污点容忍度,而不是把选择权交给用户去填 YAML。

如果你也遇到 Pending 问题,建议依次排查:节点标签是否匹配、污点是否被容忍、ResourceQuota 是否还有余量、StorageClass 是否可用、调度器扩展是否报错。我遇到的 Pending 有接近一半是后两者,而不是节点资源不够。

5.2 多集群纳管之后,凭证过期导致任务静默失败

多集群纳管跑了一周之后,我开始收到莫名其妙的任务失败反馈:明明显示“已提交”,但在子集群里根本看不到 Pod。后来查出来是 ServiceAccount 的 token 过期了。Kubernetes 默认 ServiceAccount token 有过期时间,我直接把 token 静态写进主集群的 Secret 里,到期之后所有请求都返回 401,但 CubeStudio 这边的任务调度器没有把 401 当成致命错误,只是静默丢掉了任务。

从那以后我改了做法:不再手动拷贝 token,而是让主集群通过 TokenRequest API 动态换取子集群的短期 token,并配好了自动刷新,这样即使凭证过期,也能在生成后及时更新。没有改代码之前,也可以写一个定时任务去刷新 Secret,效果类似。

5.3 配额显示和实际占用对不上,原因是镜像缓存和调度器延迟

资源组做完之后,各项目组负责人反馈过一个问题:平台页面显示“A 组还剩 2 张卡可用”,但实际他们再提交任务时系统提示配额不足。排查下来,问题出在“上报资源量”和“实际调度约束”是两个不同的系统:页面展示是基于 agent 定期上报的节点信息,调度约束是 Kubernetes 的 ResourceQuota,两者之间有几秒到几十秒的延迟。

针对这个问题,我在主观感受上给了两个建议:第一,页面上的配额展示应当标明“更新时间”,避免用户误认为实时数据;第二,如果对准确性要求极高,可以把展示数据源也切到 K8s API 的资源用量统计接口,而不是依赖 agent 上报。前者成本低、见效快,后者适合规模更大、对数据准确性有审计要求的场景。

5.4 升级 CubeStudio 版本时,存量任务全被打断的一次教训

这个坑特别值得写:有一次我升级 CubeStudio 主控集群的某个组件,升级策略没设置好,导致控制面执行了滚动重启。由于任务管理器和执行器用的是同一套命名空间,重启过程中出现短暂的数据竞争,几十个正在跑的训练任务全部被删除。那天团队里好几个同事的训练成果断了,折腾到半夜才恢复数据。

后续我做的调整有两处:一是把“平台控制面的 Deployment 更新策略”从Recreate改为RollingUpdate,同时设置maxUnavailable: 0,确保滚动过程中至少有一个旧副本存活;二是把 CubeStudio 管理的训练任务和执行器使用的工作负载通过 PodDisruptionBudget 保护起来,避免集群节点维护时被驱逐。这两步做完之后,再升级水位平稳了很多。

6. 最终跑起来的架构,和我个人的一点体会

现在这套平台跑起来的整体架构可以概括为:一个主控集群承载 CubeStudio 的 Web 服务、数据库、调度器和多集群凭证;多个子集群通过“注册 + 代理”接入,各自上报资源、执行训练任务;每个子集群内部再按节点标签划分项目组资源组,配合 ResourceQuota 做总量控制。数据面走共享存储,所有节点都能访问同一份数据集目录。

我个人的体会是,整个改造过程最难的不是任何一项具体的 Kubernetes 技术,而是“想清楚边界再动手”。什么时候做多机、哪些集群纳管进来、项目组之间怎么分卡,这都是在跟人打交道的事,比写 YAML 更消耗精力。我之所以建议“先隔离、后弹性”,也是因为内部项目组之间的摩擦通常比 GPU 利用率更值得优先解决。

如果你也在做类似的事,最后分享一个很小但很实用的建议:所有的节点标签、污点、配额、集群凭证,都要通过一套配置文件管理起来,千万别直接用kubectl label和kubectl taint手工敲。时间一长,手工操作一定会被遗忘,后面排错会非常痛苦。把配置代码化之后,你的多机、多集群、多资源组平台才能持续稳定跑下去,而不是靠某个人脑子里记着“之前是怎么设置的”。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询