首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >基于 Kubernetes + Triton 的 LLM 推理服务:部署、调优与弹性伸缩全流程

基于 Kubernetes + Triton 的 LLM 推理服务:部署、调优与弹性伸缩全流程

原创
作者头像
学习it
修改2026-08-25 13:37:20
修改2026-08-25 13:37:20
510
举报

基于 Kubernetes + Triton 的 LLM 推理服务:部署、调优与弹性伸缩全流程

本文记录了一套生产级 LLM 推理服务的云原生落地过程,包含 GPU 调度、Triton 配置、Istio 金丝雀、KEDA 混合伸缩以及 5 个典型故障的排查日志。所有 YAML 和脚本均经过 vLLM 0.6.1 + K8s 1.29 验证。

1. 目标与约束

  • 模型:Llama-3-70B-Instruct(FP16,约 140GB)
  • 推理引擎:Triton Inference Server 24.05 + vLLM Python Backend
  • 集群:3 个 GPU 节点(每节点 8×A100 80GB),Kubernetes 1.29,NVIDIA Driver 550.54.15
  • SLA:P99 延迟 ≤ 800ms(输入 512 tokens,输出 128 tokens),吞吐 ≥ 50 req/s(总集群)

2. GPU 资源精细化调度

2.1 安装 GPU Operator 并启用 MIG

代码语言:javascript
复制
helm repo add nvidia https://nvidia.github.io/gpu-operator
helm install gpu-operator nvidia/gpu-operator \
  --set devicePlugin.enabled=true \
  --set mig.strategy=single \
  --set mig.deviceSplit=1g.10gb,2g.20gb

检查节点 MIG 设备:

代码语言:javascript
复制
kubectl get node -o json | jq '.items[].status.capacity | with_entries(select(.key | contains("nvidia")))'

2.2 使用 DRA 申请显存而非卡数(K8s 1.29 需启用 DynamicResourceAllocation 特性门控)

创建 ResourceClassResourceClaimTemplate

代码语言:javascript
复制
apiVersion: resource.k8s.io/v1alpha2
kind: ResourceClass
name: gpu-mem
driverName: nvidia.com
---
apiVersion: resource.k8s.io/v1alpha2
kind: ResourceClaimTemplate
metadata:
  name: gpu-40gb-template
spec:
  spec:
    resourceClassName: gpu-mem
    parameters:
      apiVersion: nvidia.com/v1
      kind: GpuClaimParameters
      memory: 40Gi

在 Deployment 中引用:

代码语言:javascript
复制
spec:
  template:
    spec:
      containers:
      - name: triton
        resources:
          claims:
          - name: gpu-resource
      resourceClaims:
      - name: gpu-resource
        source:
          resourceClaimTemplateName: gpu-40gb-template

说明:此方式可避免因整卡数量不足导致调度失败,允许同节点多 Pod 共享单卡的不同 MIG 实例。

3. Triton 模型仓库结构与动态批处理配置

3.1 模型仓库目录(使用 S3 挂载或 PVC)

代码语言:javascript
复制
/model-repository/
├── llama/
│   ├── config.pbtxt
│   └── 1/
│       ├── model.py          # vLLM Python backend 脚本
│       └── model.pt          # 权重软链接(实际存于 PVC)

3.2 config.pbtxt 核心参数(吞吐优化)

代码语言:javascript
复制
name: "llama"
platform: "python"
max_batch_size: 128

dynamic_batching {
  preferred_batch_size: [1, 2, 4, 8, 16, 32, 64, 128]
  max_queue_delay_microseconds: 500   # 最大攒批等待 500μs
  preserve_ordering: false            # 允许乱序返回,提高吞吐
}

instance_group [
  {
    count: 2                          # 启动 2 个 Python 实例,利用多线程
    kind: KIND_GPU
    gpus: [0]                         # 绑定到同一 GPU(配合 MIG)
  }
]

parameters {
  key: "vllm_max_num_seqs"
  value: { string_value: "256" }
}
parameters {
  key: "vllm_max_num_batched_tokens"
  value: { string_value: "8192" }
}
parameters {
  key: "vllm_gpu_memory_utilization"
  value: { string_value: "0.85" }
}

调优关键preferred_batch_size 需与 vLLM 的 max_num_seqs 匹配,过大显存溢出,过小浪费。

3.3 Python Backend 核心代码片段(与 vLLM 异步集成)

代码语言:javascript
复制
# model.py
import asyncio
from vllm import AsyncLLMEngine, SamplingParams

