# 运维 Runbook — 企业知识库 RAG 系统

4 阅读14分钟

配套文档:RAG生产级开发文档.md · Prompt与评测集管理规范.md · rag-starter/ 适用对象:SRE、值班工程师、Tech Lead 版本:v1.0


0. 使用说明

本 Runbook 是 值班手册,应满足两个标准:

  1. 能让一个不熟悉系统的值班同学,按步骤把故障止血。
  2. 每个 SOP 至少经过一次真实演练。 未演练的 SOP 标为「未演练」,不能依赖。

文档里每个告警都有对应的 SOP 编号,可以从 Grafana 告警直接跳转。


1. 系统拓扑速览

用户 → Ingress(Nginx/APISIX) → rag-api(K8s) → ┬→ TEI-Embedding(GPU)
                                              ├→ TEI-Reranker(GPU)
                                              ├→ Milvus(Cluster)
                                              ├→ Elasticsearch(Cluster)
                                              ├→ Redis(Cluster)
                                              ├→ PostgreSQL(主从)
                                              └→ Anthropic API(外部)

离线:Airflow → Connectors → 解析/切分 → TEI-Embedding → Milvus + ES

观测:OpenTelemetry → Langfuse + Prometheus + Loki + Grafana

关键依赖外部性排序(出故障对业务影响):

  1. Anthropic API(不可用 = 服务整体不可用)
  2. Milvus(不可用 = 召回失败,可降级到 ES)
  3. ES(不可用 = 召回降级到纯向量)
  4. TEI-Embedding(不可用 = 召回失败)
  5. TEI-Reranker(不可用 = 跳过 rerank 仍可用)
  6. Redis(不可用 = 缓存失效,性能下降不致命)
  7. PG(不可用 = 反馈/审计停写,主链路不受影响)

2. SLO 与告警分级

2.1 SLO

指标目标错误预算(30 天)
可用性99.5%3.6 小时
首字节 P95< 3s
端到端 P95< 15s
越权穿透率0%绝对红线
召回回归 Recall@10≥ 0.85

2.2 告警分级

等级响应时限通知渠道示例
P05 分钟电话 + 短信 + IM整体不可用、越权穿透、数据泄漏
P115 分钟IM @值班P95 翻倍、错误率 > 5%、Anthropic 限流
P21 小时IM单依赖抖动、单 Pod 重启
P3次日处理邮件慢查询、磁盘 70%

3. 告警与 SOP 索引

告警名级别SOP
RAGAPIDownP0SOP-001
RAGAclBreachDetectedP0SOP-002
Anthropic5xxSurgeP1SOP-003
MilvusUnavailableP1SOP-004
ESUnavailableP1SOP-005
TEIEmbeddingDownP1SOP-006
LatencyP95HighP1SOP-007
Error5xxRateHighP1SOP-008
RecallRegressionP1SOP-009
DownvoteSurgeP1SOP-010
IndexingPipelineFailedP2SOP-011
DailyTokenBudget80P2SOP-012
RedisHighLatencyP2SOP-013
DiskUsage70P3SOP-014

4. 关键 SOP

SOP-001: RAG API 整体不可用

判定/healthz 失败、Ingress 5xx > 50%、用户大面积反馈。

止血步骤(目标:5 分钟内恢复):

  1. 快速确认范围

    kubectl -n rag get pods -l app=rag-api
    kubectl -n rag describe deployment rag-api | tail -20
    curl -s https://rag.internal/healthz
    
  2. 看最近变更(80% 的故障来自最近发布)

    kubectl -n rag rollout history deployment/rag-api | head -5
    
  3. 决策

    • 最近 1 小时有发布 → 立即回滚
      kubectl -n rag rollout undo deployment/rag-api
      kubectl -n rag rollout status deployment/rag-api --timeout=2m
      
    • 无发布 → 继续步骤 4。
  4. 检查依赖

    # 看 rag-api 日志找根因
    kubectl -n rag logs -l app=rag-api --tail=200 | grep -E "ERROR|CRITICAL"
    # 检查依赖
    kubectl -n rag get pods -l 'app in (milvus,elasticsearch,redis,postgres,tei-embedding)'
    
  5. 资源压力?

    • OOM:临时把 replica 拉到 12,同时排查 memory leak。
    • CPU 满载:检查是不是恶意流量,必要时在 Ingress 上限速。
  6. 依赖不可用?

    • 跳转对应 SOP-004 / 005 / 006。
  7. 止血后:在事故频道贴 trace_id 样本、当前指标、已做操作,让全员看到状态。

