99% 的团队在把大模型搬上 Kubernetes 时,只盯着「能不能跑起来」,却忽略了 GPU 规划和调度细节。DeepSeek 这种从 7B 蒸馏到 671B MoE 的模型家族,如果按「普通 Web 服务」那一套来部署,十有八九会踩坑。你可以把这篇当成一份「踩过坑之后」整理出来的生产级部署笔记,从能跑,到跑得稳、跑得省。

本指南围绕三条部署路径展开:适合演示的 Ollama、适合生产的 vLLM,以及适合大规模分布式的 Ray Serve + vLLM。示例统一用较小的占位模型 deepseek-ai/DeepSeek-R1-Distill-Qwen-7B,方便你在测试环境里先把 YAML 跑通,再替换成真正要用的 DeepSeek 变体。过程中会穿插一些真实案例和风险提醒,说实话,有些地方如果你忽略了,线上翻车的概率会非常高。

快速上手:在 Kubernetes 上部署 DeepSeek 的整体路径

一句话总览部署步骤

部署 DeepSeek 到 Kubernetes,大致路径是:先准备好支持 GPU 的节点,再安装 NVIDIA GPU Operator 或设备插件,让集群暴露 nvidia.com/gpu 资源。接着创建独立命名空间,使用 Secret 存储 Hugging Face Token 和内部 API Key,再用 PersistentVolumeClaim 做模型缓存,最后部署 vLLM、Ollama 或 Ray Serve + vLLM 这类推理容器。对外只暴露 Service 或 Ingress,统一通过 OpenAI 兼容接口测试。

据公开文档统计,vLLM 的 OpenAI 兼容服务已经被多家云厂商示例架构采用,原因很简单:可以直接复用现有 OpenAI 客户端和 SDK,迁移成本极低。

常见的三种选择可以这样粗分:Ollama 适合快速 Demo 和小模型私有端点;vLLM 适合主流生产推理;Ray Serve + vLLM 适合多节点、大模型、高并发。GPU 调度方面,Kubernetes 在安装好设备插件后,会通过 nvidia.com/gpu 资源来调度 GPU,你只需要在 Pod 的 limits 中声明即可。

DeepSeek 模型家族与部署差异

DeepSeek 不是一个单一模型,而是一整套从蒸馏到全量 MoE 的系列。DeepSeek-R1 仓库中列出了 671B 总参数、37B 激活参数、128K 上下文长度的全量模型,同时也提供 1.5B、7B、8B、14B、32B、70B 等基于 Qwen 和 Llama 的蒸馏检查点。DeepSeek-V3 / V3.1 / V3.2 也延续了 671B MoE、37B 激活参数、128K 上下文的设计。

一位团队在内部测试时,先用 7B 蒸馏模型在单卡 A10 上验证链路,结果上线时直接换成全量 DeepSeek-R1,却没改 GPU 拓扑,导致 Pod 一直 Pending,业务以为是「Kubernetes 坏了」。这类问题,本质是对模型体量和 GPU 需求缺乏清晰认知。你可以把小模型当作单 Pod 单 GPU 工作负载,而全量模型则要按多 GPU、甚至多节点来规划。

为什么 GPU 规划是 DeepSeek 部署的核心

DeepSeek 的硬件需求受多种因素影响:模型变体、精度(fp16/bf16/量化)、上下文长度、批大小、KV Cache、张量并行、流水线并行,以及真实流量模式。任何「一块 H100 就够了」的说法都不该被当成通用答案。有用户反馈,照抄某云厂商教程里的 8×B200 架构去跑自己量化后的小模型,结果 GPU 利用率长期低于 20%,成本直接翻车。

据公开案例,Google Cloud 的 DeepSeek-V3.1-Base 教程在 GKE Autopilot 上使用了 8×B200 的 A4 虚机,这只是特定参考架构,而不是所有 DeepSeek 部署的最低配置。更合理的做法,是先用蒸馏模型和较小上下文做压测,量出显存占用和吞吐,再决定是否扩展到多卡或多节点。

架构总览:DeepSeek 在 Kubernetes 上的典型形态

典型服务拓扑

一个常见的 DeepSeek 推理架构,大致可以画成这样:

