1. 从一台GPU服务器的"抢座位"现场说起
先讲一个我们平台团队去年遇到的真实场面。当时给算法团队配了一批新卡,A100 80G,一共8张,装在同一台物理机上。IT同事很尽责,系统装好,驱动打上,CUDA环境也配齐,然后就通知算法组:"机器可以用了。"结果第二天开始,这台服务器就变成了修罗场。
几个算法工程师同时拷入了自己的训练代码,每个人都在跑实验,但谁都没商量。有人直接开了4个进程占满显存,其他人一跑就OOM;有人在训练中途发现显存不够,干脆写了脚本定时去清空其他进程;更夸张的是,有人为了让自己的任务排上队,写了个shell脚本循环kill掉所有非自己用户的进程。那几天大家的对话基本就是:"你把我的实验杀了?""我看着你占着两张卡,就帮你腾了一下。"
这个场景其实特别典型。早期的AI平台,很多就是从"几台带GPU的裸机"开始的。裸机不是不能跑,是没法协作。谁能用哪张卡、跑多长时间、环境冲突了怎么办、资源占用怎么计量,全靠人肉协商。更麻烦的是,算法工程师想要换一个CUDA版本、装一个新库,直接改系统环境,改完别人的实验就起不来了。环境互相污染、资源互相抢占、模型版本混在一起,整个团队的时间大量消耗在"环境纠缠"和"资源打架"上。
后来我们陆续尝试过虚拟机,也试过SLURM,再后来又认真对比过Kubernetes,最终把整个AI平台从"每台机器各自为政"搬到了Kubernetes上面。这篇文章想聊的,就是为什么这条大迁徙的终点,几乎所有人都指向了Kubernetes。不是为了赶时髦,而是因为AI负载本身的几个脾性,正好被Kubernetes的设计按得死死的。
如果你也在帮团队搭AI平台,或者正在纠结"要不要上K8s"、"现有集群乱七八糟该怎么办",这篇文章应该能给你一个从原理到实践的完整脉络。
2. AI负载的四个特殊脾气,恰好被Kubernetes治住
AI平台和普通的Web后台不一样。我们做过一次梳理,发现AI工作负载几乎把IT基础设施里最麻烦的几类需求全都占齐了。可能这也是为什么很多团队绕了一圈,最后还是回到Kubernetes。
2.1 GPU是稀缺资源,Kubernetes把它变成了可调度的插槽
第一件事,也是最核心的一件事:AI离不开GPU,而GPU至今仍然是昂贵且稀缺的资源。一张主流训练卡少则几万,多则几十万,但在Kubernetes里,它不再是一块焊在机器上的硬件,而是被抽象成了资源单位。
这要归功于Device Plugin机制。NVIDIA官方提供了nvidia-device-plugin,装上之后,Kubernetes的kubelet会通过gRPC和显卡驱动通信,感知到每台节点上有多少张卡,然后把这些卡作为可调度的资源上报给API Server。你可以在Pod的resources里直接声明:
resources: requests: nvidia.com/gpu: 1 limits: nvidia.com/gpu: 1调度器看到这个声明,就会自动把Pod调度到有空闲GPU的节点上,然后把对应的设备挂载进容器。这个抽象的意义怎么强调都不为过——它让GPU从物理资产变成了有点像内存、CPU一样的逻辑资源,资源分配、统计、回收都变得标准化了。
可能有人会说:"裸机我也能分配啊,不就是自己数卡数吗?"但问题在于,当集群规模从一台机器变成几十台、上百台,卡的类型有A100、A800、V100、4090混在一起的时候,靠人肉记录谁在用哪张卡,根本就是灾难。Kubernetes给了一个统一的资源视线,你用一条kubectl get nodes就能看到所有节点资源余量,再配合Prometheus,每个GPU的使用率、温度、显存占用全都能可视化。这一步走完之后,我们团队最大的感受是:"终于不用靠吼来协调GPU了。"
2.2 训练与推理的"性格分裂",容器化天然适配
AI平台里其实藏着两类性格截然不同的工作负载:一种是长时间、大资源、破坏性强的训练任务;另一种是短延迟、波动大、需要持续运行的推理服务。
训练任务对基础设施的要求是"投入越多越好,越快跑完越好",而且中途挂了能恢复继续跑;推理服务则更像个传统Web服务,用户请求随时可能暴涨,要求低延迟、高可用。这两者在裸机上几乎没法用同一套管理逻辑。但你一旦用容器把它们都装进Pod,就可以用不同的控制器去编排它们。
训练任务我用Kubernetes的Job原语来管理。Job保证"这个Pod跑到成功为止",如果节点挂了、容器退了,它会自动新建一个替换完成。对分布式的PyTorch训练,我们用了PyTorchJob(通过Kubeflow),它在Job基础上又加了Master/Worker的角色管理。每个训练节点都是一个Pod,共享同一份代码镜像和数据集挂载,环境绝对一致。而且因为Pod之间通过Service域名发现彼此,实例挂了重建后能自动回归集群,这和传统MPI那种所有节点一起启动、一个掉队全体重来的模式相比,容错性完全不在一个维度。
推理服务则用Deployment包起来,配上Service做负载均衡,再用HPA(Horizontal Pod Autoscaler)根据GPU利用率、请求QPS或者自定义指标自动扩缩容。这套机制和跑一个普通Nginx服务几乎没有区别,但承载的却是大模型在线推理。你不需要再手写守护进程,不需要自己实现健康检查,Kubernetes的探针机制会在流量打过来之前就把不健康的实例摘掉。
2.3 环境一致性:让实验可复现,不再"在我机器上没问题"
做AI的都知道环境有多容易"爆炸":升级了一个库的版本,结果其他实验全部跑不出来;本地明明是Python 3.8跑通的推理脚本,一上服务器变成3.10就报错;更别提不同显卡、不同CUDA版本的兼容性问题。传统方式下,大家用conda在机器上建环境,但机器环境最终会变得越来越脏,谁也不敢轻举妄动。
容器直接把环境快照进镜像。算法工程师把Python版本、CUDA toolkit、cuDNN、PyTorch、依赖库全都冻结进Dockerfile,训练和推理用的就是同一个不可变环境。我们内部规定所有实验必须提供对应镜像,禁止在Pod里手工改环境。这样做的结果很直接——实验复现率几乎变成100%,因为代码、环境、数据版本全部有记录。以后排查问题,先看镜像tag,再看代码版本,省掉了一大堆"在我机器上明明是好的"这种环节。
另外,这也让跨团队的协作成本低了很多。以前算法组说"帮我配一下环境",平台组要专门跑过去处理。现在算法工程师自己写Dockerfile,CICD流水线自动构建镜像并推送到私有仓库,Kubernetes拉取镜像运行。环境问题从"人肉沟通"变成了"文件即事实"。
2.4 弹性伸缩:应对推理流量洪峰
还有一个"脾气"是流量的波动性。尤其是大模型推理服务,用户可能白天上班时间请求密集,晚上和凌晨几乎没人访问。如果是固定数量的物理服务器,要么为了高峰时段的用户体验持续空耗大量算力,要么在高峰期眼睁睁看着服务崩溃。Kubernetes的自动伸缩机制正好解决了这个问题。
我们有两条伸缩路径:一条是POD级别的HPA,根据GPU利用率自动增加或缩减推理Pod;另一条是节点的Cluster Autoscaler。以云环境为例,当Pod因为资源不足而无法调度时,Cluster Autoscaler会自动触发云厂商的接口,扩容一批新的GPU节点加入集群;等负载降下来后,再平滑缩容。这套"应用层伸缩+基础设施层伸缩"的组合,可以做到白天20个实例、夜间5个实例,按实际负载付云账单。算下来我们迁移后光推理这一块的GPU成本,比以往固定预留方式省了接近40%。
弹性伸缩听起来是"加了几个副本"这种小操作,但真要做到AI场景不踩坑,有很多细节要在后面专门聊。但至少从机制架构上说,Kubernetes让"弹性"这个词不再是宣传语,而是可以量化的成本优化工具。
3. 比较裸机、虚拟机、SLURM与Kubernetes:为什么最后普适性赢了
很多人在决定架构选型时,会陷入一个思维误区:只看单点性能。但AI平台是一个系统,不是一台机器。我用下面的表格整理了我们当时对比过的几种主流方案:
| 方案 | 资源抽象能力 | 环境隔离 | 分布式训练支持 | 推理服务支持 | 生态与工具链 | 运维复杂度 |
|---|---|---|---|---|---|---|
| 裸机 + 脚本 | 差,靠人肉登记 | 差,环境互相污染 | 很难,要自己实现多机通信和容错 | 很难,没有自动恢复 | 几乎没有 | 低门槛但高心累 |
| 虚拟机 | 中等,CPU内存好分,GPU难直通 | 强,但镜像大启动慢 | 一般,网卡虚拟化有损耗 | 中,但资源浪费严重 | 有限 | 高,需要维护虚拟化平台 |
| SLURM | 强,专精调度和队列 | 中等,可通过module/env隔离 | 强,MPI作业支持很好 | 很弱,不是为长驻服务设计 | 偏HPC,与AI工具链割裂 | 中高,需要额外搭服务层 |
| Kubernetes | 强,可扩展资源类型 | 强,容器级隔离 | 强,通过Operator实现 | 强,原生服务发现与弹性 | 极其丰富,MLOps生态完善 | 有一定学习曲线,但社区工具成熟 |
下面细说这四个方向。
3.1 裸机+脚本:能跑,但不能协作
裸机方案在单机实验、模型原型验证阶段完全没有问题,而且性能最好,因为没有虚拟化开销。但一旦超过两三个人、一两台机器,协作成本就成指数级上升。我见过很多团队在裸机时代搞出各种"聪明"的抢占脚本,本质都是丛林法则。脚本再复杂也管不住"别人改了我的环境""训练到一半被kill""数据被覆盖"这些事。说到底,裸机缺少的是"统一的状态管理"。你没有一个中心化的地方去记录:这台机器上现在跑着什么、资源归谁、规划里接下来该跑什么。
3.2 虚拟机:隔离好但太"重"
虚拟机也算一种过渡方案。它解决了部分环境隔离问题——每个人一个VM,你在里面随便折腾。但虚拟机有两个痛点对AI尤其不友好:一是GPU直通麻烦。要么用PCI直通,一张卡只能绑给一台VM,要么用vGPU拆分,需要企业级license;二是虚拟机开销大、启动慢,而且资源利用率低。如果一个人申请了一台4卡VM,实际只用了1.5卡,剩下2.5卡别人也用不上了。
我们早期在vSphere上部署过几台GPU虚拟机,后来发现运维想扩容很痛苦,算法觉得环境不直接,平台工程师觉得网络和存储配置繁琐。虚拟机作为通用的IT基础设施没问题,但作为AI平台底座,它在GPU这个维度上尤其让人别扭。
3.3 SLURM:调度强大,却和现代AI工具链断层
SLURM是HPC领域的经典调度器,在超算中心统治了几十年。它的作业调度能力非常强,队列管理、优先级、抢占、回填等机制都很成熟。如果团队主要做MPI类型的科学计算,SLURM是非常合理的选型。但我们当时深入试了之后,发现它和现代AI工具链之间存在一道坎。
SLURM管理的"作业"不是"服务"。提交一个作业,它运行,退出,然后结束。但AI推理服务是一个需要长期存活、需要接收动态流量、需要自动伸缩的"应用"。SLURM在这方面几乎没有原生支持,你要自己写一堆胶水代码,让服务保持存活、做健康检查、做滚动更新。另外,SLURM的作业提交方式(salloc、sbatch)和Kubernetes的声明式API(写yaml,交给etcd和控制器循环处理)是两种哲学:前者的交互方式是命令行式、即时型的,后者的交互方式是声明式、持续型。而现代的MLOps工具链——Kubeflow、KServe、Ray、Airflow——几乎全是围绕容器编排这个假设设计的,SLURM想接入这些生态非常别扭。
这就像一个人已经习惯用"文件存档"的方式管理数据,但现在所有新软件都默认你用"数据库"。不是文件不好,是大家已经不在一个轨道上。
3.4 Kubernetes不是性能最优解,却是综合最优解
比下来会发现,Kubernetes在每一单项上未必都是第一:裸机性能最好,SLURM调度最精细,虚拟机隔离最彻底。但Kubernetes的可怕之处在于"普适性"——它同时可以承载训练任务、推理服务、CI/CD、监控、日志、数据库、中间件,并且这些组件之间有统一的网络、存储、安全和观察性模型。
这意味着一个团队只要掌握一套Kubernetes,就能解决AI平台几乎所有的底层问题,而且当新需求出现时,社区里大概率已经有现成的Operator或Controller。我们当时选择Kubernetes,还有一个关键原因是它的API是可编程的。你可以通过CRD自己定义"模型训练"、"模型服务"这样的高层资源,配合控制器实现完全个性化的自动化。这一点是SLURM和虚拟机方案都做不到的——后者只能在其设定好的框架内使用,前者允许你把平台改造成自己想要的样子。最终让我们下定决心的,不是某一个性能指标,而是这个"可变性"。AI技术迭代这么快,平台必须能跟着变,Kubernetes天然就是把"变化"当作设计前提的。
4. 迁移到Kubernetes落地时的五个硬核问题
道理讲清楚了,真到动手迁移的时候,会遇到一批谁都会踩的坑。我们是在生产环境上分了三期才全部切过来的,下面这些问题如果你要迁移,我建议提前做好心理准备和方案设计。
4.1 GPU调度与共享:从Device Plugin到显存配额
先是GPU调度的基础配置。装上NVIDIA Device Plugin之后,默认情况下一张GPU卡只能整体分配给一个Pod。对于推理服务这种往往占不满整卡的场景,这个策略太浪费了。比如一张80G的A100只用到24G,剩下56G就闲置了,这在裸机时代不可想象。
社区里现在有一些方案。最常用的是nvidia-device-plugin配合G显存的方式,也就是把GPU卡按显存切片。NVIDIA官方在MIG(Multi-Instance GPU)之外,还提供了CUDA_VISIBLE_DEVICES之类的隔离手段。不过在Kubernetes层面做显存配额,目前比较成熟的方案有vGPU这类第三方Device Plugin,或直接把整卡分配给不同容器、但通过调度约束让同一Pod里的多个容器共享一张卡。更实用的做法是用KServe这类推理框架,它会自动把同一个模型的多个副本尽量放在同一节点上,复用显卡上下文。
我们的经验是:训练任务尽量给整卡,因为训练对显存和算力的需求都比较高;推理任务尽量走共享,因为推理的显存占用通常远低于批量训练。这两种策略混合在一个集群里,就要求调度器能区分"这个Pod要独占卡,那个Pod要共享卡"。我们通过Resource Claim的方式自定义了一个nvidia.com/gpu.shared资源,由改造过的vGPU插件提供支持,使得共享Pod不用竞争训练Pod的整卡资源。这块确实需要一些开发投入,但对GPU资源利用率来说,收益值回票价。
4.2 训练任务管理:Job、CronJob与Argo Workflows的选择
训练任务的形态其实很多:有一次性实验、定时周期性训练、编排复杂的多阶段流水线。我们做了分类:单Pod的实验任务直接用Kubernetes的Job;每天例行的数据重放和模型微调用CronJob;多阶段任务,比如数据预处理->训练->评估->注册模型,我们用Argo Workflows来编排。
Argo Workflows是一个Kubernetes原生的工作流引擎,每个DAG节点都是一个Pod,节点间可以传参、传产物。它的好处是步骤之间天然隔离,一个步骤失败可以只重跑这个步骤,不用整个Pipeline从头再来。这和传统的Airflow相比,少了很多"调度器要管理一堆worker机器"的运维负担,逻辑也直观很多。
有一个小坑要提醒:训练Pod的restartPolicy默认是Never。不少同学直接把训练脚本放进Deployment,结果Pod一退出就被拉起,跑了没两分钟就无限重启。正确做法是用Job,并且把训练脚本的退出码设计好,0代表成功,非0代表失败,让Job控制器根据退出码决定是否需要重试。对于分布式训练,我们用Kubeflow的PyTorchJob,它允许每个worker失败后独立重启,并在Master的协调下重新参与训练。这比一整个StatefulSet因为某个副本重启而影响整个训练要稳健得多。
4.3 推理服务的高可用与自动扩缩容
推理服务上K8s之后,最大的改变是从"进程管理"变成了"编排管理"。我们用Deployment定义模型服务副本数,Service负责负载均衡,然后通过HPA自动缩放。但这里有个大坑:HPA的默认指标是CPU利用率,而GPU推理服务的瓶颈往往在GPU显存或延迟上。
解决方法是用Prometheus Adapter定义自定义指标,比如根据DCGM_FI_DEV_GPU_UTIL(GPU利用率)或者根据模型推理请求队列长度来扩缩容。我们当时给大模型对话服务配置的HPA是:
metrics: - type: Object object: describedObject: apiVersion: v1 kind: Pod name: llama2-7b-server metric: name: gpu_utilization target: type: Utilization averageUtilization: 70当GPU平均利用率超过70%时增加副本,低于40%时缩减副本。结合Cluster Autoscaler,节点也跟着扩缩。这样在推理高峰期能自动加实例,低谷期缩减成本。不过扩缩容也有缓冲时间,建议设置behavior.scaleDown.stabilizationWindowSeconds,防止流量波动时副本数抖动导致不稳定。
另外,推理服务必须配置好探针。可以给模型服务定义一个/healthz或者/v1/models的健康检查接口,用liveness和readiness探针分别控制"容器是否需要重启"和"何时可以对外提供流量"。我们在初次部署时漏配了readiness探针,导致服务还没加载完模型就开始接收流量,结果大量请求超时。加上探针之后,这个现象再没出现过。
4.4 存储与IO:PVC、JuiceFS与RDMA网络的取舍
AI平台对存储的要求很分裂:训练集动不动几十TB甚至上百TB,但真正要快速读取的是其中一批数据;模型checkpoint可能要保存多份;推理服务要加载模型权重,希望延迟越低越好。Kubernetes原生用PV/PVC抽象存储,但PV的类型选择很关键。
我们最初的方案是用NFS挂载到所有节点,简单好管。但训练任务多的时候,NFS的单点性能和IO吞吐都成了瓶颈,尤其小文件多的数据集,目录列表都会卡。后来我们换成了JuiceFS——一个分布式文件系统,底层可以把数据对象存到Ceph或者云上的S3,同时利用本地缓存加速热点数据读取。训练Pod通过CSI驱动挂载JuiceFS,实测下来大文件读取性能比NFS好了不止一个数量级。另一个好处是checkpoint可以直接写到共享存储,多个Pod之间可以快速恢复,不用先把数据拷回本地。
如果是超大数据集的训练,还需要考虑RDMA网络。Kubernetes里面通过hostNetwork: true加上resource.kubernetes.io/rdma声明来分配RDMA设备。我们的经验是:100G网卡做分布式训练,普通TCP在高并发通信时会有明显延迟波动;RDMA能显著降低通信耗时。不过RDMA的部署对内核依赖和网卡驱动都有要求,节点越多越需要自动化运维。这部分如果没有专职平台团队,建议起步阶段先靠InfiniBand/ROCE的成熟方案或者云厂商的托管集群。
4.5 多团队多租户的隔离与成本账单
平台一旦上了Kubernetes,使用方就不只一个团队了,多租户隔离这种事早晚要面对。我们用了三层机制:Namespace做逻辑隔离;ResourceQuota限制每个团队的CPU/内存/GPU总量;LimitRange限制单个Pod的资源上下限。这样某个团队写段代码死循环了,也只影响自己的Namespace,不会把整个集群弄垮。
但真正的挑战在成本分摊。GPU资源贵,团队之间如果感受不到成本压力,很容易有人开着一堆跑完的Pod也不回收。我们接了一个叫Kubecost的开源组件,按Namespace、Deployment、Pod三个维度统计资源消耗,并根据GPU单价折算成费用,每个月摊到各个团队头上。这个动作看似只是"记账",但对培养资源节约意识效果非常明显。后来算法团队自己也学会在低峰时段跑批处理任务,成本下降了接近20%。
除了成本,还有权限管理。我们用kube-oidc对接公司已有的SSO,通过RBAC控制每个团队只能操作自己的Namespace,并且把kubectl权限下发到个人。这样既保证了操作追踪,又避免了管理员账号到处飞的隐患。
5. 生态绑架还是众望所归:Kubeflow、KServe与云原生的正向循环
写到这里,其实可以做一个总结了:Kubernetes之所以能成为AI平台迁徙的终点,除了它在基础设施层面的能力,更关键的是它周围形成了完整的AI生态。这种生态反哺又让Kubernetes越来越难被替代,形成一种正向循环。
先说Kubeflow。它把训练、部署、超参搜索、TensorBoard等AI开发流程包装成Kubernetes原生的CRD,用户可以通过一套统一的界面和API管理端到端ML工作流。虽然Kubeflow本身有点"重",但它的组件思想已经融入大量企业的平台中。我们用它的PytorchJob和TensorFlowJob作为训练标准,配合Katib做超参自动搜索,整个实验管理的颗粒度比之前脚本方式精细多了。
推理侧是KServe,以前叫KFServing。它支持把一个训练好的模型包(比如ONNX、TensorRT或者HuggingFace仓库)直接部署成可伸缩的推理服务,自动处理服务发现、流量切分、金丝雀发布等能力。它其实是Kubernetes上最省心的模型服务方式:你不用再自己写模型加载、前后处理、监控上报那一大坨,只需要提供模型文件和镜像配置。我们现在新模型的发布周期,从过去可能拖一两周,变成了一天之内,KServe的帮助非常大。
艾克昕生态里还有一个不能忽略的角色——Ray。Ray的架构正好和Kubernetes互补:Kubernetes负责资源调度和生命周期管理,Ray负责分布式计算框架,两者通过KubeRayOperator无缝衔接。数据处理、训练、强化学习等一类任务可以直接用Ray跑,而不用为每个框架写一套专属调度器。
还有,目前大模型推理领域最火的vLLM、TGI这些服务,官方都有适配Kubernetes的部署Chart。你可以直接通过Helm install把它们拉起来,用HorizontalPodAutoscaler自动扩缩,配合Ingress对外暴露服务。这种"官方已经帮你铺路"的感觉,和早年什么都要自己拼装完全是两个世界。
所以你会发现,与其说Kubernetes是某种强制性的标准,不如说AI工具链已经把Kubernetes当作默认的"操作系统"来设计。今天新出的任何一个AI开源项目,大概率都会带着一个deploy目录或者直接提供K8s YAML;反过来,你用SLURM或裸机去跑,就要自己手动适配所有上游工具,成了一个"孤岛"。这种生态锁定不是坏事,它意味着平台团队可以大幅度减少对底层兼容性的人力投入,把精力集中到业务算法、模型优化这些更有价值的事情上。
最后再分享一点实操体会
说了这么多,如果让我用一句话总结AI平台迁移到Kubernetes的经验:值得迁,但不要盲目迁。如果你的团队只有两台GPU服务器、只有两三个算法同学、并且没有专职的平台工程师,那先继续用裸机或者简单的容器化都没问题,硬上K8s反而会增加运维负担。可一旦规模开始扩大,机器超过十台、GPU型号增多、协作团队变多,那Kubernetes几乎是你少走弯路的唯一选择。
我自己的团队在迁移过程中踩过不少坑,最值得分享的一点是:迁移不是一次性搬完,而是按场景逐步切流。我们先把推理服务迁过去了,因为推理服务更容易定义SLA,监控和回滚路径也更清晰;然后迁训练任务,先跑非关键实验,再来跑核心大模型;最后才处理多租户和成本精细化。整个过程跨度大概三个月,几乎没有出现过客户可见的故障。
最后一个小技巧:如果你正在搭建AI平台,强烈建议从一开始就为Kubernetes集群加上完善的GPU监控。不只是显卡利用率,还要看温度、功耗、PCIe带宽和显存ECC错误。我们有一次弱故障排查就是因为没有ECC指标,直到模型精度突然崩了才发现有一张卡反复报错。后来在Node Exporter基础上接入了DCGM,把GPU硬件实时健康状态纳入了告警,这类问题就被提前拦住了。
AI技术的发展速度很快,但底层的资源管理和调度逻辑是相对稳定的。Kubernetes不是终点,它更像是这个时代AI基础设施的一块地基。把这层地基打牢,未来不管上面跑的是BERT还是GPT-10,你都不用慌。