9.人工智能实战:GPU 服务如何上 Kubernetes?从单机部署到 K8s + NVIDIA Device Plugin + HPA 的生产级改造
人工智能实战GPU 服务如何上 Kubernetes从单机部署到 K8s NVIDIA Device Plugin HPA 的生产级改造一、问题场景单机部署能跑但一上线就难维护前面我们已经把大模型服务做到了1. vLLM 高吞吐推理 2. Redis 队列削峰 3. Prometheus 监控 4. 限流、熔断、降级单机部署时启动方式大概是CUDA_VISIBLE_DEVICES0python-mvllm.entrypoints.openai.api_server--modelxxx--port8001CUDA_VISIBLE_DEVICES1python-mvllm.entrypoints.openai.api_server--modelxxx--port8002uvicorn app:app--port8000刚开始这套方式很方便。但当服务进入准生产环境后问题逐渐暴露1. 某个进程挂了需要人工重启 2. GPU 资源分配靠人记 3. 多台机器部署容易混乱 4. 服务扩容要手动改端口、改配置 5. 发布新版本时容易影响正在运行的服务 6. 监控、日志、重启策略都不统一这时问题已经不再是“模型怎么跑”而是大模型服务如何工程化运维。Kubernetes 的价值就在这里。它不是为了让部署变复杂而是为了解决调度、隔离、重启、扩容、发布、观测这篇文章就从真实部署问题出发讲清楚如何把一个 GPU 大模型服务迁移到 Kubernetes。二、真实问题为什么不能一直用裸机部署裸机部署有一个致命缺点资源和服务强绑定。例如A 模型占 GPU0 B 模型占 GPU1 C 服务监听 8000 D 服务监听 8001随着服务增多你会开始维护各种表格哪台机器 哪个 GPU 哪个端口 哪个模型 哪个进程 谁负责重启这类系统早期能跑但后期一定会乱。典型事故包括1. 两个模型抢同一张 GPU 2. 服务挂了没人发现 3. 升级版本后旧进程没杀干净 4. GPU 显存被僵尸进程占用 5. 手动扩容后负载均衡没更新所以大模型系统到一定规模后必须从进程管理升级到资源调度Kubernetes 解决的正是这个问题。三、K8s 部署 GPU 服务的核心概念很多人第一次在 K8s 上跑 GPU 服务会踩坑主要是因为把它当成普通 Deployment。普通 CPU 服务只需要resources:requests:cpu:1memory:2Gi但 GPU 不是普通资源。Kubernetes 默认并不知道你的节点上有 NVIDIA GPU。你需要安装NVIDIA Device Plugin安装后K8s 才能识别nvidia.com/gpu然后你才能在 Pod 中声明resources:limits:nvidia.com/gpu:1这句话的意思是这个 Pod 需要独占 1 张 GPU。注意是独占不是共享。四、目标架构迁移后的目标架构如下Client ↓ Ingress / Nginx ↓ LLM Gateway Deployment ↓ LLM vLLM Deployment ↓ GPU Node如果加上队列Client ↓ API Gateway ↓ Redis Queue ↓ Worker Deployment ↓ vLLM Service ↓ GPU Pod这里建议把系统拆成两类服务1. Gateway轻量 CPU 服务负责鉴权、限流、路由、日志 2. vLLM重型 GPU 服务负责模型推理不要把业务逻辑和模型推理写在一个 Pod 里。这样后续才能做到业务服务独立扩容 GPU 服务独立扩容 模型服务独立升级五、可复现前置条件需要准备1. 一个 Kubernetes 集群 2. 至少一个带 NVIDIA GPU 的节点 3. 节点已安装 NVIDIA Driver 4. kubectl 可正常访问集群 5. 容器镜像仓库检查 GPU 节点nvidia-smi检查节点kubectl get nodes-owide六、安装 NVIDIA Device Plugin执行kubectl apply-fhttps://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/v0.14.5/nvidia-device-plugin.yml查看插件状态kubectl get pods-nkube-system|grepnvidia查看节点是否识别 GPUkubectl describenodeyour-gpu-node-name|grepnvidia.com/gpu正常应该看到类似nvidia.com/gpu: 1如果没有看到通常是1. NVIDIA Driver 没装好 2. nvidia-container-toolkit 没装 3. device plugin 没启动成功七、构建一个可部署的 vLLM 镜像创建DockerfileFROM nvidia/cuda:12.1.1-runtime-ubuntu22.04 WORKDIR /app RUN apt-get update apt-get install -y \ python3 \ python3-pip \ rm -rf /var/lib/apt/lists/* RUN pip3 install vllm fastapi uvicorn EXPOSE 8000 CMD [python3, -m, vllm.entrypoints.openai.api_server, \ --model, Qwen/Qwen2.5-0.5B-Instruct, \ --host, 0.0.0.0, \ --port, 8000, \ --trust-remote-code, \ --gpu-memory-utilization, 0.80, \ --max-model-len, 2048]构建镜像dockerbuild-tyour-registry/llm-vllm:qwen-0.5b.推送镜像dockerpush your-registry/llm-vllm:qwen-0.5b注意生产环境建议把模型提前下载到镜像或挂载持久化缓存目录。 否则每次 Pod 启动都要重新下载模型会非常慢。八、Deployment 部署 vLLM创建vllm-deployment.yamlapiVersion:apps/v1kind:Deploymentmetadata:name:vllm-qwenlabels:app:vllm-qwenspec:replicas:1selector:matchLabels:app:vllm-qwentemplate:metadata:labels:app:vllm-qwenspec:containers:-name:vllmimage:your-registry/llm-vllm:qwen-0.5bports:-containerPort:8000resources:limits:nvidia.com/gpu:1memory:24Gicpu:4requests:memory:16Gicpu:2readinessProbe:httpGet:path:/healthport:8000initialDelaySeconds:60periodSeconds:10failureThreshold:12livenessProbe:httpGet:path:/healthport:8000initialDelaySeconds:120periodSeconds:20failureThreshold:3部署kubectl apply-fvllm-deployment.yaml查看kubectl get pods-lappvllm-qwen九、Service 暴露 vLLM创建vllm-service.yamlapiVersion:v1kind:Servicemetadata:name:vllm-qwenspec:selector:app:vllm-qwenports:-protocol:TCPport:8000targetPort:8000type:ClusterIP应用kubectl apply-fvllm-service.yaml集群内部访问地址http://vllm-qwen:8000测试kubectl run curl-test--imagecurlimages/curl-it--rm--sh在容器中执行curlhttp://vllm-qwen:8000/health十、部署 Gateway 服务Gateway 不需要 GPU它负责调用 vLLM。gateway.pyimporthttpxfromfastapiimportFastAPI,HTTPExceptionfrompydanticimportBaseModel,Field appFastAPI(titleLLM Gateway)VLLM_URLhttp://vllm-qwen:8000/v1/chat/completionsMODEL_NAMEQwen/Qwen2.5-0.5B-InstructclassChatRequest(BaseModel):prompt:strField(...,min_length1,max_length2000)max_tokens:intField(default128,ge1,le512)app.post(/chat)asyncdefchat(req:ChatRequest):payload{model:MODEL_NAME,messages:[{role:user,content:req.prompt}],max_tokens:req.max_tokens,temperature:0.7}try:asyncwithhttpx.AsyncClient(timeout60)asclient:respawaitclient.post(VLLM_URL,jsonpayload)resp.raise_for_status()dataresp.json()return{answer:data[choices][0][message][content]}exceptExceptionase:raiseHTTPException(500,fLLM call failed:{str(e)})app.get(/health)defhealth():return{status:ok}Gateway DeploymentapiVersion:apps/v1kind:Deploymentmetadata:name:llm-gatewayspec:replicas:2selector:matchLabels:app:llm-gatewaytemplate:metadata:labels:app:llm-gatewayspec:containers:-name:gatewayimage:your-registry/llm-gateway:latestports:-containerPort:8000resources:requests:cpu:500mmemory:512Milimits:cpu:1memory:1Gi十一、为什么 vLLM Pod 不建议一开始多副本很多人会直接写replicas:4但 GPU 服务不能这样随便扩。因为1 个 vLLM Pod 1 张 GPU如果你的节点只有 2 张 GPU却写了 4 个副本结果就是2 个 Running 2 个 Pending查看kubectl get podsPending 原因kubectl describe podpod-name通常会看到Insufficient nvidia.com/gpu这不是错误而是资源不够。十二、HPA 为什么不能直接按 GPU 扩容K8s 默认 HPA 支持CPU Memory但不直接支持GPU Utilization Queue Size P99 Latency而大模型服务真正应该按什么扩容通常不是 CPU而是1. 队列长度 2. P95 / P99 延迟 3. GPU 利用率 4. 请求等待时间所以生产环境更推荐KEDA Prometheus例如按队列长度扩容 Worker。十三、Worker 按队列扩容思路如果你的架构是Gateway → Redis Queue → Worker → vLLM那么 Worker 可以用 KEDA 根据 Redis 队列长度扩容。示意配置apiVersion:keda.sh/v1alpha1kind:ScaledObjectmetadata:name:llm-worker-scalerspec:scaleTargetRef:name:llm-workerminReplicaCount:1maxReplicaCount:5triggers:-type:redismetadata:address:redis.default.svc.cluster.local:6379listName:llm_queuelistLength:20这表示当 Redis 队列长度变长时自动扩容 Worker。注意Worker 扩容不等于 vLLM 扩容。 如果 GPU 已经满了盲目增加 Worker 只会让请求争抢更严重。十四、验证部署是否成功1. 查看 Podkubectl get pods2. 查看 GPU 分配kubectl describenodegpu-node-name|grep-A5nvidia.com/gpu3. 查看日志kubectl logs-fdeploy/vllm-qwen4. 端口转发测试kubectl port-forward svc/llm-gateway8080:8000请求curl-XPOSThttp://127.0.0.1:8080/chat\-HContent-Type: application/json\-d{ prompt: 解释一下Kubernetes中GPU调度的原理, max_tokens: 128 }十五、踩坑记录坑 1Pod 一直 Pending现象Pod Pending查看kubectl describe podpod-name如果看到Insufficient nvidia.com/gpu说明 GPU 不够或者 Device Plugin 没生效。坑 2容器里无法访问 GPU进入容器kubectlexec-itpod-name--bashnvidia-smi如果提示找不到 GPU检查1. 节点驱动 2. nvidia-container-toolkit 3. NVIDIA Device Plugin 4. Pod 是否声明 nvidia.com/gpu坑 3readinessProbe 设置太短大模型服务启动慢尤其是首次加载模型。如果 readinessProbe 太激进K8s 会认为服务不可用。建议initialDelaySeconds:60failureThreshold:12大模型越大这个时间越要加长。坑 4每次 Pod 重启都重新下载模型这会导致Pod 启动非常慢 镜像拉取正常但模型下载卡住解决方案1. 使用持久化模型缓存 2. 将模型预置到镜像 3. 使用 initContainer 预拉模型坑 5Gateway 和 vLLM 混在一个容器短期方便长期麻烦。问题包括1. 业务逻辑无法独立升级 2. 模型服务无法独立扩容 3. 故障边界不清晰 4. CPU 服务被 GPU 服务拖累建议拆开。十六、适合收藏的 K8s GPU 部署 Checklist基础环境 [ ] GPU 节点能执行 nvidia-smi [ ] 已安装 nvidia-container-toolkit [ ] 已安装 NVIDIA Device Plugin [ ] kubectl describe node 能看到 nvidia.com/gpu Deployment [ ] Pod 声明 nvidia.com/gpu [ ] readinessProbe 时间足够长 [ ] livenessProbe 不要过于激进 [ ] 模型缓存已处理 [ ] 资源 requests / limits 合理 架构 [ ] Gateway 和 vLLM 分离 [ ] vLLM 通过 ClusterIP 暴露 [ ] 外部只暴露 Gateway [ ] GPU 服务有独立监控 [ ] Pod Pending 有排查流程十七、验证结果迁移到 Kubernetes 后系统变化主要体现在运维能力上1. 进程挂了可以自动重启 2. 服务发布更规范 3. Gateway 可以水平扩容 4. GPU 资源分配更清晰 5. Pod 状态可观测 6. 后续接入 Prometheus、KEDA 更方便但也要注意Kubernetes 不会自动让模型更快。它解决的是资源调度和服务治理问题。如果你的推理本身很慢仍然要从模型、vLLM、batch、KV Cache 角度优化。十八、经验总结这次迁移给我的核心经验是裸机部署适合验证Kubernetes 适合生产治理。大模型服务进入生产后需要的不是简单启动一个 Python 进程而是1. 资源隔离 2. 自动重启 3. 滚动发布 4. 服务发现 5. 监控告警 6. 弹性扩容K8s 的价值不在于让部署更酷而在于让系统可维护。十九、优化建议后续可以继续做1. 使用 Helm 管理部署模板 2. 使用 KEDA 按队列长度扩容 Worker 3. 使用 Prometheus Adapter 按自定义指标扩容 4. 使用 NodeSelector / Taints 精确调度 GPU 节点 5. 使用模型缓存 PVC 加速 Pod 启动 6. 使用蓝绿发布降低模型升级风险 7. 使用多 vLLM 实例做模型路由一句话总结当大模型服务从 Demo 走向生产K8s 不是加分项而是工程治理的起点。