Client / App
    |
    v
Ingress / API Gateway / Service Mesh
    |
    v
Kubernetes Service
    |
    v
vLLM, Ollama, or Ray Serve Pod(s)
    |
    v
GPU Node Pool
    |
    +--> PersistentVolumeClaim / model cache
    |
    +--> Monitoring: Prometheus, Grafana, DCGM Exporter, vLLM metrics

核心目标有三点:模型权重尽量命中缓存、流量只通过认证后的内部或网关入口、同时监控应用延迟和 GPU 饱和度。NVIDIA GPU Operator 能自动化驱动、设备插件、Container Toolkit、GPU Feature Discovery 和 DCGM 监控组件的安装,省掉大量手工运维工作。

生产环境下的关键关注点

在生产环境里,DeepSeek 推理服务往往会处理内部代码、业务数据甚至日志内容,安全边界必须拉高。你需要关注:

  • 模型缓存:PVC 或本地 SSD,避免频繁从 Hugging Face 拉权重
  • 访问控制:API 网关、身份代理、内部域名
  • 监控:Prometheus + Grafana + DCGM Exporter + vLLM/Ray 指标
  • 日志:请求日志脱敏、审计日志留存
  • 升级策略:滚动发布、灰度流量、回滚预案

有团队在未做任何限流和认证的情况下,把 OpenAI 兼容接口直接暴露到公网,结果被脚本疯狂刷请求,GPU 长时间 100% 占用,内部业务完全打不进来。这种翻车其实完全可以通过网关限流和 API Key 避免。

部署选型:Ollama、vLLM 还是 Ray Serve?

什么时候用 Ollama

当你需要的是「先跑起来给大家看」而不是「扛住生产流量」,Ollama 是个很顺手的选择。它对小型蒸馏 DeepSeek 模型支持不错,提供简单的 HTTP API,也有部分 OpenAI 兼容能力,配合 Docker 和 PVC 就能快速在 Kubernetes 上起一个本地风格的推理端点。

Ollama 的优势是上手快、配置简单,缺点也很明显:在高并发、复杂调度、精细监控方面不如 vLLM 和 Ray Serve。你可以把它当作团队内部 PoC、产品演示、或开发环境的首选,而不是直接扛核心业务的生产流量。

什么时候用 vLLM

vLLM 更适合作为 DeepSeek 的生产推理服务器。它提供 OpenAI 兼容的 HTTP 服务(如 /v1/chat/completions),支持高效批处理、KV Cache 优化,并且官方文档里有专门的 Kubernetes 部署示例。很多团队会直接把现有调用 OpenAI 的客户端指向 vLLM,自建一套私有 LLM 服务。

我自己的观察是:在单集群、单区域、单模型为主的场景里,vLLM 足以覆盖绝大多数需求。你只需要把 GPU 资源、PVC 缓存和 HPA 指标配好,就能获得比较稳定的吞吐和延迟表现。当然,如果你要做跨节点的大模型并行,那就得看下一节的 Ray Serve 了。

什么时候用 Ray Serve + vLLM

当单个 Pod 已经扛不住模型体量或流量压力时,Ray Serve + vLLM 就登场了。Ray Serve LLM 针对大模型推理做了专门封装,支持多节点部署、自动扩缩、负载均衡、OpenAI 兼容接口以及内置监控面板。Ray 官方已经给出 DeepSeek-R1 在 Kubernetes 上的示例,用 KubeRay + Ray Serve + vLLM 部署 deepseek-ai/DeepSeek-R1

Ray Serve 的学习曲线会比单纯的 Deployment 略陡一些,但换来的好处是:你可以更自然地表达「一个模型由多张 GPU 组成」「按请求数自动扩缩 worker 组」这类复杂拓扑。风险点在于:多节点 LLM 故障往往是网络、NCCL、存储这些基础设施问题,而不是模型本身,所以上线前的联调和压测一定要做足。

前置条件与平台要求

Kubernetes 与 GPU 支持

你需要一个可用的 Kubernetes 集群,并且拥有创建 Deployment、Service、Secret、PVC,以及可选 Ingress 或 RayService 的权限。GPU 支持依赖设备插件机制:安装好厂商插件后,集群会暴露 nvidia.com/gpu 等资源,Pod 可以在 resources.limits 中声明 GPU 数量。