事后:填《事故复盘单》模板,48 小时内召开复盘。


SOP-002: 越权穿透

判定:日志中出现 acl_violation ERROR、或用户报告看到别部门数据。

⚠️ 这是公司红线,按数据安全事故走最高级流程。

止血步骤(目标:10 分钟内阻断):

  1. 立即对外停服(防止扩散):

    # 把 Ingress 切到维护页
    kubectl -n rag apply -f deploy/k8s/maintenance-ingress.yaml
    
  2. 保留现场

    • 不要重启 Pod。
    • dump 最近 1 小时 rag-api 日志和 audit_log 表数据到对象存储。
    kubectl -n rag logs --since=1h -l app=rag-api > /tmp/incident-$(date +%s).log
    psql $PG_DSN -c "COPY (SELECT * FROM audit_log WHERE created_at > NOW() - INTERVAL '2 hours') TO STDOUT WITH CSV" > /tmp/audit.csv
    
  3. 拉群通报:安全 + 数据 + 法务 + 算法 + SRE,按公司数据事件协议走。

  4. 定位

    • acl_violation 日志找具体 chunk_id 和 user_id。
    • 检查该 chunk 在 Milvus / ES 里的 acl 字段是否正确:
      # rag-api 容器内执行
      from app.core.clients import get_milvus
      client = get_milvus()
      result = client.query(collection_name="kb_chunks_v1", filter=f'chunk_id == "<...>"', output_fields=["acl"])
      
    • 检查用户的 acl 是不是错配:联系 IAM 同学。
  5. 修复

    • 数据层错误:紧急重跑索引 pipeline,修正 acl 字段。
    • 代码层错误:紧急 hotfix + 强制 PR 走双人审。
    • 用户层错误:联系 IAM 修复。
  6. 恢复服务

    • 修复确认后,先用 staging 全量验证(专项 ACL 测试集)。
    • 灰度 1% → 10% → 100%,每步至少 30 分钟。

事后:72 小时内提交事故报告至安全委员会,告知所有可能受影响用户。


SOP-003: Anthropic API 异常

判定Anthropic5xxSurge 告警、generation 错误率突增。

止血步骤

  1. 看错误类型

    kubectl -n rag logs -l app=rag-api --tail=500 | grep "anthropic" | grep -oE "(429|529|500|502|503)" | sort | uniq -c
    
    • 429:限流,跳到步骤 2。
    • 529:Anthropic 过载,跳到步骤 3。
    • 5xx:上游故障,跳到步骤 3。
  2. 限流 (429)

    • 短期:在 LLM Gateway 把全局并发 semaphore 调低 20%。
    • 中期:联系 Anthropic 提配额(提前准备好 ticket 模板)。
    • 长期:把分类/改写换 Haiku,主回答保留 Sonnet。
  3. 上游故障 (5xx/529)

    • 检查 Anthropic Status
    • 降级路径:把主模型切到 Haiku(应急配置):
      kubectl -n rag set env deployment/rag-api CLAUDE_MODEL_MAIN=claude-haiku-4-5-20251001
      kubectl -n rag rollout restart deployment/rag-api
      
      注意:Haiku 答案质量下降,需要业务团队同步知会。
    • 如果有备用通道(Bedrock / Vertex 部署的 Claude),切换流量。
  4. 持续超过 30 分钟无法恢复

    • 在前端展示「LLM 服务异常,仅检索可用」提示。
    • 把 API 改为只返回检索结果(citation 列表),不生成答案。

SOP-004: Milvus 不可用

判定milvus_search 错误率 > 50%、Milvus QueryNode Pod 异常。

