通义千问Embedding模型弹性伸缩:K8s自动扩缩容实战

如果你正在用通义千问的Embedding模型做知识库,肯定遇到过这样的问题:白天用户多的时候,模型响应慢,GPU资源吃紧;到了半夜,GPU又闲着没事干,白白浪费钱。

这就像开了一家餐馆,中午饭点厨师忙不过来,顾客等得着急;晚上厨师又闲着打瞌睡,工资还得照付。有没有一种办法,能让厨师数量自动调整,人多就多请几个,人少就少请几个?

今天要聊的,就是怎么用Kubernetes(K8s)给通义千问Embedding模型装上“自动伸缩”的能力。我们以Qwen3-Embedding-4B这个模型为例,配合vLLM和Open WebUI,打造一个能根据流量自动扩缩容的知识库系统。

1. 为什么需要弹性伸缩?

先说说为什么这事重要。Qwen3-Embedding-4B是个很实用的模型,4B参数、3GB显存就能跑,支持32k长文本和2560维向量,在多语言和代码理解上表现都不错。但再好的模型,部署时也得考虑资源利用。

传统部署的痛点:

  • 资源浪费:按峰值流量配置GPU,大部分时间资源闲置
  • 响应延迟:流量突增时,单实例处理不过来,用户等待时间变长
  • 运维复杂:手动调整实例数量,半夜还得盯着监控
  • 成本不可控:固定资源意味着固定成本,无法按需付费

弹性伸缩的好处:

  • 省钱:用多少资源付多少钱,闲时自动缩容
  • 省心:系统自动调整,不用人工干预
  • 稳定:流量高峰时自动扩容,保证服务可用性
  • 灵活:根据CPU、内存、请求数等多种指标触发伸缩

简单说,就是让系统像有生命一样,能自己“呼吸”——忙的时候多吸几口气(扩容),闲的时候少喘几口(缩容)。

2. 技术栈选型与架构设计

要搞自动伸缩,得先选对工具。我们的方案基于以下几个核心组件:

2.1 核心组件介绍

Qwen3-Embedding-4B:阿里开源的4B参数双塔Embedding模型。它的特点是“中等体量、大能力”——32k上下文长度、2560维向量、支持119种语言。用GGUF量化后只要3GB显存,RTX 3060就能跑到800文档/秒。

vLLM:专门为LLM推理优化的服务框架。它最大的优势是PagedAttention技术,能高效管理GPU内存,让多个请求共享KV缓存。对Embedding模型来说,vLLm能显著提升并发处理能力。

Open WebUI:开源的Web界面,提供知识库管理、对话界面、模型配置等功能。它支持多种后端,能很方便地对接vLLM服务。

Kubernetes:容器编排平台,负责管理Pod(容器组)的生命周期。K8s的HPA(Horizontal Pod Autoscaler)功能,能根据监控指标自动调整Pod数量。

2.2 整体架构

整个系统的架构是这样的:

用户请求 → Open WebUI前端 → vLLM服务(多个Pod) → Qwen3-Embedding-4B模型
                     ↑
                K8s HPA控制器
                     ↑
                Prometheus监控
  • vLLM服务:运行在K8s的Pod里,每个Pod部署一个vLLM实例,加载Qwen3-Embedding-4B模型
  • Open WebUI:作为前端界面,用户通过它上传文档、提问、查看结果
  • Prometheus:监控系统,收集vLLM Pod的CPU、内存、请求数等指标
  • HPA:根据Prometheus的监控数据,自动调整vLLM Pod的数量

当请求增多时,HPA检测到指标超过阈值,就创建新的vLLM Pod;请求减少时,自动销毁多余的Pod。

3. 环境准备与部署

3.1 基础环境要求

要跑这个方案,你需要:

  • Kubernetes集群:可以是云厂商的托管服务(如阿里云ACK、腾讯云TKE),也可以是自建的(如kubeadm部署)
  • GPU节点:至少有一个带GPU的节点,显存≥8GB(建议RTX 3060以上)
  • 存储:需要持久化存储保存模型文件,避免每次重启都重新下载
  • 网络:集群内Pod能互相通信,能访问外部镜像仓库

如果还没有K8s环境,可以用Minikube或Kind在本地搭个测试环境,不过GPU支持可能有限。

3.2 模型准备与优化

Qwen3-Embedding-4B有几种格式可选,我们选最适合生产部署的:

# 模型配置建议
模型格式: GGUF-Q4量化版
显存占用: 约3GB
推理速度: RTX 3060上约800文档/秒
部署方式: 下载到持久化存储,Pod启动时挂载

GGUF格式的好处是量化后体积小、加载快,对显存要求低。你可以从Hugging Face或ModelScope下载预量化好的版本:

# 下载模型(示例)
wget https://huggingface.co/Qwen/Qwen3-Embedding-4B-GGUF/resolve/main/qwen3-embedding-4b-q4_0.gguf

把模型文件放到NFS、Ceph或其他共享存储上,这样所有Pod都能访问同一份模型文件。

3.3 vLLM服务部署

vLLM提供了官方的Docker镜像,我们可以基于它创建K8s Deployment:

# vllm-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: qwen-embedding-vllm
  namespace: ai-services
spec:
  replicas: 1  # 初始副本数,HPA会自动调整
  selector:
    matchLabels:
      app: qwen-embedding
  template:
    metadata:
      labels:
        app: qwen-embedding
    spec:
      containers:
      - name: vllm-server
        image: vllm/vllm-openai:latest
        command: ["python3", "-m", "vllm.entrypoints.openai.api_server"]
        args:
        - "--model"
        - "/models/qwen3-embedding-4b-q4_0.gguf"
        - "--host"
        - "0.0.0.0"
        - "--port"
        - "8000"
        - "--served-model-name"
        - "qwen3-embedding-4b"
        - "--max-model-len"
        - "32768"  # 32k上下文
        - "--gpu-memory-utilization"
        - "0.9"
        resources:
          limits:
            nvidia.com/gpu: 1  # 申请1个GPU
            memory: "8Gi"
            cpu: "2"
          requests:
            nvidia.com/gpu: 1
            memory: "6Gi"
            cpu: "1"
        volumeMounts:
        - name: model-storage
          mountPath: /models
          readOnly: true
        ports:
        - containerPort: 8000
      volumes:
      - name: model-storage
        persistentVolumeClaim:
          claimName: model-pvc

关键配置说明:

  • GPU资源:明确申请GPU资源,K8s会把Pod调度到有GPU的节点
  • 内存限制:设置合理的内存限制,防止容器占用过多内存
  • 模型路径:通过volume挂载共享存储中的模型文件
  • 端口暴露:vLLM服务监听8000端口,提供OpenAI兼容的API

创建Service暴露这个Deployment:

# vllm-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: qwen-embedding-service
  namespace: ai-services
spec:
  selector:
    app: qwen-embedding
  ports:
  - port: 8000
    targetPort: 8000
  type: ClusterIP  # 内部访问,通过Ingress或NodePort对外

3.4 Open WebUI部署

Open WebUI也提供了Docker镜像,部署方式类似:

# webui-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: open-webui
  namespace: ai-services
spec:
  replicas: 1
  selector:
    matchLabels:
      app: open-webui
  template:
    metadata:
      labels:
        app: open-webui
    spec:
      containers:
      - name: webui
        image: ghcr.io/open-webui/open-webui:main
        env:
        - name: OLLAMA_BASE_URL
          value: "http://qwen-embedding-service:8000"  # 指向vLLM服务
        - name: WEBUI_SECRET_KEY
          valueFrom:
            secretKeyRef:
              name: webui-secrets
              key: secret-key
        ports:
        - containerPort: 8080
        resources:
          requests:
            memory: "512Mi"
            cpu: "250m"
          limits:
            memory: "1Gi"
            cpu: "500m"

Open WebUI配置里最关键的是OLLAMA_BASE_URL环境变量,要指向vLLM服务的地址。这样用户通过WebUI界面操作时,请求就会转发到vLLM服务。

4. 配置自动扩缩容(HPA)

前面都是基础部署,现在进入核心部分——配置HPA让系统能自动伸缩。

4.1 安装Metrics Server

HPA需要监控数据,K8s默认的监控组件是Metrics Server。先确认集群里有没有:

# 检查Metrics Server
kubectl get pods -n kube-system | grep metrics-server

# 如果没有,安装它
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml

Metrics Server会收集每个Pod的CPU和内存使用率,这是HPA做决策的基础。

4.2 基于CPU/内存的HPA

最简单的HPA是基于CPU或内存使用率来伸缩。比如,当vLLM Pod的CPU使用率超过70%时,就增加副本数:

