9.人工智能实战:GPU 服务如何上 Kubernetes?从单机部署到 K8s + NVIDIA Device Plugin + HPA 的生产级改造

人工智能实战:GPU 服务如何上 Kubernetes?从单机部署到 K8s + NVIDIA Device Plugin + HPA 的生产级改造


一、问题场景:单机部署能跑,但一上线就难维护

前面我们已经把大模型服务做到了:

text 复制代码
1. vLLM 高吞吐推理
2. Redis 队列削峰
3. Prometheus 监控
4. 限流、熔断、降级

单机部署时,启动方式大概是:

bash 复制代码
CUDA_VISIBLE_DEVICES=0 python -m vllm.entrypoints.openai.api_server --model xxx --port 8001
CUDA_VISIBLE_DEVICES=1 python -m vllm.entrypoints.openai.api_server --model xxx --port 8002
uvicorn app:app --port 8000

刚开始这套方式很方便。

但当服务进入准生产环境后,问题逐渐暴露:

text 复制代码
1. 某个进程挂了,需要人工重启
2. GPU 资源分配靠人记
3. 多台机器部署容易混乱
4. 服务扩容要手动改端口、改配置
5. 发布新版本时容易影响正在运行的服务
6. 监控、日志、重启策略都不统一

这时问题已经不再是"模型怎么跑",而是:

text 复制代码
大模型服务如何工程化运维。

Kubernetes 的价值就在这里。

它不是为了让部署变复杂,而是为了解决:

text 复制代码
调度、隔离、重启、扩容、发布、观测

这篇文章就从真实部署问题出发,讲清楚如何把一个 GPU 大模型服务迁移到 Kubernetes。


二、真实问题:为什么不能一直用裸机部署?

裸机部署有一个致命缺点:

text 复制代码
资源和服务强绑定。

例如:

text 复制代码
A 模型占 GPU0
B 模型占 GPU1
C 服务监听 8000
D 服务监听 8001

随着服务增多,你会开始维护各种表格:

text 复制代码
哪台机器
哪个 GPU
哪个端口
哪个模型
哪个进程
谁负责重启

这类系统早期能跑,但后期一定会乱。

典型事故包括:

text 复制代码
1. 两个模型抢同一张 GPU
2. 服务挂了没人发现
3. 升级版本后旧进程没杀干净
4. GPU 显存被僵尸进程占用
5. 手动扩容后负载均衡没更新

所以,大模型系统到一定规模后,必须从:

text 复制代码
进程管理

升级到:

text 复制代码
资源调度

Kubernetes 解决的正是这个问题。


三、K8s 部署 GPU 服务的核心概念

很多人第一次在 K8s 上跑 GPU 服务会踩坑,主要是因为把它当成普通 Deployment。

普通 CPU 服务只需要:

yaml 复制代码
resources:
  requests:
    cpu: "1"
    memory: "2Gi"

但 GPU 不是普通资源。

Kubernetes 默认并不知道你的节点上有 NVIDIA GPU。

你需要安装:

text 复制代码
NVIDIA Device Plugin

安装后,K8s 才能识别:

text 复制代码
nvidia.com/gpu

然后你才能在 Pod 中声明:

yaml 复制代码
resources:
  limits:
    nvidia.com/gpu: 1

这句话的意思是:

text 复制代码
这个 Pod 需要独占 1 张 GPU。

注意,是独占,不是共享。


四、目标架构

迁移后的目标架构如下:

text 复制代码
Client
  ↓
Ingress / Nginx
  ↓
LLM Gateway Deployment
  ↓
LLM vLLM Deployment
  ↓
GPU Node

如果加上队列:

text 复制代码
Client
  ↓
API Gateway
  ↓
Redis Queue
  ↓
Worker Deployment
  ↓
vLLM Service
  ↓
GPU Pod

这里建议把系统拆成两类服务:

text 复制代码
1. Gateway:轻量 CPU 服务,负责鉴权、限流、路由、日志
2. vLLM:重型 GPU 服务,负责模型推理

不要把业务逻辑和模型推理写在一个 Pod 里。

这样后续才能做到:

text 复制代码
业务服务独立扩容
GPU 服务独立扩容
模型服务独立升级

五、可复现前置条件

需要准备:

text 复制代码
1. 一个 Kubernetes 集群
2. 至少一个带 NVIDIA GPU 的节点
3. 节点已安装 NVIDIA Driver
4. kubectl 可正常访问集群
5. 容器镜像仓库

检查 GPU 节点:

bash 复制代码
nvidia-smi

检查节点:

bash 复制代码
kubectl get nodes -o wide

六、安装 NVIDIA Device Plugin

执行:

bash 复制代码
kubectl apply -f https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/v0.14.5/nvidia-device-plugin.yml

查看插件状态:

bash 复制代码
kubectl get pods -n kube-system | grep nvidia

查看节点是否识别 GPU:

bash 复制代码
kubectl describe node <your-gpu-node-name> | grep nvidia.com/gpu

正常应该看到类似:

text 复制代码
nvidia.com/gpu: 1

如果没有看到,通常是:

text 复制代码
1. NVIDIA Driver 没装好
2. nvidia-container-toolkit 没装
3. device plugin 没启动成功

七、构建一个可部署的 vLLM 镜像

创建 Dockerfile

dockerfile 复制代码
FROM 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"]

构建镜像:

bash 复制代码
docker build -t your-registry/llm-vllm:qwen-0.5b .

推送镜像:

bash 复制代码
docker push your-registry/llm-vllm:qwen-0.5b

注意:

text 复制代码
生产环境建议把模型提前下载到镜像或挂载持久化缓存目录。
否则每次 Pod 启动都要重新下载模型,会非常慢。

八、Deployment 部署 vLLM

创建 vllm-deployment.yaml

yaml 复制代码
apiVersion: apps/v1
kind: Deployment
metadata:
  name: vllm-qwen
  labels:
    app: vllm-qwen
spec:
  replicas: 1
  selector:
    matchLabels:
      app: vllm-qwen
  template:
    metadata:
      labels:
        app: vllm-qwen
    spec:
      containers:
        - name: vllm
          image: your-registry/llm-vllm:qwen-0.5b
          ports:
            - containerPort: 8000
          resources:
            limits:
              nvidia.com/gpu: 1
              memory: "24Gi"
              cpu: "4"
            requests:
              memory: "16Gi"
              cpu: "2"
          readinessProbe:
            httpGet:
              path: /health
              port: 8000
            initialDelaySeconds: 60
            periodSeconds: 10
            failureThreshold: 12
          livenessProbe:
            httpGet:
              path: /health
              port: 8000
            initialDelaySeconds: 120
            periodSeconds: 20
            failureThreshold: 3

部署:

bash 复制代码
kubectl apply -f vllm-deployment.yaml

查看:

bash 复制代码
kubectl get pods -l app=vllm-qwen

九、Service 暴露 vLLM

创建 vllm-service.yaml

yaml 复制代码
apiVersion: v1
kind: Service
metadata:
  name: vllm-qwen
spec:
  selector:
    app: vllm-qwen
  ports:
    - protocol: TCP
      port: 8000
      targetPort: 8000
  type: ClusterIP

应用:

bash 复制代码
kubectl apply -f vllm-service.yaml

集群内部访问地址:

text 复制代码
http://vllm-qwen:8000

测试:

bash 复制代码
kubectl run curl-test --image=curlimages/curl -it --rm -- sh

在容器中执行:

bash 复制代码
curl http://vllm-qwen:8000/health

十、部署 Gateway 服务

Gateway 不需要 GPU,它负责调用 vLLM。

gateway.py

python 复制代码
import httpx
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field

app = FastAPI(title="LLM Gateway")

VLLM_URL = "http://vllm-qwen:8000/v1/chat/completions"
MODEL_NAME = "Qwen/Qwen2.5-0.5B-Instruct"


class ChatRequest(BaseModel):
    prompt: str = Field(..., min_length=1, max_length=2000)
    max_tokens: int = Field(default=128, ge=1, le=512)


