Kubernetes GPU 集群部署 LLM 实战:Llama 3.3 70B 和 DeepSeek R1
教程详细演示如何构建轻量级 LLM 网关、容器化并部署到带 GPU 支持的 K8s 集群,使用 Oxlo.ai 进行推理。
完整中文译文
我们需要让内部平台工具能够访问 Llama 3.3 70B 和 DeepSeek R1,又不想为模型权重配置 PB 级别的 PersistentVolume。本教程将构建一个轻量级 LLM 网关,容器化它,然后部署到支持 GPU 的 Kubernetes 集群,由 Oxlo.ai 提供推理服务。
前提条件
- 一个已运行的支持 GPU 的 Kubernetes 集群(已安装 NVIDIA device plugin),kubectl 已配置好
- OpenAI SDK 和 FastAPI:
pip install openai fastapi uvicorn - 从 https://portal.oxlo.ai 获取 Oxlo.ai API key
步骤 1:编写网关服务
我先锁定 system prompt。我希望这个 Agent 扮演一位资深平台工程师,而不是一个通用的聊天机器人。
SYSTEM_PROMPT = """You are a senior platform engineer. Answer concisely.
Prefer code over prose. If you do not know something, say so."""
然后把它接入一个 FastAPI 应用。我使用 OpenAI SDK,因为 Oxlo.ai 暴露的是完全兼容的 API。
import os
from fastapi import FastAPI
from pydantic import BaseModel
from openai import OpenAI
app = FastAPI()
client = OpenAI(
base_url="https://api.oxlo.ai/v1",
api_key=os.environ["OXLO_API_KEY"],
)
class ChatRequest(BaseModel):
message: str
@app.post("/chat")
async def chat(req: ChatRequest):
response = client.chat.completions.create(
model="llama-3.3-70b",
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": req.message},
],
)
return {"response": response.choices[0].message.content}
步骤 2:容器化应用
我把镜像做得很小,这样在新节点上拉取更快。用的是 slim Python 基础镜像,然后安装依赖。
# requirements.txt
fastapi
uvicorn
openai
pydantic
# Dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY main.py .
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
步骤 3:在 Kubernetes 中存储 Oxlo.ai API key
我创建了一个 Secret,这样密钥永远不会打到容器镜像里。
apiVersion: v1
kind: Secret
metadata:
name: oxlo.ai-secret
namespace: default
type: Opaque
stringData:
OXLO_API_KEY: "YOUR_OXLO_API_KEY"
步骤 4:部署到支持 GPU 的节点池
我们的集群运行着用于训练和推理工作负载的 GPU 节点。我添加了 nodeSelector 和 toleration,让网关落到那个节点池里,但没有为容器本身申请 NVIDIA device。Oxlo.ai 托管模型,所以我们不需要为 70B 权重预留本地 GPU 内存。
apiVersion: apps/v1
kind: Deployment
metadata:
name: llm-gateway
namespace: default
spec:
replicas: 2
selector:
matchLabels:
app: llm-gateway
template:
metadata:
labels:
app: llm-gateway
spec:
nodeSelector:
accelerator: nvidia-gpu
tolerations:
- key: nvidia.com/gpu
operator: Exists
effect: NoSchedule
containers:
- name: gateway
image: llm-gateway:latest
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8000
env:
- name: OXLO_API_KEY
valueFrom:
secretKeyRef:
name: oxlo.ai-secret
key: OXLO_API_KEY
resources:
limits:
memory: "1Gi"
cpu: "1000m"
requests:
memory: "256Mi"
cpu: "100m"
步骤 5:暴露服务
我添加了一个 ClusterIP Service,这样其他 Pod 可以通过 http://llm-gateway.default.svc.cluster.local 访问网关。
apiVersion: v1
kind: Service
metadata:
name: llm-gateway
namespace: default
spec:
selector:
app: llm-gateway
ports:
- port: 80
targetPort: 8000
type: ClusterIP
步骤 6:应用清单
我构建了镜像,推送到内部仓库,然后应用清单文件。
docker build -t llm-gateway:latest .
# tag and push to your registry if necessary
kubectl apply -f secret.yaml
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
kubectl rollout status deployment/llm-gateway
我把服务 port-forward 出来,然后发了一个测试请求。
kubectl port-forward service/llm-gateway 8080:80
然后用一条关于 Kubernetes probes 的问题调用端点。
curl -X POST http://localhost:8080/chat \
-H "Content-Type: application/json" \
-d '{"message": "Write a Python readiness probe for Kubernetes."}'
Oxlo.ai 通过我们的网关返回了如下内容:
{
"response": "from fastapi import FastAPI\nfrom fastapi.responses import PlainTextResponse\n\napp = FastAPI()\n\n@app.get('/healthz', response_class=PlainTextResponse)\ndef healthz():\n return 'ok'\n"
}
你现在有了一个运行在 Kubernetes 上的无状态 LLM 网关,它调用 Oxlo.ai 进行推理。两个具体的下一步:可以添加 HorizontalPodAutoscaler 根据请求率扩缩副本,或者把模型换成 deepseek-v3.2 用于编码任务,而无需改动任何基础设施。