Kubernetes 官方文档建议 GPU 只在 limits 中声明;如果同时写了 requests,那么 requestslimits 必须相等。这样调度器才能正确把 Pod 安排到有足够 GPU 的节点上。

硬件与模型规模的关系

DeepSeek 没有「一块 GPU 通吃」的通用答案。你需要根据以下维度来估算:

  • 模型大小:7B / 14B / 70B / 671B MoE
  • 精度:fp16、bf16、量化(如 INT4/INT8)
  • 上下文长度:4K / 8K / 32K / 128K
  • 并发与批大小:QPS、tokens/s 目标
  • 并行策略:张量并行、流水线并行

Ray 官方 DeepSeek R1 示例中,给出的参考架构是两台节点,每台 8×H100 80GB,用来跑全量 DeepSeek 模型。这种配置对很多团队来说偏「豪华」,但它提供了一个上限参考:如果你要跑的是同级别模型,至少要在 GPU 数量和显存级别上接近这个量级。

在 Kubernetes 中准备 GPU 能力

确认 GPU 节点与资源

先确认集群中 GPU 节点是否可见:

kubectl get nodes -o wide
kubectl describe nodes | grep -i -A5 "nvidia.com/gpu"

正常情况下,你会在节点的 Allocatable 字段看到类似:

Allocatable:
  cpu:                32
  memory:             250Gi
  nvidia.com/gpu:     1

要让 DeepSeek 推理 Pod 调度到 GPU 节点,在容器 resources.limits 中声明 GPU:

resources:
  limits:
    nvidia.com/gpu: "1"

如果 GPU 节点打了标签,可以加上 nodeSelector

nodeSelector:
  accelerator: nvidia-gpu

若 GPU 节点被打了污点,需要添加容忍:

tolerations:
  - key: "nvidia.com/gpu"
    operator: "Exists"
    effect: "NoSchedule"

在有多种 GPU(A10、L4、A100、H100、B200 等)的集群里,合理使用节点标签和选择器,可以避免大模型误调度到小显存卡上。

创建命名空间、Secret 与 PVC

基础资源清单

建议为 DeepSeek 单独创建命名空间,并用 Secret 管理 Hugging Face Token 和内部 API Key,再用 PVC 做模型缓存:

apiVersion: v1
kind: Namespace
metadata:
  name: deepseek
---
apiVersion: v1
kind: Secret
metadata:
  name: deepseek-secrets
  namespace: deepseek
type: Opaque
stringData:
  HF_TOKEN: ""
  VLLM_API_KEY: ""
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: deepseek-model-cache
  namespace: deepseek
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: ""
  resources:
    requests:
      storage: 200Gi

应用配置:

kubectl apply -f deepseek-base.yaml

生产环境里,不要把明文 Secret 清单直接提交到 Git。可以用外部密钥管理服务或 Sealed Secret,把敏感信息从仓库里剥离出去。

Secret 安全提示

Kubernetes Secret 本身并不是完整的密钥管理方案。更安全的做法包括:

  • 启用 Secret 静态加密
  • 用最小权限的 RBAC 控制访问
  • 避免把 Secret 挂载给不需要的 Pod
  • 在生产中优先使用外部密钥管理或 Secrets Store CSI

有团队在调试时图省事,把 HF Token 写进镜像环境变量,结果镜像被推到公共仓库,Token 被人扫到后疯狂拉模型,这种事故其实完全可以通过 Secret + RBAC 避免。

主路径:使用 vLLM 在 Kubernetes 上部署 DeepSeek

vLLM 部署清单示例

