部署上线:生产环境与 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)
二、资源规划参考
| 规模 | 应用+Worker | GPU(推理) | 存储 | 说明 |
|---|---|---|---|---|
| 试点(<100 人,<1 万切片) | 1 台 8C16G | 1 × 24G(或 API) | 200G | 单机 docker compose 可扛 |
| 部门级(<1000 人,<50 万切片) | 2-3 台 8C16G | 2 × 24G / 1 × 48G | 1T | 双副本 + 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 版本匹配) |
| CUDA | vLLM 官方镜像自带运行时,宿主机只需驱动 |
| vLLM 关键参数 | --max-model-len(控显存)、--gpu-memory-utilization 0.9、--max-num-seqs(并发路数) |
| 预热 | 启动后先发 3~5 条真实长度请求,消除首 token 延迟毛刺 |
| 多实例负载均衡 | vLLM ×2 前挂 Nginx/K8s Service round-robin |
| Embedding | TEI 镜像独立部署,吞吐不够加副本而非加大单实例 |
| 降级预案 | 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 off、proxy_read_timeout 放大、gzip 对 text/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 |
| 向量库 | 快照;终极兜底=原件重跑流水线 | 每周 | 可重建 |
| 配置/Secret | Git 版本化 + 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-运维与持续迭代。