# hpa-cpu.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: qwen-embedding-hpa
  namespace: ai-services
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: qwen-embedding-vllm
  minReplicas: 1  # 最少1个副本
  maxReplicas: 10  # 最多10个副本
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70  # CPU使用率目标70%
  - type: Resource
    resource:
      name: memory
      target:
        type: Utilization
        averageUtilization: 80  # 内存使用率目标80%

这个配置的意思是:

  • 监控qwen-embedding-vllm这个Deployment
  • 副本数在1到10之间自动调整
  • 当CPU平均使用率超过70% 内存平均使用率超过80%时,增加副本
  • 当使用率低于目标值时,减少副本(有默认的冷却时间)

4.3 基于自定义指标的HPA

但CPU/内存指标有个问题:它们不能直接反映业务压力。可能CPU还没跑满,但请求队列已经很长了。

这时候就需要自定义指标。对于Embedding服务,最相关的指标是:

  • 请求速率(QPS):每秒处理的请求数
  • 请求延迟:每个请求的处理时间
  • 队列长度:等待处理的请求数

我们需要安装Prometheus来收集这些指标:

# 使用Helm安装Prometheus
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm install prometheus prometheus-community/kube-prometheus-stack -n monitoring

然后在vLLM服务里暴露指标。vLLM本身有Prometheus指标端点,我们只需要配置ServiceMonitor让Prometheus能发现它:

# servicemonitor.yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: vllm-monitor
  namespace: ai-services
spec:
  selector:
    matchLabels:
      app: qwen-embedding
  endpoints:
  - port: 8000
    path: /metrics  # vLLM的指标端点
    interval: 15s

现在Prometheus就能收集到vLLM的指标了。接下来安装Prometheus Adapter,把Prometheus的指标转换成K8s能识别的自定义指标:

# 基于自定义指标的HPA
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: qwen-embedding-hpa-custom
  namespace: ai-services
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: qwen-embedding-vllm
  minReplicas: 1
  maxReplicas: 10
  metrics:
  - type: Pods
    pods:
      metric:
        name: vllm_requests_per_second  # 自定义指标名
      target:
        type: AverageValue
        averageValue: 50  # 目标:每个Pod每秒处理50个请求

这个配置的意思是:保持每个Pod平均每秒处理50个请求。如果总请求数增加,就增加Pod数量;请求数减少,就减少Pod数量。

4.4 多指标组合策略

实际生产中,我们往往需要多个指标组合判断。比如:

  • 请求数高 延迟大 → 肯定要扩容
  • 请求数高 延迟正常 → 可能还能承受
  • 请求数低 CPU高 → 可能是某个请求特别耗时

可以配置多个指标,HPA会取所有指标计算出的副本数的最大值:

metrics:
- type: Pods
  pods:
    metric:
      name: vllm_requests_per_second
    target:
      type: AverageValue
      averageValue: 50
- type: Pods
  pods:
    metric:
      name: vllm_request_duration_seconds
    target:
      type: AverageValue
      averageValue: 0.5  # 目标延迟500ms
- type: Resource
  resource:
    name: cpu
    target:
      type: Utilization
      averageUtilization: 70

这样系统就会综合考虑请求数、延迟和CPU使用率,做出更合理的伸缩决策。

5. 实战:从部署到验证

5.1 完整部署流程

假设你已经有了K8s集群和GPU节点,完整的部署流程是这样的:

# 1. 创建命名空间
kubectl create namespace ai-services

# 2. 创建存储,保存模型文件
kubectl apply -f storage.yaml -n ai-services

# 3. 下载模型到存储(这步需要在能访问存储的机器上做)
# 把qwen3-embedding-4b-q4_0.gguf放到共享存储

# 4. 部署vLLM服务
kubectl apply -f vllm-deployment.yaml -n ai-services
kubectl apply -f vllm-service.yaml -n ai-services

# 5. 部署Open WebUI
kubectl apply -f webui-deployment.yaml -n ai-services
kubectl apply -f webui-service.yaml -n ai-services

# 6. 配置Ingress或NodePort,让外部能访问WebUI
kubectl apply -f ingress.yaml -n ai-services

# 7. 部署监控和HPA
kubectl apply -f prometheus-config.yaml -n monitoring
kubectl apply -f hpa-custom.yaml -n ai-services

# 8. 等待所有Pod就绪
kubectl get pods -n ai-services -w

5.2 压力测试与扩缩容验证