下面是一个单副本的 DeepSeek 推理服务 Deployment,使用 vLLM 和较小的蒸馏模型占位:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: deepseek-vllm
  namespace: deepseek
  labels:
    app.kubernetes.io/name: deepseek-vllm
    app.kubernetes.io/component: inference
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: deepseek-vllm
  template:
    metadata:
      labels:
        app.kubernetes.io/name: deepseek-vllm
        app.kubernetes.io/component: inference
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "8000"
        prometheus.io/path: "/metrics"
    spec:
      terminationGracePeriodSeconds: 120
      nodeSelector:
        accelerator: nvidia-gpu
      tolerations:
        - key: "nvidia.com/gpu"
          operator: "Exists"
          effect: "NoSchedule"
      securityContext:
        fsGroup: 1000
      containers:
        - name: vllm
          image: "vllm/vllm-openai:"
          imagePullPolicy: IfNotPresent
          command: ["vllm", "serve"]
          args:
            - "$(MODEL_ID)"
            - "--host"
            - "0.0.0.0"
            - "--port"
            - "8000"
            - "--served-model-name"
            - "deepseek-r1-distill"
            - "--dtype"
            - "auto"
            - "--api-key"
            - "$(VLLM_API_KEY)"
            # 压测后再调这些参数:
            # - "--max-model-len"
            # - "8192"
            # - "--gpu-memory-utilization"
            # - "0.90"
          env:
            - name: MODEL_ID
              value: "deepseek-ai/DeepSeek-R1-Distill-Qwen-7B"
            - name: HF_HOME
              value: "/models/huggingface"
            - name: HF_TOKEN
              valueFrom:
                secretKeyRef:
                  name: deepseek-secrets
                  key: HF_TOKEN
            - name: HUGGING_FACE_HUB_TOKEN
              valueFrom:
                secretKeyRef:
                  name: deepseek-secrets
                  key: HF_TOKEN
            - name: VLLM_API_KEY
              valueFrom:
                secretKeyRef:
                  name: deepseek-secrets
                  key: VLLM_API_KEY
          ports:
            - name: http
              containerPort: 8000
          resources:
            requests:
              cpu: "4"
              memory: "32Gi"
            limits:
              cpu: "8"
              memory: "64Gi"
              nvidia.com/gpu: "1"
          volumeMounts:
            - name: model-cache
              mountPath: /models/huggingface
            - name: shm
              mountPath: /dev/shm
          startupProbe:
            httpGet:
              path: /health
              port: http
            failureThreshold: 120
            periodSeconds: 10
          readinessProbe:
            httpGet:
              path: /health
              port: http
            initialDelaySeconds: 10
            periodSeconds: 10
            failureThreshold: 6
          livenessProbe:
            httpGet:
              path: /health
              port: http
            initialDelaySeconds: 60
            periodSeconds: 30
            failureThreshold: 5
          securityContext:
            allowPrivilegeEscalation: false
            capabilities:
              drop: ["ALL"]
      volumes:
        - name: model-cache
          persistentVolumeClaim:
            claimName: deepseek-model-cache
        - name: shm
          emptyDir:
            medium: Memory
            sizeLimit: "8Gi"
            # 更大模型或高并发时可适当增大 shm
---
apiVersion: v1
kind: Service
metadata:
  name: deepseek-vllm
  namespace: deepseek
  labels:
    app.kubernetes.io/name: deepseek-vllm
spec:
  type: ClusterIP
  selector:
    app.kubernetes.io/name: deepseek-vllm
  ports:
    - name: http
      port: 8000
      targetPort: http
        # Increase shm size for larger models, tensor parallelism, or high-concurrency workloads after load testing.

DeepSeek-R1 模型卡建议:不要使用 system prompt,而是把指令写在 user prompt 中,这一点在迁移现有对话逻辑时很容易被忽略。

部署与健康检查

应用 vLLM 清单:

kubectl apply -f deepseek-vllm.yaml

查看状态:

kubectl -n deepseek get pods
kubectl -n deepseek describe pod -l app.kubernetes.io/name=deepseek-vllm
kubectl -n deepseek logs -l app.kubernetes.io/name=deepseek-vllm -f

端口转发到本地:

kubectl -n deepseek port-forward svc/deepseek-vllm 8000:8000

测试 OpenAI 兼容的 Chat Completions 接口:

curl http://localhost:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer " \
  -d '{
    "model": "deepseek-r1-distill",
    "messages": [
      {
        "role": "user",
        "content": "Explain the steps to deploy a GPU workload on Kubernetes. Keep the answer concise."
      }
    ],
    "temperature": 0.6,
    "max_tokens": 512
  }'