@app.post("/chat")
async def chat(req: ChatRequest):
    payload = {
        "model": MODEL_NAME,
        "messages": [
            {"role": "user", "content": req.prompt}
        ],
        "max_tokens": req.max_tokens,
        "temperature": 0.7
    }

    try:
        async with httpx.AsyncClient(timeout=60) as client:
            resp = await client.post(VLLM_URL, json=payload)
            resp.raise_for_status()
            data = resp.json()

        return {
            "answer": data["choices"][0]["message"]["content"]
        }

    except Exception as e:
        raise HTTPException(500, f"LLM call failed: {str(e)}")


@app.get("/health")
def health():
    return {"status": "ok"}

Gateway Deployment:

yaml 复制代码
apiVersion: apps/v1
kind: Deployment
metadata:
  name: llm-gateway
spec:
  replicas: 2
  selector:
    matchLabels:
      app: llm-gateway
  template:
    metadata:
      labels:
        app: llm-gateway
    spec:
      containers:
        - name: gateway
          image: your-registry/llm-gateway:latest
          ports:
            - containerPort: 8000
          resources:
            requests:
              cpu: "500m"
              memory: "512Mi"
            limits:
              cpu: "1"
              memory: "1Gi"

十一、为什么 vLLM Pod 不建议一开始多副本?

很多人会直接写:

yaml 复制代码
replicas: 4

但 GPU 服务不能这样随便扩。

因为:

text 复制代码
1 个 vLLM Pod = 1 张 GPU

如果你的节点只有 2 张 GPU,却写了 4 个副本,结果就是:

text 复制代码
2 个 Running
2 个 Pending

查看:

bash 复制代码
kubectl get pods

Pending 原因:

bash 复制代码
kubectl describe pod <pod-name>

通常会看到:

text 复制代码
Insufficient nvidia.com/gpu

这不是错误,而是资源不够。


十二、HPA 为什么不能直接按 GPU 扩容?

K8s 默认 HPA 支持:

text 复制代码
CPU
Memory

但不直接支持:

text 复制代码
GPU Utilization
Queue Size
P99 Latency

而大模型服务真正应该按什么扩容?

通常不是 CPU,而是:

text 复制代码
1. 队列长度
2. P95 / P99 延迟
3. GPU 利用率
4. 请求等待时间

所以生产环境更推荐:

text 复制代码
KEDA + Prometheus

例如按队列长度扩容 Worker。


十三、Worker 按队列扩容思路

如果你的架构是:

text 复制代码
Gateway → Redis Queue → Worker → vLLM

那么 Worker 可以用 KEDA 根据 Redis 队列长度扩容。

示意配置:

yaml 复制代码
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: llm-worker-scaler
spec:
  scaleTargetRef:
    name: llm-worker
  minReplicaCount: 1
  maxReplicaCount: 5
  triggers:
    - type: redis
      metadata:
        address: redis.default.svc.cluster.local:6379
        listName: llm_queue
        listLength: "20"

这表示:

text 复制代码
当 Redis 队列长度变长时,自动扩容 Worker。

注意:

text 复制代码
Worker 扩容不等于 vLLM 扩容。
如果 GPU 已经满了,盲目增加 Worker 只会让请求争抢更严重。

十四、验证部署是否成功

1. 查看 Pod

bash 复制代码
kubectl get pods

2. 查看 GPU 分配

bash 复制代码
kubectl describe node <gpu-node-name> | grep -A 5 nvidia.com/gpu

3. 查看日志

bash 复制代码
kubectl logs -f deploy/vllm-qwen

4. 端口转发测试

bash 复制代码
kubectl port-forward svc/llm-gateway 8080:8000

请求:

bash 复制代码
curl -X POST "http://127.0.0.1:8080/chat" \
-H "Content-Type: application/json" \
-d '{
  "prompt": "解释一下Kubernetes中GPU调度的原理",
  "max_tokens": 128
}'

十五、踩坑记录

坑 1:Pod 一直 Pending

现象:

text 复制代码
Pod Pending

查看:

bash 复制代码
kubectl describe pod <pod-name>

如果看到:

text 复制代码
Insufficient nvidia.com/gpu

说明 GPU 不够,或者 Device Plugin 没生效。