止血步骤

  1. 快速诊断

    kubectl -n milvus get pods
    kubectl -n milvus logs <querynode-pod> --tail=100
    
  2. 降级:在 rag-api 配置里把 hybrid 退化为纯 ES:

    kubectl -n rag set env deployment/rag-api RETRIEVAL_MODE=es_only
    kubectl -n rag rollout restart deployment/rag-api
    

    ⚠️ 此降级要求代码侧支持 RETRIEVAL_MODE 配置,starter 默认不带,需提前实现。

  3. 恢复 Milvus

    • QueryNode 重启:kubectl -n milvus rollout restart sts milvus-querynode
    • 数据节点问题:联系 DBA / 用 Milvus Operator 修复。
    • 索引损坏:从快照恢复(要求 Milvus 已开启 backup)。
  4. 恢复后:去掉降级配置,灰度切回 hybrid。


SOP-005: ES 不可用

判定:BM25 召回错误率 > 50%。

止血步骤

  1. 诊断

    curl http://elasticsearch:9200/_cluster/health?pretty
    
    • status=red:分片丢失,严重。
    • status=yellow:副本未分配,但可用。
  2. 降级:hybrid 退化为纯向量召回。代码侧已支持 es_search 失败回退(hybrid.py 中已有 try-catch)。理论上无需人工干预,但需要监控 Recall@10 是否回落到可接受区间。

  3. 恢复

    • 黄色:等副本自动分配,或手动 reroute。
    • 红色:从快照恢复,参考 ES 官方文档。

SOP-006: TEI Embedding 不可用

判定embed_query 错误率 > 50%。

止血步骤

  1. 诊断

    kubectl -n rag get pods -l app=tei-embedding
    kubectl -n rag logs <tei-pod> --tail=100
    curl http://tei-embedding/health
    
  2. 常见原因

    • GPU OOM:批量请求过大,单实例承载不住 → 扩副本。
    • 显存碎片:滚动重启。
    • GPU 驱动异常:联系基础架构。
  3. 降级

    • 没有简单降级。Embedding 是向量召回必经。
    • 唯一兜底是切到纯 BM25 ES 召回(同 SOP-004 步骤 2 的反向)。
  4. 预防:永远保留至少 2 副本,且分布在不同 GPU 节点。


SOP-007: 延迟突增

判定rag_latency_seconds{quantile="0.95"} > 5s 持续 5 分钟。

排查路径(按从快到慢的顺序):

  1. 看 Langfuse trace 找哪一段最慢:

    • Rewrite > 1s?→ Anthropic 抖动,跳 SOP-003。
    • Retrieval > 2s?→ Milvus/ES 抖动,跳 SOP-004/005。
    • Rerank > 2s?→ TEI-Reranker 压力,看 GPU 利用率。
    • LLM > 10s?→ 上下文太长(看 token 数),或 Anthropic 慢。
  2. 看上下文 token 数

    kubectl -n rag logs -l app=rag-api --tail=500 | grep "context_tokens" | jq '.context_tokens' | sort -n | tail -20
    

    异常长 → 检查切分是否退化、retrieval 是否返回了超大 chunk。

  3. 看流量:是否突增导致排队。Grafana 看 QPS,HPA 是否扩了副本。

  4. 临时缓解

    • RETRIEVAL_TOP_K 从 50 调到 30。
    • RERANK_TOP_N 从 8 调到 5。
    • CONTEXT_TOKEN_BUDGET 临时调低。

SOP-008: 5xx 错误率突增

判定rag_request_total{status=~"5.."} 占比 > 1%。

排查

  1. 看错误分布

    kubectl -n rag logs -l app=rag-api --tail=1000 | grep ERROR | grep -oE 'error[^,]*' | sort | uniq -c | sort -rn | head
    
  2. 常见根因

    • UpstreamTimeout → 跳 SOP-007。
    • UpstreamUnavailable → 跳对应依赖 SOP。
    • pipeline_failed → 看具体 exception,可能是代码 bug。
    • OOM → 扩容 + 排查 memory leak。
  3. 临时缓解:限流 / 扩容 / 回滚最近发布。


SOP-009: 召回质量回归

判定:nightly eval recall@10 低于阈值。