也可以用 OpenAI Python 客户端直接调用:

from openai import OpenAI

client = OpenAI(
    base_url="http://localhost:8000/v1",
    api_key="",
)

response = client.chat.completions.create(
    model="deepseek-r1-distill",
    messages=[
        {
            "role": "user",
            "content": "Give me a Kubernetes checklist for serving DeepSeek with GPUs."
        }
    ],
    temperature=0.6,
    max_tokens=512,
)

print(response.choices[0].message.content)

备用路径:使用 Ollama 在 Kubernetes 上部署 DeepSeek

Ollama 部署清单

Ollama 适合做 Demo、本地风格工作流和小型蒸馏模型:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: deepseek-ollama
  namespace: deepseek
  labels:
    app.kubernetes.io/name: deepseek-ollama
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: deepseek-ollama
  template:
    metadata:
      labels:
        app.kubernetes.io/name: deepseek-ollama
    spec:
      nodeSelector:
        accelerator: nvidia-gpu
      tolerations:
        - key: "nvidia.com/gpu"
          operator: "Exists"
          effect: "NoSchedule"
      containers:
        - name: ollama
          image: "ollama/ollama:"
          imagePullPolicy: IfNotPresent
          ports:
            - name: http
              containerPort: 11434
          resources:
            requests:
              cpu: "2"
              memory: "16Gi"
            limits:
              cpu: "6"
              memory: "48Gi"
              nvidia.com/gpu: "1"
          volumeMounts:
            - name: ollama-data
              mountPath: /root/.ollama
      volumes:
        - name: ollama-data
          persistentVolumeClaim:
            claimName: ollama-model-cache
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: ollama-model-cache
  namespace: deepseek
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: ""
  resources:
    requests:
      storage: 100Gi
---
apiVersion: v1
kind: Service
metadata:
  name: deepseek-ollama
  namespace: deepseek
spec:
  type: ClusterIP
  selector:
    app.kubernetes.io/name: deepseek-ollama
  ports:
    - name: http
      port: 11434
      targetPort: http

应用配置:

kubectl apply -f deepseek-ollama.yaml

拉取 DeepSeek 模型到 PVC:

kubectl -n deepseek exec deploy/deepseek-ollama -- ollama pull deepseek-r1:8b

端口转发并测试:

kubectl -n deepseek port-forward svc/deepseek-ollama 11434:11434

curl http://localhost:11434/api/chat \
  -H "Content-Type: application/json" \
  -d '{
    "model": "deepseek-r1:8b",
    "messages": [
      {
        "role": "user",
        "content": "Write a short Kubernetes GPU readiness checklist."
      }
    ],
    "stream": false
  }'

Ollama 的简单是优势,也是限制。要在生产中使用它,需要额外验证:请求排队策略、内存压力、模型加载时间、API 鉴权和自动扩缩行为,否则在高峰期很容易出现响应抖动甚至 OOM。

进阶路径:Ray Serve + vLLM 部署大规模 DeepSeek

Ray Serve DeepSeek 架构概念

当单 Pod vLLM 已经不够用时,可以考虑 Ray Serve DeepSeek 架构。Ray Serve LLM 提供了 ray.serve.llm:build_openai_app 这类封装,支持多节点部署、自动扩缩、负载均衡和 OpenAI 兼容接口。Ray 官方的 Kubernetes DeepSeek 示例使用 KubeRay + Ray Serve + vLLM 部署 deepseek-ai/DeepSeek-R1,对大模型场景很有参考价值。

下面是一个概念性的 RayService 清单,更多是帮助你理解结构,而不是直接复制上线:

apiVersion: ray.io/v1
kind: RayService
metadata:
  name: deepseek-rayserve
  namespace: deepseek