坑 2:容器里无法访问 GPU

进入容器:

bash 复制代码
kubectl exec -it <pod-name> -- bash
nvidia-smi

如果提示找不到 GPU,检查:

text 复制代码
1. 节点驱动
2. nvidia-container-toolkit
3. NVIDIA Device Plugin
4. Pod 是否声明 nvidia.com/gpu

坑 3:readinessProbe 设置太短

大模型服务启动慢,尤其是首次加载模型。

如果 readinessProbe 太激进,K8s 会认为服务不可用。

建议:

yaml 复制代码
initialDelaySeconds: 60
failureThreshold: 12

大模型越大,这个时间越要加长。


坑 4:每次 Pod 重启都重新下载模型

这会导致:

text 复制代码
Pod 启动非常慢
镜像拉取正常但模型下载卡住

解决方案:

text 复制代码
1. 使用持久化模型缓存
2. 将模型预置到镜像
3. 使用 initContainer 预拉模型

坑 5:Gateway 和 vLLM 混在一个容器

短期方便,长期麻烦。

问题包括:

text 复制代码
1. 业务逻辑无法独立升级
2. 模型服务无法独立扩容
3. 故障边界不清晰
4. CPU 服务被 GPU 服务拖累

建议拆开。


十六、适合收藏的 K8s GPU 部署 Checklist

text 复制代码
基础环境:
[ ] 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 后,系统变化主要体现在运维能力上:

text 复制代码
1. 进程挂了可以自动重启
2. 服务发布更规范
3. Gateway 可以水平扩容
4. GPU 资源分配更清晰
5. Pod 状态可观测
6. 后续接入 Prometheus、KEDA 更方便

但也要注意:

text 复制代码
Kubernetes 不会自动让模型更快。

它解决的是:

text 复制代码
资源调度和服务治理问题。

如果你的推理本身很慢,仍然要从模型、vLLM、batch、KV Cache 角度优化。


十八、经验总结

这次迁移给我的核心经验是:

text 复制代码
裸机部署适合验证,Kubernetes 适合生产治理。

大模型服务进入生产后,需要的不是简单启动一个 Python 进程,而是:

text 复制代码
1. 资源隔离
2. 自动重启
3. 滚动发布
4. 服务发现
5. 监控告警
6. 弹性扩容

K8s 的价值不在于让部署更酷,而在于让系统可维护。


十九、优化建议

后续可以继续做:

text 复制代码
1. 使用 Helm 管理部署模板
2. 使用 KEDA 按队列长度扩容 Worker
3. 使用 Prometheus Adapter 按自定义指标扩容
4. 使用 NodeSelector / Taints 精确调度 GPU 节点
5. 使用模型缓存 PVC 加速 Pod 启动
6. 使用蓝绿发布降低模型升级风险
7. 使用多 vLLM 实例做模型路由

一句话总结:

text 复制代码
当大模型服务从 Demo 走向生产,K8s 不是加分项,而是工程治理的起点。
相关推荐
Slow菜鸟1 小时前
AI学习篇(四) | AI设计类Skills推荐清单(2026年)
人工智能·学习
迦南的迦 亚索的索1 小时前
AI_11_Coze_AI面试助手
人工智能·面试·职场和发展
pangtout1 小时前
国云强智:天翼云押注Token,争夺AI时代新入口
人工智能
dog2501 小时前
圆锥曲线和二次曲线
开发语言·网络·人工智能·算法·php
岛雨QA1 小时前
🎉Token自由-Ollama部署本地大模型超详细操作指南
人工智能·llm·ollama
云游1 小时前
从“人工打补丁”到“自主进化”:多轮对话文本转SQL智能体的技术跃迁
人工智能·文本转sql
区块block1 小时前
Infinity Alpha(无限阿尔法)即将发布纯链上AI收益引擎通证IA
人工智能·区块链
有为少年1 小时前
从概率估计到“LLM 训练是有损压缩”
人工智能·线性代数·机器学习·计算机视觉·矩阵
迦南的迦 亚索的索1 小时前
AI_10_Coze_Multi-Agent多智能体
人工智能