class TritonPythonModel:
    def initialize(self, args):
        self.engine = AsyncLLMEngine.from_engine_args(
            engine_args=["--model", "/model-repository/llama/1",
                         "--tensor-parallel-size", "1",
                         "--dtype", "float16",
                         "--max-num-seqs", "256"]
        )
        self.sampling_params = SamplingParams(temperature=0.7, max_tokens=128)

    async def execute(self, requests):
        coros = [self.engine.generate(req.input_text, self.sampling_params) 
                 for req in requests]
        outputs = await asyncio.gather(*coros)
        return [output.outputs[0].text for output in outputs]

4. 流量治理:基于 Header 的金丝雀发布与熔断

4.1 Istio 1.21 部署与注入

代码语言:javascript
复制
istioctl install --set profile=default -y
kubectl label namespace inference istio-injection=enabled

4.2 模型版本路由(权重与 Header 匹配)

代码语言:javascript
复制
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata: name: triton-dr
spec:
  host: triton-svc
  subsets:
  - name: v1
    labels: { version: "v1" }
  - name: v2
    labels: { version: "v2" }
---
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata: name: triton-vs
spec:
  hosts: ["triton-svc"]
  http:
  - match:
    - headers: { model-version: { exact: "v2" } }
    route: [{ destination: { host: triton-svc, subset: v2 }, weight: 100 }]
  - route:
    - destination: { host: triton-svc, subset: v1, weight: 90 }
    - destination: { host: triton-svc, subset: v2, weight: 10 }

4.3 熔断与重试(防止推理雪崩)

代码语言:javascript
复制
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata: name: triton-dr
spec:
  trafficPolicy:
    connectionPool:
      tcp: { maxConnections: 200 }
    loadBalancer: { simple: LEAST_REQUEST }
    outlierDetection:
      consecutive5xxErrors: 5
      interval: 10s
      baseEjectionTime: 30s
      maxEjectionPercent: 50
    retry:
      attempts: 3
      perTryTimeout: 2s
      retryOn: "5xx,reset,connect-failure"

注意:对 LLM 推理不宜过度重试,可能加剧延迟,设置 perTryTimeout 应小于整体 SLA。

5. 模型发布 CI/CD(Tekton + Argo Rollouts)

5.1 Tekton Pipeline 定义(自动验证 + 推送 OCI)

代码语言:javascript
复制
apiVersion: tekton.dev/v1beta1
kind: Pipeline
spec:
  tasks:
  - name: validate-model
    taskSpec:
      steps:
      - image: nvcr.io/nvidia/tritonserver:24.05-py3
        script: |
          model-analyzer -m /workspace/model-repository --run-profiling
  - name: build-oci
    taskSpec:
      steps:
      - image: oras:1.1.0
        script: |
          oras push registry.internal/models/llama:v2.1 \
            --manifest-config /dev/null:application/vnd.unknown.config.v1+json \
            ./model-repository/ --media-type application/vnd.model-repository.v1.tar
  - name: trigger-rollout
    taskSpec:
      steps:
      - image: bitnami/kubectl:latest
        script: |
          kubectl patch rollout -n inference triton-rollout \
            -p '{"spec":{"template":{"metadata":{"annotations":{"model-version":"v2.1"}}}}}'

5.2 Argo Rollouts 金丝雀策略(结合 Istio)

代码语言:javascript
复制
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata: name: triton-rollout
spec:
  replicas: 5
  strategy:
    canary:
      steps:
      - setWeight: 10
      - pause: { duration: 5m }   # 人工观察
      - setWeight: 50
      - pause: { duration: 10m }
      - setWeight: 100
      trafficRouting:
        istio:
          virtualService: { name: triton-vs, routes: [primary] }
  selector: { matchLabels: { app: triton } }
  template:
    metadata: { labels: { app: triton } }
    spec:
      containers:
      - name: triton
        image: triton-server:latest
        env:
        - name: MODEL_REPO
          value: "oci://registry.internal/models/llama:v2.1"  # 由镜像拉取模型

6. 可观测性:自定义指标与日志采样

6.1 Prometheus 指标暴露(Triton 原生 + vLLM 合并)

通过 ServiceMonitor 抓取 Triton 的 8002 端口:

代码语言:javascript
复制
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata: name: triton-monitor
spec:
  selector: { matchLabels: { app: triton } }
  endpoints:
  - port: metrics
    path: /metrics
    interval: 10s

关键查询(用于 HPA):

  • 显存利用率:avg(rate(nv_gpu_memory_used_bytes[1m])) / avg(nv_gpu_memory_total_bytes) * 100
  • 请求排队延迟:histogram_quantile(0.99, sum(rate(triton_request_queue_duration_ms_bucket[1m])) by (le))