spec:
  serveConfigV2: |
    applications:
      - name: deepseek
        import_path: ray.serve.llm:build_openai_app
        route_prefix: "/"
        args:
          llm_configs:
            - model_loading_config:
                model_id: deepseek-r1
                model_source: deepseek-ai/DeepSeek-R1
              engine_kwargs:
                dtype: bfloat16
                max_model_len: 8192
                tensor_parallel_size: 
                pipeline_parallel_size: 
                gpu_memory_utilization: 0.90
              deployment_config:
                autoscaling_config:
                  min_replicas: 1
                  max_replicas: 2
                  target_ongoing_requests: 64
                max_ongoing_requests: 128
  rayClusterConfig:
    rayVersion: ""
    headGroupSpec:
      serviceType: ClusterIP
      rayStartParams:
        dashboard-host: "0.0.0.0"
      template:
        spec:
          containers:
            - name: ray-head
              image: "rayproject/ray:"
              ports:
                - containerPort: 6379
                  name: gcs
                - containerPort: 8265
                  name: dashboard
                - containerPort: 8000
                  name: serve
              env:
                - name: HUGGING_FACE_HUB_TOKEN
                  valueFrom:
                    secretKeyRef:
                      name: deepseek-secrets
                      key: HF_TOKEN
              resources:
                requests:
                  cpu: "4"
                  memory: "16Gi"
                limits:
                  cpu: "8"
                  memory: "32Gi"
    workerGroupSpecs:
      - groupName: gpu-workers
        replicas: 
        minReplicas: 
        maxReplicas: 
        rayStartParams: {}
        template:
          spec:
            nodeSelector:
              accelerator: nvidia-gpu
            tolerations:
              - key: "nvidia.com/gpu"
                operator: "Exists"
                effect: "NoSchedule"
            containers:
              - name: ray-worker
                image: "rayproject/ray:"
                env:
                  - name: HUGGING_FACE_HUB_TOKEN
                    valueFrom:
                      secretKeyRef:
                        name: deepseek-secrets
                        key: HF_TOKEN
                resources:
                  requests:
                    cpu: "16"
                    memory: "128Gi"
                  limits:
                    cpu: "32"
                    memory: "256Gi"
                    nvidia.com/gpu: ""

这里有个容易被忽略的点:Ray Serve + vLLM 最好使用自定义镜像,把 Ray 版本、vLLM、CUDA/NCCL 以及模型依赖都固定下来,避免线上因为镜像更新导致行为漂移。

大模型部署的额外验证项

在大规模 DeepSeek 部署中,除了常规的 CPU/Mem/GPU 指标,还需要重点验证:

  • NCCL 通信:多卡、多节点间的 AllReduce/AllGather 是否稳定
  • 网络:是否有 RDMA 或高带宽网络,跨机房延迟是否可接受
  • 拓扑感知调度:GPU 拓扑、NUMA 亲和性
  • 镜像大小:大镜像拉取时间是否影响扩缩
  • 共享缓存:模型权重是否能在节点间复用

有用户在多节点部署时,只关注了 vLLM 日志,却忽略了底层网络抖动,结果表现为「偶发性超时」,排查了很久才发现是节点间带宽不足。这类问题,监控和压测比任何「经验值」都更可靠。

安全暴露 DeepSeek 服务

内部 Ingress 示例

不要把未鉴权的 DeepSeek 推理端点直接暴露到公网。更推荐的做法是:

  • 通过 API 网关或 Ingress 暴露内部域名
  • 在网关层做 TLS、鉴权和限流
  • 只允许特定命名空间或服务访问推理端点

一个最小化的内部 Ingress 示例:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: deepseek-vllm
  namespace: deepseek
  annotations:
    nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - deepseek.internal.example.com
      secretName: deepseek-tls
  rules:
    - host: deepseek.internal.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: deepseek-vllm
                port:
                  number: 8000

在生产环境里,API Key 或身份代理应该在网关层强制执行,而不是只依赖应用内部的 --api-key 参数。

DeepSeek 的扩缩策略

为什么不能只看 CPU HPA

LLM 扩缩和传统无状态 REST 服务完全不同。CPU 使用率往往不是瓶颈,真正的限制来自:GPU 显存、KV Cache 压力、请求队列长度、tokens/s、首 token 延迟等。Kubernetes 的 autoscaling/v2 HPA 支持内存和自定义指标,这对 LLM 推理非常关键。

下面是一个基于自定义指标的 HPA 示例,假设你通过 Prometheus Adapter 暴露了 vllm_num_requests_waiting

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: deepseek-vllm
  namespace: deepseek
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: deepseek-vllm
  minReplicas: 1
  maxReplicas: 4
  metrics:
    - type: Pods
      pods:
        metric:
          name: vllm_num_requests_waiting
        target:
          type: AverageValue
          averageValue: "8"