排查

  1. 看是哪类 case 退化

    cat reports/full.json | jq '.cases[] | select(.recall_at_10 < 0.5) | .tags' | sort | uniq -c
    
  2. 常见原因

    • 索引 pipeline 异常:某批数据没入库 → 跳 SOP-011。
    • Embedding 模型换了:版本号不一致。
    • 切分策略改动:chunk 边界破坏了原结构。
  3. 回滚:召回回归是 P1 但通常可以等 24h 修复。如果影响重大业务,走 prompt/索引版本回滚流程。


SOP-010: 用户负反馈突增

判定down_rate = down / (up + down) > 15% 持续 1 小时。

排查

  1. 看是哪类问题被踩

    SELECT al.question, COUNT(*) as cnt
    FROM feedback f JOIN audit_log al USING (trace_id)
    WHERE f.rating='down' AND f.created_at > NOW() - INTERVAL '2 hours'
    GROUP BY al.question ORDER BY cnt DESC LIMIT 20;
    
  2. 常见根因

    • 知识库内容陈旧(业务变了文档没更新)→ 通知内容团队。
    • 某类长尾问题召回不到 → 加进 hard.jsonl,立项优化。
    • 最近 prompt 变更引起 → 看是不是要回滚。
  3. 应急响应:把高频负反馈 query 的 top-3 加入「已知问题清单」,前端展示前置提示。


SOP-011: 索引 Pipeline 失败

判定:Airflow DAG 失败、indexing_failures_total 突增。

排查

  1. 看 Airflow 日志找哪个 task 失败。

  2. 常见原因

    • 数据源 API 限流 → 调整 connector 并发度。
    • 文档格式异常 → 看 parser 异常栈,加 fallback 解析。
    • TEI 不可用 → 跳 SOP-006。
    • 双写一致性失败 → 检查 Milvus / ES 一致性,必要时重跑该 doc。
  3. 影响评估

    • 失败 < 1% 文档 → 走重试。
    • 失败 > 10% → 暂停 pipeline,根因修复后再放开。

⚠️ 红线:禁止直接跳过失败 doc 标记成功,必须有兜底重试或人工介入。


SOP-012: 成本预算告警

判定:当日 token 消费达月预算 80%。

应急动作

  1. 看消费分布

    SELECT prompt_version, COUNT(*) cnt,
           SUM((latency_ms->>'llm_ms')::int) total_llm_ms
    FROM audit_log
    WHERE created_at > NOW() - INTERVAL '1 day'
    GROUP BY prompt_version;
    
  2. 快速降本

    • 把分类/改写从 Sonnet 切到 Haiku。
    • 检查 Prompt Caching 命中率,不到 50% 必须排查(看 cache_read_input_tokens 占比)。
    • 启用答案缓存(如已实现)。
    • 限制单用户日 token 配额。
  3. 中期

    • 找出高频重复 query,运营沉淀成 FAQ 直答。
    • 评估能否做 query 聚类后批量缓存。

SOP-013: Redis 延迟

判定:Redis P99 > 50ms。

步骤

  1. 看 slowlog:redis-cli -h <host> SLOWLOG GET 20
  2. 看内存:redis-cli -h <host> INFO memory
  3. 应急扩容或清理过期 key。
  4. 缓存不可用时 rag-api 应能 graceful 降级(直查后端),需在代码侧确保。

SOP-014: 磁盘空间

判定:节点磁盘使用率 > 70%。

