
先说结论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 组的节点是groupaB 组的节点是groupb公共的调试节点是groupcommon。同时我给节点加了对应的污点防止没有任何项目组归属的任务被调度上去。这样的好处是物理上把不同项目组的算力隔离开了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手工敲。时间一长手工操作一定会被遗忘后面排错会非常痛苦。把配置代码化之后你的多机、多集群、多资源组平台才能持续稳定跑下去而不是靠某个人脑子里记着“之前是怎么设置的”。