对小模型来说,「一 Pod 一 GPU,多副本扩缩」通常效果不错。对使用张量并行或流水线并行的大模型,扩缩往往意味着增加一整组 Pod,而不是简单加一个副本,这一点在设计 HPA 时要特别注意。

监控与可观测性

关键监控维度

要同时监控模型服务、GPU、Kubernetes 工作负载和用户体验:

  • GPU:利用率、显存占用、温度、功耗(DCGM Exporter)
  • 模型服务:请求队列长度、延迟、吞吐、错误率(vLLM / Ray 指标)
  • 集群:Pod 状态、重启次数、调度失败、节点健康
  • 业务:首 token 延迟、整体响应时间、失败请求比例

NVIDIA DCGM Exporter 可以以 DaemonSet 形式部署在 GPU 节点上,通过 /metrics 暴露 GPU 指标,供 Prometheus 抓取。vLLM 自身也提供了丰富的 Prometheus 指标,用来做自动扩缩和容量规划。

安全最佳实践

安全检查清单

生产环境的 DeepSeek 推理端点,默认应该被视为「敏感服务」。可以按下面的清单逐项检查:

  • 命名空间隔离:推理服务与业务服务分离
  • RBAC:只给 CI/CD、运维和必要服务最小权限
  • Secret:启用静态加密,避免明文存储
  • 网络:用 NetworkPolicy 限制访问来源
  • 镜像:使用只读根文件系统、丢弃多余 Capabilities

Kubernetes RBAC 文档明确提醒,不要使用过度宽泛的通配权限。NetworkPolicy 则可以精细控制哪些命名空间或 Pod 能访问 DeepSeek 服务。

NetworkPolicy 示例

下面是一个只允许 api-gateway 命名空间访问 DeepSeek vLLM 的 NetworkPolicy:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-gateway-to-deepseek
  namespace: deepseek
spec:
  podSelector:
    matchLabels:
      app.kubernetes.io/name: deepseek-vllm
  policyTypes:
    - Ingress
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: api-gateway
      ports:
        - protocol: TCP
          port: 8000

如果你的集群使用自定义命名空间标签,可以把 namespaceSelector 换成自己的标签策略。

性能优化思路

从正确性到性能

先保证「能稳定给出正确结果」,再去挤性能。高影响的调优方向包括:

  • 精度与量化:bf16 / fp16 / INT4 的权衡
  • 上下文长度:控制在业务真实需要的范围内
  • 批处理:合理的 batch size 提升吞吐
  • KV Cache:缓存大小与回收策略
  • GPU 利用率:通过 vLLM 参数调节并发与显存占用

不要一上来就把上下文拉到 128K、并发开到最大。长上下文会显著增加 KV Cache 压力,很多团队在压测时发现,适度缩短上下文、控制并发,反而能在整体吞吐和成本之间找到更好的平衡。

故障排查与清理

基本清理命令

当你需要回收资源或重建环境时,可以按下面的顺序清理:

删除 vLLM 部署:

kubectl -n deepseek delete deployment deepseek-vllm
kubectl -n deepseek delete service deepseek-vllm

删除 Ollama 部署:

kubectl -n deepseek delete deployment deepseek-ollama
kubectl -n deepseek delete service deepseek-ollama
kubectl -n deepseek delete pvc ollama-model-cache

删除共享资源:

kubectl -n deepseek delete pvc deepseek-model-cache
kubectl delete namespace deepseek

如果使用了 Ray Serve,可以删除 RayService:

kubectl -n deepseek delete rayservice deepseek-rayserve

关键收获与行动建议

如果你读到这里,基本已经具备在 Kubernetes 上部署 DeepSeek 的完整路径:从 GPU 节点准备,到 vLLM/Ollama/Ray Serve 选型,再到安全暴露、扩缩和监控。真正拉开差距的,不是「能不能跑起来」,而是你是否用合适的模型规模、合理的 GPU 拓扑和可靠的监控信号来支撑业务。