步骤

  1. 看大头:du -sh /var/lib/{milvus,elasticsearch,postgres}/* | sort -h | tail
  2. 常见可清理:
    • ES 老 index:curl -X DELETE elasticsearch:9200/kb_chunks_v0-*(确认无在用)
    • Milvus 老 collection:要求该 collection 退役 ≥ 7 天。
    • PG 老 audit_log:超过 180 天归档到对象存储。
  3. 扩容 PVC。

5. 发布与回滚 SOP

SOP-R1:标准发布流程

前置检查清单

  • PR 已合并主分支
  • CI 全部通过(lint / unit / smoke eval)
  • 镜像已推送到 registry 并扫过漏洞
  • 影响评估写入 PR 描述
  • 通知值班同学和业务接口人

发布步骤

  1. 预发部署

    kubectl -n rag-staging set image deployment/rag-api rag-api=<image-tag>
    kubectl -n rag-staging rollout status deployment/rag-api
    
  2. 预发回归

    poetry run pytest tests/eval -m smoke --env=staging
    
  3. 生产金丝雀(1%)

    • 用 Argo Rollouts / Flagger / 手动 traffic split。
    • 观察 30 分钟:错误率、P95、👎 率。
  4. 小流量(10%):观察 2 小时。

  5. 全量:观察 24 小时,期间值班不离岗。

SOP-R2:紧急回滚

触发条件(任一即触发):

  • 5xx 比例 > 2%
  • P95 延迟翻倍
  • 👎 率 > 15%
  • 越权穿透
  • 评测指标回归 > 5%

步骤

  1. 代码回滚(< 1 分钟):

    kubectl -n rag rollout undo deployment/rag-api
    kubectl -n rag rollout status deployment/rag-api --timeout=2m
    
  2. 配置回滚(如有):

    kubectl -n rag apply -f configs/last-known-good/configmap.yaml
    kubectl -n rag rollout restart deployment/rag-api
    
  3. 数据回滚(向量库 collection 切换):

    # 把别名切回老 collection
    poetry run python scripts/switch_milvus_alias.py --alias kb_chunks_alias --target kb_chunks_v1
    
  4. 通知:在值班群和事故频道同步状态。

  5. 复盘:48 小时内开会。


6. 演练计划

未演练的 SOP 等于摆设。建议演练频率:

演练频率形式
紧急回滚每月在 staging 实操
Anthropic 切换每季在 staging 把 base_url 切到模拟 503
Milvus 单节点 down每季Chaos 工具 kill 一个 Pod
ACL 越权红蓝对抗每半年安全团队主导
全链路停电每年整个 region 切换

每次演练产出 演练报告:步骤是否清晰、耗时多少、卡点在哪、需要 SOP 改进什么。


7. 值班交接

每周值班轮换。交接清单:

  • 当周告警列表与处理状态
  • 未关闭的 P1/P2
  • 下周计划发布
  • 进行中的演练
  • 待加进评测集的 bad case
  • 待补充的 SOP

模板存在 runbook/handover-template.md,每周提交一次。


8. 联系人与外部资源

角色工作时间紧急联系
RAG 算法 OnCallIM手机
平台 SRE OnCallIM手机
Anthropic 商务邮件客户成功经理
数据/IAM OwnerIM手机
安全 OnCallIM24h 值班电话

关键外部链接


附录 A:事故复盘模板

# 事故复盘 - [日期] [简要标题]

## 一句话总结
(用户视角影响 + 根因)

## 时间线
- HH:MM - 告警触发 / 用户反馈
- HH:MM - 值班介入
- HH:MM - 定位到 XX
- HH:MM - 止血操作
- HH:MM - 服务恢复
- HH:MM - 完全修复

## 影响范围
- 持续时间:
- 影响用户:
- 影响功能:
- 数据是否泄漏:

## 根因
(5 Whys)

## 处置过程评价
做得好的:
做得不好的:

## 改进项(必须可追踪)
- [ ] [P0] 修复根因代码 - Owner: XX - DDL: 
- [ ] [P1] 增加监控覆盖 - Owner: XX - DDL: 
- [ ] [P2] 补充 SOP / 演练 - Owner: XX - DDL: 

## 教训
(一句话,可以放进 CLAUDE.md 或 wiki 沉淀)

附录 B:值班同学的应对原则

  1. 先止血,再定位:用户体验优先于根因。
  2. 不要单独操作生产:双人复核 + 留下记录。
  3. 不确定就停手:宁可让告警飞一会,也不要乱操作放大故障。
  4. 保留现场:重启前先 dump 日志和状态。
  5. 及时同步:值班群 5 分钟一次状态更新,让所有人知道你在做什么。
  6. 学会喊人:超过 30 分钟没进展,毫不犹豫升级到 Tech Lead。
  7. 复盘是文化不是追责:故障是系统问题,不是个人问题。