资讯动态

AI平台为何都要上K8s:GPU资源池化到统一底座

发布时间:2026/10/15 4:40:55 来源:尧图企业网站定制
先交代一下背景。去年年初我接手了一套内部AI推理平台当时所有模型服务都直接裸跑在十几台物理机上算法同事要部署新模型得先找运维要机器、装驱动、配环境运气好半天能上线运气不好赶上升级窗口等两天都是常有的事。到了晚上用户量一上来个别接口超时能到四五秒你问负责的同学怎么回事他只能告诉你“机器扛不住了”至于哪台机器、哪个进程占的GPU、能不能先调度走一部分流量没人能说得清。今年我把这套平台整体迁到了Kubernetes上训练、推理、批量任务全部统一纳管。迁移完成之后回头看我才真正理解为什么现在AI平台都在往Kubernetes上走——这根本不是赶时髦而是当你的GPU规模、业务复杂度和迭代速度到了一定程度之后旧的管理方式确实走不通了。这篇就是想把这个“为什么”拆开讲透中间也会带上我们迁移过程中踩过的一些坑希望能给正在纠结要不要上K8s的团队一点参考。1. 迁徙的起点先把“旧世界”的痛苦摊开看很多人聊K8s喜欢直接讲功能但我更建议先看问题。AI平台在没有Kubernetes之前常见的形态就是“物理机 手工部署 脚本调度”规模小的时候完全能忍规模一大痛苦是叠加出现的。1.1 不用K8s时GPU资源是怎么“管”的先说资源。物理机时代每台机器装了几块卡、跑了什么任务基本靠一张Excel表格或者一个共享文档记录。算法组提交训练任务用的是“登录服务器 nohup python train.py ”的组合看不到队列也不知道前面排了多少任务。推理服务更原始搞个systemd脚本拉起进程对外暴露一个端口负载均衡靠运维手动配。这种方式最大的问题不是“不够自动化”而是“没有抽象层”。GPU是一块一块物理绑定的某台机器的某块卡被占满了但其他机器上还有空闲卡你也没法把任务挪过去因为服务没有被打包成可以调度的单元。结果就是机器的碎片化特别严重——单看每台机器可能都没跑满整个集群的利用率却一直上不去。我自己的实测数据是裸机阶段集群整体GPU利用率大概在20%到30%之间但机器数量不断增加成本一点没省下来。有一次线上晚高峰一个对话模型的推理实例负载到了95%我想给它加一台机器得到的答复是“新机器要等采购流程最快下个月”。那种无力感经历过的人都懂。1.2 业务扩张之后旧方案的三个“必然崩溃点”规模上来之后旧方案的问题就不是“不方便”而是“必然崩溃”。我总结下来有三个致命点。第一是故障恢复太慢。物理机上的推理进程如果因为显存耗尽崩溃了不会自动重启需要人登录上去看日志、手动拉起。半夜出问题值班同学要从床上爬起来先看监控再上机器最快也要半小时才能恢复。训练任务更惨跑了一半挂了如果没做checkpoint前功尽弃。第二是混部能力为零。在线推理和离线训练本来对资源的需求特征完全不同——推理服务白天晚上都有流量训练任务凌晨跑得欢。理想情况是两者错峰使用GPU但物理机没法做这种动态调度只能在规划阶段人为分机器。分完之后推理那边空闲的卡给不了训练训练那边空闲的卡也帮不上推理两边都在等资源两边都在浪费。第三是发布和回滚全靠手工。换一个模型版本基本就是复制文件、重启进程、盯日志三板斧。出问题要回滚不好意思得先找到原来的旧版本文件还在不在。K8s出来之后大家觉得这些是天然该解决的但在那之前每一件都在消耗团队的精力和耐心。可以说AI平台要往前走需要的不是一个“更好的部署脚本”而是一个能把机器资源抽象出来、以应用为单位做调度的平台底座。而这个底座的竞争最后就落到了Kubernetes头上。2. 候选方案盘点为什么不是 Slurm、裸金属或 Ray聊到“AI平台底座”很多人会问一句既然AI负载这么特殊为什么不直接用为高性能计算设计的方案比如Slurm或者干脆用Ray自己管资源这些方案我都认真研究对比过各有优势但最终都没能替代Kubernetes的位置。2.1 Slurm 强在排队调度弱在在线服务Slurm是超算和高校科研机构里最常见的调度器管理批处理任务非常成熟作业排队、优先级、节点分配都做得很好。如果你的AI平台只跑离线训练偶尔跑跑数据预处理Slurm其实是能用的而且很多团队就是这么跑的。但今天的AI平台不能只有离线任务在线推理是更重要的部分。推理服务要求的是常驻、低延迟、快速扩缩容。一个用户请求过来你不可能让它先去Slurm队列里排个队再出结果那些几百毫秒的调度延迟在C端场景下是不可接受的。另外一个容易被忽略的点是Slurm对容器和微服务的支持比较弱它管理的是“作业”不是“应用”。你没法用Slurm优雅地做一个滚动升级、灰度发布也没法从负载均衡那边自动摘除不健康的实例。Slurm在自己的领域很强但AI平台需要的是“私有云操作系统”两者不是一回事。2.2 Ray 是计算框架不是平台底座Ray这两年很火做分布式训练和强化学习的人对它评价很高它确实把“分布式计算”这件事抽象得比K8s更贴近AI开发者——写代码的人不用关心任务在哪些机器上跑一个ray.remote装饰器就能把函数拆到多机执行。但你仔细想一下Ray自己能管GPU吗能管容器吗能做跨节点的网络策略吗能处理持久化存储吗答案都是不能。Ray运行在节点上但它不负责节点生命周期、不负责镜像分发、不负责故障自愈。它需要一个“底座”来提供这些能力而这个底座现在主流的选择就是Kubernetes。Ray官方从1.4版本开始就把Kubernetes集成作为一等公民社区也提供了Ray Operator来在K8s上管理Ray集群。所以“Ray替代K8s”是个伪命题真实的架构是“K8s作为底座Ray作为上层计算引擎”。两者是互补关系不是替代关系。2.3 裸金属脚本看起来自由实际处处是坑还有一个选择是“什么都不用就写脚本管理”。有些团队觉得自己就几十张卡没必要上K8s于是写了一套Golang或Python脚本通过SSH到机器上执行命令来部署服务。这种方案初期确实能跑但维护成本会随着规模增长直线上升。举几个我见过的真实场景某个脚本部署服务后不知道哪个环节把容器环境变量搞错了线上所有实例连不上配置中心排查了一整天另一个团队自己实现了一个“简易调度器”但只支持“一台机器跑一个任务”不支持多个小任务共享一张卡资源利用率低到发指。裸金属脚本方案最致命的问题是没有标准化的“应用描述”——你的服务有什么依赖、需要多少GPU、暴露哪些端口、怎么做健康检查全都散落在各个脚本里。要知道K8s最核心的价值就是把这一切收敛成一份份声明式的YAML让应用成为可以被版本管理、被复制、被审计的对象。这个价值在机器少的时候感觉不出来机器一多差异就非常明显。2.4 候选方案对比各维度的一次直观横评维度KubernetesSlurm裸金属脚本Ray在线服务托管强原生的Deployment/Service模型弱作业模型不适合常驻服务极弱靠手工拉起进程中等更偏计算而非托管GPU调度能力强配合DevicePlugin和调度器扩展强但局限在批处理场景无纯靠人分配弱依赖底层底座弹性扩缩容强HPA/KEDA等成熟方案弱主要针对作业排队无中等能扩计算节点但服务层不擅长故障恢复强探针自动重启节点自愈中等作业会重跑但服务不行极弱基本靠人中任务有重试但节点管理弱生态丰富度极高事实标准较高但偏向HPC领域最低较高但偏软件框架看完这个对比就很清楚不同方案各有自己的“舒适区”但AI平台要承载的负载太综合了——既有训练这种批处理又有推理这种在线服务还要求快速迭代、频繁发布、按流量伸缩。能同时把这些都接住的现阶段只有Kubernetes一个。3. 走完一圈答案还是 K8s平台底座能力拆解确定了K8s作为底座之后很多人的下一个问题是Kubernetes到底用什么机制支撑起AI平台或者说我凭哪些点可以跟领导/同事解释“必须上K8s”我把它拆成五个细节每个都是AI场景里天天会遇到的实际问题。3.1 GPU池化把“某块卡”变成“算力资源”物理机时代GPU是绑在机器上的K8s时代GPU变成了可以被调度的资源。这里面最核心的机制是Device Plugin——NVIDIA官方提供了一个DaemonSet每台机器上跑一个插件把本机的GPU数量和健康状态上报给Kubelet这样调度器就知道集群里总共有多少张卡、谁在用、谁空闲。有了这一层抽象你就可以实现真正的“GPU池化”。比如你有十台机器每台四张卡总共四十张卡K8s不关心你的Pod跑在哪台机器上它只关心能不能凑够你要的“nvidia.com/gpu: 1”。这对平台的意义非常大训练任务可以binpack把请求打满少数几台机器推理服务可以spread分散到多台机器降低单点风险混部才能成为可能。再往深走NVIDIA还提供了MIGMulti-Instance GPU支持一张A100可以切成多个小实例每个实例有自己的显存和计算单元。K8s配合MIG Plugin可以把一张物理卡当成7个逻辑卡来调度对小模型推理、小批量数据预处理这种场景特别划算。我在生产环境中就切过A100的MIG来跑轻量模型显存利用率和吞吐都能提升一大截。3.2 在线推理与离线训练的统一不再维护两套系统AI平台最尴尬的一个阶段是“训练一套、推理一套”。训练这边用队列系统跑批任务推理那边用进程守护脚本拉常驻服务两边互不相通监控、日志、告警各管各的SRE同学要多学两套工具。K8s对这个问题给出的答案是训练和推理都是Pod只是控制器的类型不同。训练跑批用Job/CronJob跑完就退出推理服务用Deployment保证有N个副本常驻。但底层的资源申请、调度、网络、存储、健康检查、日志采集全都是一样的。这意味着你只需要建设一套监控体系、一套发布流程、一套权限模型。我们迁移之后算法同学从提交训练到发布推理服务的路径变得高度一致先通过Git提交代码和镜像构建然后apply一份YAML剩下的事情平台负责。这个“统一”的价值很难用具体数字衡量但团队的认知负担和运维成本是实打实降下来了。3.3 靠指标伸缩不再靠“感觉”加机器为什么说K8s解决的是“平台问题”而不是“工具问题”一个很直观的例子就是弹性伸缩。裸机时代你要评估服务是否需要扩容得去看监控面板、看延迟、看排队数然后手动开单申请机器。整个过程以天为单位。K8s时代弹性伸缩是平台能力的一部分。推理服务通常配合HPAHorizontalPodAutoscaler使用监听CPU或自定义指标。AI场景里我强烈建议监听自定义指标比如“正在排队处理的请求数”或者“GPU的平均利用率”而不是单纯的CPU——因为GPU推理服务的CPU占用往往不高但GPU可能已经满载。我们用Prometheus Adapter把GPU利用率暴露成metric再配上KEDA的ScaledObject就实现了“当GPU利用率超过80%持续两分钟自动扩容两个副本”的效果。实测下来晚高峰扩容的响应时间从手工操作的半小时以上缩短到了几十秒级别“扩缩容”变成了一件不需要人参与的事。3.4 故障恢复的“免疫系统”从救火到自愈K8s最被低估的能力是它自带的“免疫系统”。每个Pod有探针liveness/readiness服务没起来就不给流量服务挂掉就自动重启。节点宕机了上面的Pod会被重新调度到健康节点。这些机制单独看都很简单但组合起来的效果是大部分线上故障在用户察觉之前就已经被处理掉了。对我们这种模型推理服务来说这个能力尤其重要。模型推理进程有一个特点——偶发性的显存OOMOut of Memory很难提前预测一旦发生进程就崩了。在裸机时代这可能意味着一个请求成功率下跌五分钟。在K8s里OOM之后Pod自动重启readiness探针自动把流量摘掉整个影响时间被压缩到几十秒以内通常只会在监控曲线里看到一个小毛刺用户完全无感。3.5 多租户隔离算法团队不互相“抢货”最后说说多租户。AI平台的用户通常不是一个人而是多个算法团队、多个业务线。在没有隔离机制的时候一个团队的大规模训练任务可能把集群资源吃光导致另一个团队的核心推理服务缩容这种“互相踩踏”在物理机时代非常难治理。K8s通过Namespace ResourceQuota LimitRange把集群切分成多个“租户”。每个团队只能动用自己配额内的资源超额了会直接报错。再配合Kueue这类队列组件可以让训练任务排队等待而不是一提交就抢占所有空余资源。设置好优先级之后“推理服务最高优先、批量任务次之、开发测试最后”的策略一下子就能落地团队之间不用再互相扯皮了。4. 实操落地AI 推理/训练服务迁到 K8s 的完整路径说了这么多“为什么”肯定要落到“怎么做”上。下面是我们迁移过程中用过的最小可复现方案适合已经有K8s集群、想把AI服务从裸机搬上去的团队做一个起步参考。4.1 最小可用的GPU推理服务长什么样先看一个最基础的GPU推理服务部署描述它会在集群里申请一块GPU跑起一个模型推理服务并暴露一个ClusterIP供内部调用。apiVersion: apps/v1 kind: Deployment metadata: name: llm-inference namespace: ai-platform spec: replicas: 2 selector: matchLabels: app: llm-inference template: metadata: labels: app: llm-inference spec: containers: - name: inference image: registry.example.com/llm-server:v1.2.0 ports: - containerPort: 8000 resources: limits: nvidia.com/gpu: 1 requests: nvidia.com/gpu: 1 readinessProbe: httpGet: path: /health port: 8000 initialDelaySeconds: 30 periodSeconds: 10 livenessProbe: httpGet: path: /health port: 8000 initialDelaySeconds: 60 periodSeconds: 30 --- apiVersion: v1 kind: Service metadata: name: llm-inference-svc namespace: ai-platform spec: selector: app: llm-inference ports: - port: 80 targetPort: 8000有几个细节我特别提醒一下。第一requests和limits的nvidia.com/gpu都填1这个不是CPU那种可以set大于request的设置GPU是严格配额必须一致否则调度器报错。第二readinessProbe的initialDelaySeconds不要设太短大模型启动要加载权重文件我们最早设10秒结果Pod还没起来就被探针判死重启进入死循环后来根据实测稳定启动时间设为30秒以上才稳。4.2 让流量进来Ingress与网关选型Service创建之后内部可以访问了但外部流量还是进不来。这时候需要Ingress或者网关层。我们直接沿用了集群里的Nginx Ingress Controller给上面那个推理服务指一个域名就完成了对外暴露。apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: llm-inference-ing namespace: ai-platform spec: ingressClassName: nginx rules: - host: llm.example.com http: paths: - path: / pathType: Prefix backend: service: name: llm-inference-svc port: number: 80如果服务对性能要求极高或者要支持流式输出SSE、WebSocket、gRPC这些协议建议在Ingress之上再考虑服务网格或者API网关但那是后话了。对大部分起步团队来说Nginx Ingress完全够用它天然支持WebSocket和SSE透传模型推理场景里常见的流式对话都没问题。4.3 训练任务怎么编排Job 与队列训练和推理不一样训练是“有始有终”的。一次训练任务跑完Pod就应该退出资源立即释放这正好是Kubernetes Job模型的语义。apiVersion: batch/v1 kind: Job metadata: name: fine-tune-job-001 namespace: ai-platform spec: completions: 1 parallelism: 1 backoffLimit: 2 template: spec: restartPolicy: OnFailure containers: - name: trainer image: registry.example.com/trainer:latest command: [python, train.py, --config, configs/lora.yaml] resources: limits: nvidia.com/gpu: 4 requests: nvidia.com/gpu: 4 volumeMounts: - name: model-storage mountPath: /data volumes: - name: model-storage persistentVolumeClaim: claimName: training-data-pvc如果同时提交训练任务的人很多建议在集群里装Kueue做队列管理。Kueue把训练Job放进队列里排队配合集群的配额可以避免集群资源被瞬间打满也能让多人提交任务时“有秩序”。没有队列的情况下多人同时提交Job资源会被瓜分得很随机关键任务反而要等这个体验很糟糕。4.4 存储规划模型文件放哪怎么读AI服务离不开存储模型权重文件通常几个GB到几十GB不等必须在K8s里规划好。我们的做法是用共享文件存储NFS/EFS这类挂载到所有需要加载模型的Pod上。模型发布新版本只需要把文件放到存储对应目录滚动重启Pod即可完成更新。但这里有个大坑共享存储的吞吐能力有限如果同时有二三十个Pod都在启动时从同一份存储加载几十GB的文件很容易把存储带宽打满启动时间从几分钟拖到几十分钟。我们的解法是把模型文件打包进镜像或者做成独立镜像启动时直接从本地读取同时保留一份共享存储用于读写小文件比如checkpoint、tokenizer配置。这个优化直接把我们的大模型实例启动时间从15分钟压缩到了3分钟。4.5 可观测性GPU利用率、日志、报警AI服务迁上K8s之后可观测性是保命的关键。我们这侧用的组合是Metrics Server负责CPU内存DCGM Exporter负责GPU指标Prometheus负责抓取Grafana负责展示。部署完这套之后每张GPU的利用率、显存占用、温度、功耗全部可视化了这比裸机时代“进机器敲nvidia-smi”后再判断策略要直观太多。日志方面我们用Fluent Bit把Pod的stdout采集到统一日志平台检索一次跨多实例的推理日志不用再登录每台机器grep。告警规则也顺手配置了一批GPU利用率超过90%持续5分钟就告警、Pod重启次数超过阈值就告警、Pod Pending时间超过10分钟就告警。这些规则帮助我们在服务出问题之前就把原因找出来了。5. 踩坑实录几个值得说道的教训最后聊几个我们迁移过程中真实踩过的坑这些细节在官方文档里通常不会写但对后来者非常有用。5.1 探针超时设置不合理导致“自杀式重启”这个前面隐约提过。早期我们给一个大模型推理服务配readinessProbeinitialDelaySeconds设了10秒periodSeconds设了5秒。结果Pod要花20多秒加载模型探针在加载期间连续打到失败K8s判定Pod不健康把它杀掉重启新Pod起来又走到同样的命运。从日志看Pod像疯了一样反复重启GPU资源被白白占着。后续调整成initialDelaySeconds: 60periodSeconds: 30timeoutSeconds: 5然后跑了一个周再也没有误杀过。给AI推理服务配探针务必先量一下正常启动时间留足余量。5.2 GPU显存的“超卖幻觉”K8s的DevicePlugin上报给调度器的是物理卡的数量而不是显存大小。这就导致一个现象你申请的nvidia.com/gpu: 1调度器只知道“给你一张卡”但如果你这张卡是A100 80G你在容器里实际能用80G如果你拿到的是T4 16G那你最多只能用16G。所以在混用不同GPU型号的集群里Pod被调度到小显存机器上可能直接OOM。更隐蔽的是K8s默认不对GPU显存做“资源隔离”逻辑一个Pod卡死了它占用的显存可能在进程退出后不会立即释放。我们后来通过给每台机器打GPU型号标签并在调度时用nodeSelector强制特定模型只跑在符合显存要求的节点上这类问题才彻底解决。5.3 大镜像拉取拖垮启动速度也拖垮集群网络AI镜像动辄几个GB甚至十几GB里面装了一堆CUDA依赖和Python包。如果每次发布都把全部文件重新推到仓库、再被所有节点拉取集群网络和镜像仓库压力都会很大。我们初期没注意发布频率一高宿主机上的镜像存储空间很快告急拉取耗时也很长。后面做了三件事基础镜像尽量复用CUDA和Python依赖层单独打一层只有代码层变化频繁开启Harbor的镜像加速和清理策略节点本地缓存镜像预热把发布高峰要用的镜像提前拉好。这三件事做完发布耗时从十几分钟降到两三分钟镜像仓库也稳了。5.4 把K8s当成“便宜的Slurm”用训练体验反而下降有些团队只看中K8s的调度能力把训练任务全塞进去配置一个很大的Job一把梭。结果发现K8s对长时训练任务的故障恢复、断点续训、优先级抢占都没有Slurm那么成熟。训练跑到一半节点宕机Job被重新调度但如果没有设计好checkpoint保存重跑还是从头开始成本反而比之前更高。我对团队的建议是训练任务上线前一定要在代码里设计好“周期性保存checkpoint到持久化存储”的机制并且把Job的restartPolicy设置成OnFailure配合Kueue做队列管理。K8s不是不能跑训练而是要用对待“平台”的态度去设计训练任务而不是简单套用一个YAML模板。5.5 弹性伸缩有“假死期”扩容不能等被打满再动HPA的默认行为是持续超过阈值一段时间才扩容这个“一段时间”在AI场景里可能是致命的。我们的高峰期流量是陡增型的晚上八点用户一下子涌进来HPA检测到GPU利用率超过80%还要再等几分钟才扩容新Pod镜像拉取加模型加载又花几分钟等新副本真正接流量早期用户已经感受到了延迟。后来我们改成了“提前扩容”策略KEDA监听消息队列的堆积长度堆积数一超过阈值就提前扩容同时把HPA的扩容周期调短并设置了一个“最小副本数”作为保底避免从零开始扩容。另外容器的启动命令里做了模型预加载和进程预热让新副本一旦ready就能立刻接流量尽量缩短“假死期”。最后说点实在的这套平台迁到Kubernetes之后我们内部最大的感受不是“用了什么新潮技术”而是“终于有了一个统一的基础”。以前管GPU、管训练、管推理、管模型发布各是一摊事现在它们都是Kubernetes上的对象可以用同一套工具、同一套流程去管理。迁移之前我们的GPU平均利用率不到30%高峰扩容按天计发布模型靠手工迁移之后平均利用率能到40%以上晚高峰可以稳定在60%到70%扩容缩容不用人盯着模型发布变成了Git提交和YAML应用这两步。我个人体会是AI平台选择Kubernetes不是因为它是最完美的调度器而是因为它给了整个平台一个足够稳定的底座。上面可以跑vLLM、可以跑Ray、可以跑Kubeflow未来出现新的AI框架依然可以把它装进容器、跑在K8s上。框架会变模型会变但“用统一底座承载AI负载”这个方向已经很清晰了。如果你的团队也正在被GPU资源碎片化、发布效率低、扩缩容慢这些问题困扰不妨从最小的推理服务开始试着往K8s上挪一挪——不用一步到位先让一小部分业务跑起来你会很快感受到那些“老问题”正在一个个消失。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