这个判断框架在不同模型和云环境下都能复用,值得你收藏下来,作为以后每次上新模型时的检查清单。如果你正准备从托管 API 迁移到自建 DeepSeek,这篇内容往往比问一圈身边人更系统,也更接近真实生产场景。

常见问题

Q:如何判断该用 Ollama 还是 vLLM 来部署 DeepSeek?

A:如果你的目标是快速演示、小规模内部试用或开发环境验证,优先用 Ollama;如果要承载稳定的生产流量、需要 OpenAI 兼容接口、批处理和完善监控,则更适合用 vLLM。原因在于 Ollama 的设计偏向「本地体验」,配置简单但在高并发和精细调优上空间有限,而 vLLM 从一开始就面向大模型推理场景,提供了更多可调参数和指标。建议做法是:先用 Ollama 快速验证 Prompt 和业务逻辑,一旦确认要上线,就迁移到 vLLM,并配合 GPU 规划和 HPA 做压测。

Q:DeepSeek 在 Kubernetes 上一定要用 GPU 吗?

A:从实用角度看,是的,生产环境几乎必然需要 GPU。小模型在 CPU 上可以跑通功能测试,但在真实业务 QPS 和上下文长度下,CPU 推理的延迟和成本都很难接受。DeepSeek 系列模型参数量大、上下文长,对算力和显存要求高。更合理的做法是:在开发环境用小模型 + CPU 验证链路,在预生产和生产环境使用 GPU 节点,通过 nvidia.com/gpu 资源调度,并结合 DCGM 监控显存和利用率,避免盲目堆机器。

Q:能否在单块 GPU 上运行 DeepSeek-R1?

A:部分小型蒸馏版 DeepSeek-R1(如 7B 级别)在合适的精度和上下文长度下,可以在单块中高端 GPU 上运行,但全量 DeepSeek-R1 级别模型并不适合单小卡部署。原因是全量模型的参数量和激活参数规模远超单卡显存承载能力,需要多 GPU 甚至多节点并行。建议先用蒸馏模型在单卡上做功能和压测,记录显存占用和吞吐,再根据业务需求决定是否升级到多卡或多节点架构,避免一上来就尝试在单卡上硬塞全量模型。

Q:如何把 DeepSeek 暴露为 OpenAI 兼容 API?

A:可以通过 vLLM 或 Ray Serve LLM 来实现。vLLM 自带 OpenAI 兼容的 HTTP 服务,支持 /v1/chat/completions 等常用端点;Ray Serve LLM 则在此基础上提供多节点部署和自动扩缩能力。关键在于:在 Kubernetes 中为推理服务创建 Service,再通过 Ingress 或 API 网关对外暴露,并在网关层强制 API Key 或身份认证。操作建议是:先在集群内部通过 kubectl port-forward 验证接口行为,再配置内部域名和 TLS,最后才考虑是否对外开放或接入零信任网关。

Q:在 Kubernetes 上扩缩 DeepSeek 服务时,应该看哪些指标?

A:比起传统的 CPU 使用率,更应该关注请求队列长度、tokens/s、首 token 延迟、整体响应时间以及 GPU 显存利用率。原因是 LLM 推理的瓶颈往往在 GPU 和 KV Cache,而不是 CPU。可操作做法是:为 vLLM 暴露的队列长度等指标配置 Prometheus 抓取,通过 Prometheus Adapter 映射为自定义 HPA 指标,例如 vllm_num_requests_waiting,再结合 DCGM 的显存利用率判断是否需要扩容。对大模型并行部署,还要监控 NCCL 错误和节点间网络延迟,避免只看应用层指标。

Q:自建 DeepSeek 和使用托管 API 相比,哪个更好?

A:没有绝对更好,只有更适合的场景。自建 DeepSeek 的优势在于数据本地化、网络边界可控、模型版本和参数可高度自定义,在大规模长期使用时也有潜在成本优势;托管 API 的优势是运维简单、弹性好、上线快。判断标准可以看三点:合规要求是否必须自建、业务是否需要深度定制模型行为、以及长期调用量是否足以摊薄自建的运维成本。如果这三点中至少有两点偏向自建,自建 DeepSeek 就很值得认真评估。