部署完成后,需要验证自动伸缩是否生效。我们可以用简单的压力测试工具模拟请求:

# pressure_test.py
import requests
import concurrent.futures
import time

# vLLM服务的地址(通过Service访问)
VLLM_URL = "http://qwen-embedding-service.ai-services.svc.cluster.local:8000/v1/embeddings"

def send_embedding_request(text):
    """发送单个Embedding请求"""
    payload = {
        "model": "qwen3-embedding-4b",
        "input": text,
        "encoding_format": "float"
    }
    try:
        response = requests.post(VLLM_URL, json=payload, timeout=30)
        return response.status_code
    except Exception as e:
        return str(e)

def run_pressure_test(concurrent_users=10, duration=60):
    """运行压力测试"""
    print(f"开始压力测试: {concurrent_users}并发,持续{duration}秒")
    
    # 准备测试文本
    test_texts = [
        "机器学习是人工智能的一个分支",
        "深度学习基于神经网络技术",
        "自然语言处理让计算机理解人类语言",
        "计算机视觉处理图像和视频数据",
        "强化学习通过试错来学习策略"
    ] * 20  # 重复生成更多文本
    
    start_time = time.time()
    request_count = 0
    
    with concurrent.futures.ThreadPoolExecutor(max_workers=concurrent_users) as executor:
        while time.time() - start_time < duration:
            # 提交一批请求
            futures = []
            for text in test_texts[:concurrent_users]:
                future = executor.submit(send_embedding_request, text)
                futures.append(future)
            
            # 等待这批请求完成
            for future in concurrent.futures.as_completed(futures):
                result = future.result()
                request_count += 1
            
            time.sleep(0.1)  # 稍微控制一下节奏
    
    print(f"压力测试完成,总请求数: {request_count}")
    print(f"平均QPS: {request_count / duration:.2f}")

if __name__ == "__main__":
    # 先来个小流量热身
    print("=== 阶段1: 低负载测试 ===")
    run_pressure_test(concurrent_users=5, duration=30)
    time.sleep(10)
    
    # 然后加大压力
    print("\n=== 阶段2: 高负载测试 ===")
    run_pressure_test(concurrent_users=50, duration=120)
    
    # 最后恢复低负载
    print("\n=== 阶段3: 恢复期观察 ===")
    time.sleep(60)

运行压力测试时,同时在另一个终端观察Pod的变化:

# 观察Pod数量变化
watch -n 2 "kubectl get pods -n ai-services | grep qwen-embedding"

# 观察HPA状态
watch -n 2 "kubectl get hpa -n ai-services"

# 观察资源使用率
watch -n 2 "kubectl top pods -n ai-services"

你应该能看到:

  1. 压力测试开始时,Pod的CPU/内存使用率上升
  2. 当使用率超过阈值后,HPA开始创建新的Pod
  3. Pod数量逐渐增加到满足需求
  4. 压力测试结束后,使用率下降
  5. 经过一段冷却时间,多余的Pod被自动删除

5.3 效果验证与调试

如果扩缩容没有按预期工作,可以检查以下几点:

1. 检查Metrics Server是否正常工作:

kubectl get apiservices | grep metrics
kubectl get --raw /apis/metrics.k8s.io/v1beta1/nodes | head -20

2. 检查Prometheus是否收集到指标:

# 进入Prometheus Pod
kubectl exec -it prometheus-server-xxxx -n monitoring -- sh

# 查询vLLM指标
curl -s "http://localhost:9090/api/v1/query?query=vllm_requests_total" | jq .

3. 检查HPA事件和状态:

# 查看HPA详细状态
kubectl describe hpa qwen-embedding-hpa -n ai-services

# 查看HPA事件
kubectl get events -n ai-services --field-selector involvedObject.name=qwen-embedding-hpa

4. 常见问题与解决:

  • 问题:HPA显示<unknown>指标

    • 原因:Metrics Server或Prometheus Adapter没装好
    • 解决:检查相关Pod状态和日志
  • 问题:Pod数量不增加

    • 原因:资源请求设置过高,节点没有足够资源
    • 解决:调整Pod的resources.requests,或增加集群节点
  • 问题:Pod频繁伸缩(抖动)

    • 原因:指标波动太大,冷却时间太短
    • 解决:调整HPA的stabilizationWindowSeconds(稳定窗口)

6. 生产环境优化建议

6.1 资源规划与配额

在生产环境,要做好资源规划:

# 资源配额示例
apiVersion: v1
kind: ResourceQuota
metadata:
  name: ai-services-quota
  namespace: ai-services
spec:
  hard:
    requests.cpu: "20"
    requests.memory: 40Gi
    requests.nvidia.com/gpu: "4"
    limits.cpu: "40"
    limits.memory: 80Gi
    limits.nvidia.com/gpu: "8"
    pods: "20"

设置合理的ResourceQuota可以防止某个服务占用所有资源,影响其他服务。

6.2 Pod调度优化

GPU资源宝贵,要确保Pod调度到正确节点:

# 节点选择与亲和性
spec:
  template:
    spec:
      nodeSelector:
        accelerator: nvidia-gpu  # 选择有GPU的节点
      tolerations:
      - key: "nvidia.com/gpu"
        operator: "Exists"
        effect: "NoSchedule"
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values:
                  - qwen-embedding
              topologyKey: kubernetes.io/hostname

这个配置的意思是:

  • 只调度到有accelerator: nvidia-gpu标签的节点
  • 容忍GPU节点的污点
  • 尽量把同一个服务的Pod分散到不同节点(提高可用性)

6.3 优雅伸缩与数据一致性

扩缩容时要注意服务连续性:

# Pod生命周期配置
spec:
  template:
    spec:
      containers:
      - name: vllm-server
        lifecycle:
          preStop:
            exec:
              command: ["/bin/sh", "-c", "sleep 30"]  # 停止前等待30秒
        readinessProbe:
          httpGet:
            path: /health
            port: 8000
          initialDelaySeconds: 10
          periodSeconds: 5
          failureThreshold: 3
        livenessProbe:
          httpGet:
            path: /health
            port: 8000
          initialDelaySeconds: 30
          periodSeconds: 10
          failureThreshold: 3

关键配置:

  • preStop:Pod停止前等待30秒,让正在处理的请求完成
  • readinessProbe:就绪探针,确保Pod完全启动后再接收流量
  • livenessProbe:存活探针,检查Pod是否健康

6.4 监控与告警

完整的监控体系包括:

# Prometheus告警规则示例
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: vllm-alerts
  namespace: monitoring
spec:
  groups:
  - name: vllm
    rules:
    - alert: HighRequestLatency
      expr: histogram_quantile(0.95, rate(vllm_request_duration_seconds_bucket[5m])) > 1
      for: 2m
      labels:
        severity: warning
      annotations:
        summary: "vLLM请求延迟高"
        description: "95%的请求延迟超过1秒,当前值: {{ $value }}秒"
    
    - alert: PodScalingFailed
      expr: kube_horizontalpodautoscaler_status_condition{condition="ScalingLimited", status="true"} == 1
      for: 1m
      labels:
        severity: critical
      annotations:
        summary: "HPA扩缩容失败"
        description: "HPA {{ $labels.horizontalpodautoscaler }} 无法继续扩容,可能达到最大副本数或资源不足"

7. 总结

通过K8s的自动扩缩容能力,我们为通义千问Embedding模型打造了一个弹性伸缩的知识库系统。这个方案的核心价值在于:

1. 成本优化:按需使用GPU资源,闲时自动缩容,能节省30%-50%的云资源成本。

2. 性能保障:流量高峰时自动扩容,确保服务响应时间稳定,提升用户体验。

3. 运维简化:无需人工干预扩缩容,系统自动根据负载调整,降低运维复杂度。

4. 高可用性:多副本部署配合健康检查,单个Pod故障不影响整体服务。

实际部署建议:

  • 从小规模开始:先部署最小规模(1个副本),观察基线性能
  • 逐步调整阈值:根据实际负载调整HPA的阈值,避免频繁抖动
  • 监控是关键:建立完整的监控体系,包括资源使用、业务指标、错误率等
  • 定期压力测试:定期模拟高峰流量,验证扩缩容策略是否有效
  • 设置安全边界:合理设置minReplicas和maxReplicas,防止无限扩容

这个方案不仅适用于Qwen3-Embedding-4B,也适用于其他Embedding模型甚至生成式模型。随着业务增长,你还可以考虑更高级的特性,如基于预测的扩缩容、多集群弹性等。

弹性伸缩不是一劳永逸的配置,而是一个持续优化的过程。通过监控、测试、调整,你会逐渐找到最适合自己业务场景的伸缩策略。


获取更多AI镜像

想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