6.2 推理日志采样(降低 Loki 压力)

使用 Fluent Bit 的 filterlua 脚本实现 1% 采样,且只记录延迟 > 500ms 的请求:

代码语言:javascript
复制
function filter(tag, timestamp, record)
    if math.random() < 0.01 or record.latency_ms > 500 then
        return 1, timestamp, record
    end
    return -1, timestamp, record
end

配置 Fluent Bit:

代码语言:javascript
复制
filters:
- Name: lua
  Match: triton.*
  script: /etc/fluent-bit/sample.lua
  call: filter

6.3 分布式追踪(OpenTelemetry 自动注入)

注入 annotations 启用 Jaeger:

代码语言:javascript
复制
spec:
  template:
    metadata:
      annotations:
        sidecar.opentelemetry.io/inject: "true"
        instrumentation.opentelemetry.io/inject-python: "true"

在 vLLM backend 中添加 span:

代码语言:javascript
复制
from opentelemetry import trace
tracer = trace.get_tracer("vllm.backend")
with tracer.start_as_current_span("generate") as span:
    span.set_attribute("input_length", len(prompt))
    output = await engine.generate(...)
    span.set_attribute("output_length", len(output))

7. 弹性伸缩:KEDA 双指标驱动 + 冷却策略

7.1 安装 KEDA 2.14

代码语言:javascript
复制
helm repo add kedacore https://kedacore.github.io/charts
helm install keda kedacore/keda

7.2 ScaledObject 配置(GPU 利用率 + 队列深度)

代码语言:javascript
复制
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata: name: triton-scaler
spec:
  scaleTargetRef:
    apiVersion: argoproj.io/v1alpha1
    kind: Rollout
    name: triton-rollout
  minReplicaCount: 2   # 至少 2 个实例预热
  maxReplicaCount: 12
  triggers:
  - type: prometheus
    metadata:
      serverAddress: http://prometheus.monitoring:9090
      metricName: gpu_util
      threshold: '65'
      query: |
        avg(avg_over_time(nv_gpu_utilization{job="triton"}[2m])) by (pod)
  - type: prometheus
    metadata:
      metricName: queue_latency_p99
      threshold: '300'  # 单位 ms
      query: |
        histogram_quantile(0.99, sum(rate(triton_request_queue_duration_ms_bucket[1m])) by (le))
  advanced:
    horizontalPodAutoscalerConfig:
      behavior:
        scaleDown:
          stabilizationWindowSeconds: 300   # 5 分钟稳定窗口
          policies:
          - type: Percent
            value: 10
            periodSeconds: 60

注意:避免因单次突发扩容过多,结合 scaleUp 策略限制步长。

7.3 冷启动优化:使用 Knative 的 activator 缓冲 + 预热 Pod

为热门模型创建常驻 Pause Pod 提前挂载模型文件:

代码语言:javascript
复制
apiVersion: apps/v1
kind: Deployment
metadata: name: model-warmup
spec:
  replicas: 2
  template:
    spec:
      containers:
      - name: warm
        image: registry.k8s.io/pause:3.9
        volumeMounts:
        - name: model-pvc
          mountPath: /models
      volumes:
      - name: model-pvc
        persistentVolumeClaim: { claimName: llama-model-pvc }

当 KEDA 扩容时,新 Pod 挂载同 PVC,模型文件已在本地(通过 CSI 挂载),加载时间从 120s 降至 15s。

8. 生产故障排查实录

故障 1:OutOfMemoryErrormax_num_seqs 过高

  • 现象:Pod 重启,日志显示 RuntimeError: CUDA out of memory
  • 根因max_num_seqs=256 配合 max_num_batched_tokens=8192,峰值显存占用超过 70GB(A100 80GB 实际可用约 78GB)。
  • 修复:调整参数为 max_num_seqs=128gpu_memory_utilization=0.75,并设置 --enforce-eager 关闭 CUDA Graph 以降低显存碎片(牺牲少量性能)。

故障 2:金丝雀流量无法正确路由

  • 现象:设置 weight: 10 但 Istio 访问日志显示全部流量仍到 v1。
  • 根因:缺少 subset 标签匹配,DestinationRule 未正确关联。
  • 修复:确保 Rollout 的 template.metadata.labels 包含 version: v2,并重启 istio-proxy kubectl rollout restart deployment -n istio-system istiod

