返回 RAG 工程实战目录

部署上线:生产环境与 CI/CD

准备发布门禁、监控、备份和回滚

生命周期 · 阶段六 部署上线 | 上游:测试准出(08~09)| 下游:运维迭代(11)

本文站在部署/运维工程师角度,覆盖生产拓扑、资源规划、容器化部署、GPU 模型服务、网关、CI/CD、监控与回滚。

一、生产部署拓扑

flowchart TB U((用户)) --> NG["Nginx 主备 HTTPS · 限流 · SSE 透传"] NG --> APP["应用服务 ×N FastAPI(无状态)"] APP --> MY[("MySQL 主从")] APP --> RD[("Redis")] APP --> WK["Celery Worker ×N 入库流水线"] APP --> VDB[("Qdrant / Milvus 集群 + 持久卷")] APP -.-> MIO[("MinIO 文档原件")] WK -.-> MIO APP --> GPU WK --> GPU subgraph GPUNODE["GPU 节点(只跑模型服务)"] direction LR G1["vLLM LLM"] --- G2["TEI Embedding"] --- G3["Rerank"] end GPU{{" "}} style NG fill:#f5f5f5,stroke:#999 style GPUNODE fill:#fff0f5,stroke:#e06fa8 style MIO fill:#f0fff4,stroke:#4abf60

部署分割线原则:

  • CPU 节点跑应用/Worker/中间件,GPU 节点只跑模型服务(成本隔离)
  • 应用与 Worker 分开部署:扩容节奏不同(问答高峰扩应用,批量入库扩 Worker)

二、资源规划参考

规模应用+WorkerGPU(推理)存储说明
试点(<100 人,<1 万切片)1 台 8C16G1 × 24G(或 API)200G单机 docker compose 可扛
部门级(<1000 人,<50 万切片)2-3 台 8C16G2 × 24G / 1 × 48G1T双副本 + GPU 双实例
企业级(>1000 人,>500 万切片)K8s 集群GPU 池(vLLM ×3+)分布式存储Milvus 分布式 + 多副本

显存初筛:模型权重可按参数量 × 每参数字节数粗估,但 KV Cache 还受上下文长度、批处理、并发、精度和推理引擎影响。任何显存与并发数字都要用目标模型、硬件和请求分布实测,不能直接套用固定换算。

三、Docker Compose 部门级参考骨架

# docker-compose.prod.yml
services:
  app:
    image: registry.example.com/rag-kb/app:${TAG:-latest}
    restart: always
    environment: [APP_ENV=prod]
    env_file: [.env.prod]
    depends_on: {mysql: {condition: service_healthy}, qdrant: {condition: service_started}}
    deploy:
      resources: {limits: {cpus: "4", memory: 8G}}
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/api/v1/health"]
      interval: 30s
      timeout: 5s
      retries: 3

  worker:
    image: registry.example.com/rag-kb/app:${TAG}
    restart: always
    command: celery -A app.worker worker -Q ingest -c 4 --max-tasks-per-child=50
    env_file: [.env.prod]          # worker 单独扩容:docker compose up --scale worker=3
    deploy:
      resources: {limits: {cpus: "8", memory: 16G}}

  mysql:
    image: mysql:8.0
    restart: always
    volumes: ["mysql_data:/var/lib/mysql"]
    environment: {MYSQL_ROOT_PASSWORD: "${MYSQL_ROOT_PWD}"}
    healthcheck: {test: ["CMD", "mysqladmin", "ping", "-h", "localhost"], interval: 10s, retries: 5}

  redis:
    image: redis:7-alpine
    restart: always
    command: redis-server --requirepass "${REDIS_PWD}"
    volumes: ["redis_data:/data"]

  qdrant:
    image: qdrant/qdrant
    restart: always
    volumes: ["qdrant_data:/qdrant/storage"]
    ports: ["6333:6333"]

  minio:
    image: minio/minio
    restart: always
    command: server /data --console-address ":9001"
    volumes: ["minio_data:/data"]
    environment: {MINIO_ROOT_USER: "${MINIO_USER}", MINIO_ROOT_PASSWORD: "${MINIO_PWD}"}

  nginx:
    image: nginx:1.25
    restart: always
    ports: ["443:443"]
    volumes: ["./nginx.conf:/etc/nginx/conf.d/default.conf:ro", "./certs:/etc/nginx/certs:ro"]
    depends_on: [app]

volumes: {mysql_data: {}, redis_data: {}, qdrant_data: {}, minio_data: {}}

四、Kubernetes 部署(企业级)

apiVersion: apps/v1
kind: Deployment
metadata: {name: rag-app}
spec:
  replicas: 3
  selector: {matchLabels: {app: rag-app}}
  template:
    metadata:
      labels: {app: rag-app}
      annotations:
        prometheus.io/scrape: "true"        # 自动接入监控
        prometheus.io/port: "8000"
    spec:
      containers:
- name: app
        image: registry.example.com/rag-kb/app:${TAG}
        resources:
          requests: {cpu: "2", memory: 4Gi}
          limits: {cpu: "4", memory: 8Gi}
        readinessProbe: {httpGet: {path: /api/v1/health, port: 8000}, periodSeconds: 10}
        envFrom: [{secretRef: {name: rag-prod-secret}}]
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata: {name: rag-app-hpa}
spec:
  scaleTargetRef: {apiVersion: apps/v1, kind: Deployment, name: rag-app}
  minReplicas: 3
  maxReplicas: 10
  metrics: [{type: Resource, resource: {name: cpu, target: {type: Utilization, averageUtilization: 70}}}]
---
# GPU 模型服务:vLLM
apiVersion: apps/v1
kind: Deployment
metadata: {name: vllm}
spec:
  replicas: 2
  template:
    spec:
      nodeSelector: {"nvidia.com/gpu.product": "RTX-4090"}   # 打到 GPU 节点
      containers:
- name: vllm
        image: vllm/vllm-openai:latest
        args: ["--model", "Qwen/Qwen2.5-7B-Instruct",
               "--max-model-len", "8192", "--gpu-memory-utilization", "0.9"]
        resources: {limits: {"nvidia.com/gpu": 1}}

要点:Worker 独立 Deployment(队列名隔离);配置走 ConfigMap、密钥走 Secret;模型镜像/权重用 PVC 或提前预热到节点。

五、GPU 模型服务部署要点

要求
驱动NVIDIA Driver ≥ 535(按 CUDA 版本匹配)
CUDAvLLM 官方镜像自带运行时,宿主机只需驱动
vLLM 关键参数--max-model-len(控显存)、--gpu-memory-utilization 0.9--max-num-seqs(并发路数)
预热启动后先发 3~5 条真实长度请求,消除首 token 延迟毛刺
多实例负载均衡vLLM ×2 前挂 Nginx/K8s Service round-robin
EmbeddingTEI 镜像独立部署,吞吐不够加副本而非加大单实例
降级预案GPU 全挂 → 切备用商用 API(配置开关,见 03 篇适配器)

六、Nginx 网关配置

server {
    listen 443 ssl;
    server_name kb.example.com;
    ssl_certificate     /etc/nginx/certs/server.crt;
    ssl_certificate_key /etc/nginx/certs/server.key;

    client_max_body_size 200m;          # 与上传上限一致

    location /api/v1/chat {
        proxy_pass http://app_pool;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_buffering off;            # SSE 必须:关闭缓冲,token 立即下发
        proxy_cache off;
        proxy_read_timeout 300s;        # 生成耗时兜底
        chunked_transfer_encoding on;
    }

    location /api/ {
        proxy_pass http://app_pool;
        proxy_read_timeout 60s;
        limit_req zone=api burst=20 nodelay;   # 限流(http 段定义 zone)
    }
}

SSE 三个必查项proxy_buffering offproxy_read_timeout 放大、gziptext/event-stream 关闭——任何一项漏配都会导致"流式变卡顿/中断"。

七、CI/CD 流水线

flowchart LR A["lint 代码检查"] --> B["test 单测/集成"] --> C["build 构建镜像"] --> D["deploy-dev 部署测试环境"] --> E{"eval 评测卡点 (链路变更必跑)"} -->|指标≥基线| F["deploy-prod 手动审批后发布"] E -->|不达标| X["阻断合并"] style E fill:#fff8e1,stroke:#d4a017 style F fill:#eaffea,stroke:#4abf60 style X fill:#ffeaea,stroke:#e05a5a
# .gitlab-ci.yml
stages: [lint, test, build, deploy-dev, eval, deploy-prod]

lint:
  stage: lint
  image: python:3.11
  script: [pip install ruff, ruff check app tests]
  coverage: '/TOTAL.*\s+(\d+)%/'

test:
  stage: test
  image: python:3.11
  services: [qdrant/qdrant]           # 依赖容器跑集成测试
  script:
- pip install -e ".[dev]"
- pytest tests/unit tests/e2e --cov=app --cov-report=term
  coverage: '/TOTAL.*\s+(\d+)%/'
  rules: [{if: '$CI_PIPELINE_SOURCE == "merge_request_event"'}]

build:
  stage: build
  image: docker:24
  script:
- docker build -f docker/Dockerfile -t $REGISTRY/rag-kb/app:$CI_COMMIT_SHORT_SHA .
- docker push $REGISTRY/rag-kb/app:$CI_COMMIT_SHORT_SHA
  rules: [{if: '$CI_COMMIT_BRANCH == "main"'}]

deploy-dev:
  stage: deploy-dev
  script: [./deploy.sh dev $CI_COMMIT_SHORT_SHA]
  environment: dev

eval:                                  # RAG 特有卡点:效果回归(见 09 篇)
  stage: eval
  script:
- python tests/eval/run_eval.py --baseline baselines/dev.json --threshold "recall@5>=0.85,faithfulness>=0.90"
  rules: [{if: '$CHANGE_SCOPE =~ /prompt|retrieval|pipeline/'}]   # 触及链路的变更必跑

deploy-prod:
  stage: deploy-prod
  when: manual                          # 生产手动审批
  script: [./deploy.sh prod $CI_COMMIT_SHORT_SHA --rollout]
  environment: prod

流水线纪律

  • 代码变更(CR 时):lint + 单测 + 集成测试
  • 链路变更(Prompt/检索/切片参数):必须过评测卡点,指标不低于基线才可上线
  • 镜像 tag 用 commit SHA,禁用 latest 上生产;.env/Secret 永不进镜像与仓库

八、配置与密钥管理

环境配置载体密钥载体
开发.env.example 模板 + 本地 .env本地
测试ConfigMap加密 Secret(GitLab 变量注入)
生产ConfigMap(版本化)K8s Secret / Vault,季度轮换

三条红线:密钥不进 Git(pre-commit 扫描)、不进镜像(构建时 --build-arg 禁用)、不打日志(日志脱敏中间件)。

九、监控告警部署

# prometheus 告警规则示例
groups:
- name: rag-kb
  rules:
- alert: ApiErrorRateHigh
    expr: sum(rate(http_requests_total{status=~"5.."}[5m])) / sum(rate(http_requests_total[5m])) > 0.01
    for: 5m
    labels: {severity: critical}
    annotations: {summary: "5xx 错误率超 1% 持续 5 分钟"}
- alert: ChatP95High
    expr: histogram_quantile(0.95, sum(rate(chat_latency_bucket[10m])) by (le)) > 8
    for: 10m
    labels: {severity: warning}
    annotations: {summary: "问答 P95 超过 8s"}
- alert: IngestQueueBacklog
    expr: celery_queue_length{queue="ingest"} > 100
    for: 15m
    labels: {severity: warning}
    annotations: {summary: "入库队列积压 {{ $value }} 任务"}
- alert: GpuMemoryNearLimit
    expr: gpu_memory_used / gpu_memory_total > 0.95
    for: 10m
    labels: {severity: warning}

Grafana 面板最低配齐四块:接口性能(QPS/P95/错误率)、队列健康(积压/Worker 存活)、GPU(利用率/显存)、业务指标(问答量/拒答率/点踩率)。

十、备份与恢复

对象方式频率RPO
文档原件(MinIO)快照 + 异地同步每日≤ 24h
MySQL全备 + binlog全备每日 / binlog 实时≤ 5min
向量库快照;终极兜底=原件重跑流水线每周可重建
配置/SecretGit 版本化 + Vault变更即备0

恢复演练(频率按 RPO/RTO 与风险确定,且必须实际执行):

  • 恢复 MySQL 到指定时间点 → 核对任务表与文档表
  • MinIO 原件回放 → 全量重跑入库流水线 → 抽查检索一致
  • 记录恢复耗时,更新应急预案

向量库的可重建性是设计出来的:原件 + 幂等流水线 = 向量数据不是唯一副本

十一、发布策略与回滚

  • 发布顺序:dev(自动化验证)→ test(评测集回归)→ prod 灰度(按用户组 10% → 50% → 100%)
  • 模型/链路变更:先双跑(新旧 Prompt 各跑评测集对比),确认达标再切
  • Embedding 升级:蓝绿双 Collection,新库建完校验条数一致 → 切流量 → 旧库保留 7 天回滚窗口
  • 回滚三步:镜像回退到上一 SHA → ConfigMap 回退(版本化)→ 观察监控 15 分钟;5 分钟内可完成
  • 每次生产发布必须登记:版本、变更内容、评测结论、回滚点

十二、安全加固清单

  • [ ] 全链路 HTTPS;内部服务间网络隔离(仅放行必要端口)
  • [ ] 容器以非 root 运行;镜像漏洞扫描(Trivy)进 CI,高危阻断
  • [ ] MinIO/MySQL/Redis/Qdrant 全部强密码 + 内网访问,禁公网暴露
  • [ ] Nginx 限流 + WAF(防 Prompt 注入的恶意高频请求)
  • [ ] 依赖季度升级;CVE 订阅告警
  • [ ] 审计日志(上传/删除/越权尝试)按适用法规、业务风险和公司制度确定留存周期
  • [ ] 上线前渗透测试:越权检索、注入话术、文件上传绕过(见 08 篇 TC-SEC 系列)

本篇交付与下一步

本章 Compose、CI、告警与容量数字是参考骨架,不能直接视为生产基线。上线前还应完成镜像版本锁定、Secret 管理、TLS、网络隔离、数据迁移、备份恢复、最小权限、日志脱敏和回滚演练。下一步进入 11-运维与持续迭代