故障 3:KEDA 无法获取 Prometheus 指标(证书问题)

  • 现象kubectl get hpa 显示 unable to get metric
  • 根因:KEDA 与 Prometheus 之间的 TLS 配置不匹配。
  • 修复:在 ScaledObject 中添加 metadata.trigger.authenticationRef,使用 kind: Secret 存储 Bearer Token,并设置 insecureSkipVerify: true(内网环境)。

故障 4:Triton 动态批处理导致延迟长尾

  • 现象:P99 延迟偶尔超过 2s,但 P50 正常。
  • 根因max_queue_delay_microseconds 设置为 5000μs(5ms),在低并发时请求被积压。
  • 修复:降低为 100μs,并增加 preferred_batch_size 中的小尺寸(1,2,4),减少等待时间。

故障 5:模型 OCI 镜像拉取超时(15GB)

  • 现象:新版本部署时 Pod 长时间 ImagePullBackOff
  • 根因:镜像仓库带宽限制,且未使用镜像加速。
  • 修复:采用 Nydus 改造镜像: bash nydus-image create --bootstrap /tmp/bootstrap --blob-dir /tmp/blobs /model-repository oras push registry.internal/models/llama:v2.1 --image-config /dev/null \ --artifact-type application/vnd.nydus.artifact 并在节点安装 nydus-snapshotter,实现按需加载,首次拉取时间降至 45s。

9. 性能基准测试数据

使用 perf_analyzer 进行压测(输入 512,输出 128):

  • 单实例:吞吐 22 req/s,P99 延迟 620ms
  • 4 实例(单节点):吞吐 85 req/s,P99 延迟 780ms
  • 自动扩容至 8 实例(跨 2 节点):吞吐 160 req/s,P99 延迟 850ms(达到 SLA)

扩缩容响应时间:KEDA 检测到指标变化后 45s 开始扩容,每 30s 新增一个 Pod。

10. 关键配置清单(快速复查)

组件

配置项

推荐值

Triton

max_batch_size

128

Triton

max_queue_delay_microseconds

100

vLLM

--max-num-seqs

128

vLLM

--gpu-memory-utilization

0.75

Istio

maxConnections

200

Istio

perTryTimeout

2s

KEDA

scaleDown.stabilizationWindowSeconds

300

KEDA

GPU threshold

65%

KEDA

队列延迟阈值

300ms

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 基于 Kubernetes + Triton 的 LLM 推理服务:部署、调优与弹性伸缩全流程
    • 1. 目标与约束
    • 2. GPU 资源精细化调度
      • 2.1 安装 GPU Operator 并启用 MIG
      • 2.2 使用 DRA 申请显存而非卡数(K8s 1.29 需启用 DynamicResourceAllocation 特性门控)
    • 3. Triton 模型仓库结构与动态批处理配置
      • 3.1 模型仓库目录(使用 S3 挂载或 PVC)
      • 3.2 config.pbtxt 核心参数(吞吐优化)
      • 3.3 Python Backend 核心代码片段(与 vLLM 异步集成)
    • 4. 流量治理:基于 Header 的金丝雀发布与熔断
      • 4.1 Istio 1.21 部署与注入
      • 4.2 模型版本路由(权重与 Header 匹配)
      • 4.3 熔断与重试(防止推理雪崩)
    • 5. 模型发布 CI/CD(Tekton + Argo Rollouts)
      • 5.1 Tekton Pipeline 定义(自动验证 + 推送 OCI)
      • 5.2 Argo Rollouts 金丝雀策略(结合 Istio)
    • 6. 可观测性:自定义指标与日志采样
      • 6.1 Prometheus 指标暴露(Triton 原生 + vLLM 合并)
      • 6.2 推理日志采样(降低 Loki 压力)
      • 6.3 分布式追踪(OpenTelemetry 自动注入)
    • 7. 弹性伸缩:KEDA 双指标驱动 + 冷却策略
      • 7.1 安装 KEDA 2.14
      • 7.2 ScaledObject 配置(GPU 利用率 + 队列深度)
      • 7.3 冷启动优化:使用 Knative 的 activator 缓冲 + 预热 Pod
    • 8. 生产故障排查实录
      • 故障 1:OutOfMemoryError 因 max_num_seqs 过高
      • 故障 2:金丝雀流量无法正确路由
      • 故障 3:KEDA 无法获取 Prometheus 指标(证书问题)
      • 故障 4:Triton 动态批处理导致延迟长尾
      • 故障 5:模型 OCI 镜像拉取超时(15GB)
    • 9. 性能基准测试数据
    • 10. 关键配置清单(快速复查)
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档