Skip to content

29|部署与降级:上线要考虑什么

ops-assistant 的核心功能完整了。但本机能跑不代表能放值班入口。

服务上线后,至少要处理五件事:进程托管、健康检查、可观测性、成本控制、异常降级。本篇是系列最后一篇,讲这些上线必须考虑的事。

进程托管

服务不能靠 nohup 挂着,要交给进程管理器。systemd 是 Linux 上最常用的:

ini
# /etc/systemd/system/ops-assistant.service
[Unit]
Description=Ops Assistant API
After=network.target

[Service]
User=ops-assistant
WorkingDirectory=/opt/ops-assistant
EnvironmentFile=/etc/ops-assistant.env
ExecStart=/usr/bin/uv run uvicorn app.main:app --host 0.0.0.0 --port 8000
Restart=always
RestartSec=5

NoNewPrivileges=true
PrivateTmp=true

[Install]
WantedBy=multi-user.target

Restart=always 让进程崩溃后自动重启。/etc/ops-assistant.env 权限设 600,避免 Key 被普通用户读。NoNewPrivilegesPrivateTmp 是基本的进程隔离。

容器化固定运行环境。本机能跑换机器缺依赖是最常见的上线问题。Dockerfile 里显式创建非 root 用户并切换——容器内的 root 和宿主机 root 是同一个 UID(不用 user namespace 的话),USER appuser 是必要安全实践不是可选优化。

健康检查看依赖

最小 /health 只能证明进程活着。更实用的健康检查要看依赖:

AIOps 概念图:进程托管和健康检查

python
@app.get("/health")
def health():
    checks = {
        "index_exists": Path("knowledge/indexes").exists(),
        "model_configured": bool(settings.primary_model),
        "sessions_db": check_sessions_db(),
        "mcp_connections": check_mcp_connections(),
    }
    status = "ok" if all(checks.values()) else "degraded"
    return {"status": status, "checks": checks}

Chroma 索引损坏时进程仍返回 200,深度健康检查能更早发现这种问题。degraded 状态让上游知道服务在降级运行,不是完全正常。

可观测性:服务级和 Agent 级

传统 Web 服务只看服务级指标(QPS、延迟)。agent 系统还要看 Agent 级指标,因为成本和延迟主要来自 agent 内部。

AIOps 概念图:可观测性和成本控制

text
ops_assistant_requests_total          # 总请求数
ops_assistant_request_seconds         # 请求延迟
ops_assistant_model_tokens_total      # Token 消耗
ops_assistant_model_cost_dollars      # 估算成本
ops_assistant_tool_errors_total       # 工具错误数(按工具名分)
ops_assistant_agent_turns_total       # Agent 轮数分布
ops_assistant_recovery_total          # 错误恢复次数

服务级指标回答"接口是否正常"。Agent 级指标回答"排查是否顺利"。接口 P99 升高后,再看 tool_errors_total 是否集中在某个工具,recovery_total 是否突增。

监控 agent 系统的成本和监控传统 Web 服务不同。传统服务成本主要和 QPS 相关,agent 系统成本主要和"每轮 token 消耗 × 轮数 × 模型单价"相关。两个请求同样的 QPS,一个只调 1 轮用 fast 模型,另一个调 8 轮用 primary 模型,成本可能差一个数量级。只看接口级指标会漏掉这个差异。

成本控制:reasoning tokens 计入

推理模型的账单分三块——输入 token、输出 token、推理 token(第 15 篇)。后两块经常被忽略。

python
usage = {
    "input_tokens": response.usage.input_tokens,
    "output_tokens": response.usage.output_tokens,
    "reasoning_tokens": (
        response.usage.output_tokens_details.reasoning_tokens
        if response.usage.output_tokens_details else 0
    ),
}

reasoning_tokens 藏在 output_tokens_details 里,和可见输出 token 一样按输出价计费。reasoning.effort 越高这部分越大,是成本优化最直接的杠杆——告警摘要用 low,根因分析才上 high。漏算推理 token 是成本预估偏低的常见原因。

成本控制不是上线后才看账单,是在 Runtime 里就要记录每次调用的用量。

多模型策略

模型选择写成配置,不在降级表里写死模型名。第 2 篇的 settings 在这里扩展为多模型策略:

python
class Settings(BaseSettings):
    primary_model: str = "gpt-5.5"   # 重任务:根因分析、长报告
    fast_model: str = ""             # 轻任务:告警摘要、意图分类;留空退回 primary
    fallback_mode: str = "readonly"  # 模型不可用时的兜底模式

primary_model 处理重任务,fast_model 处理轻任务。fast_model 留空时退回 primary_model——没配置降级至少不报错。真实模型名上线前以官方价格和账号权限为准,不写死。

异常降级

异常降级用三档组合,不再出现"降级到更便宜模型"却填同一个模型名的自相矛盾:

AIOps 概念图:异常降级

python
DEGRADATION_LEVELS = {
    "normal": {
        "model": settings.primary_model,
        "max_turns": 8,
        "tools": "all",
    },
    "degraded": {
        "model": settings.fast_model or settings.primary_model,
        "max_turns": 5,
        "tools": "readonly_only",
    },
    "emergency": {
        "model": None,
        "fallback_mode": settings.fallback_mode,
    },
}

降级触发条件:模型 API 连续超时 3 次降到 degraded,成本超小时预算 80% 降到 degraded,模型完全不可用降到 emergency。emergency 模式返回固定文案,保证用户知道发生了什么而不是看到 500。

阈值不是固定值,动态计算。小时成本预算的 80% 触发降级,但 80% 在凌晨低峰期太保守、告警高峰期太激进。根据历史数据算每小时平均成本,设 avg_cost * 1.5 作为动态阈值。

MCP 部署的 DNS rebinding 防护

第 24 篇讲过 DNS rebinding。MCP server 上线时这条防护不能漏——本地 stdio 调试不受影响,但改成 Streamable HTTP 远程部署后,必须校验 Origin/Host、绑定 127.0.0.1、加认证。这是真实 CVE 背后的漏洞类型,不是上线后补的细节。

工具服务和 agent 服务分开托管,故障时能分别排查。

备份恢复

至少备份这些:Runbook 知识库、检索索引、会话状态、trace、历史报告、评估用例。恢复时先恢复 Runbook 和索引(能重建),再恢复 session、trace 和报告(丢了难还原历史排查过程)。

系列收尾

到这里,ops-assistant 从一条告警出发,走完了"读资料 → 调工具 → 生成带证据报告"的完整链路,并且能上线运行。

回顾全系列的主线:设计一个好用的运维 agent,不是堆代码,是做一连串设计决策——任务怎么说清楚、模型看到什么材料、怎么一步步安全地做、怎么协作、怎么落地。每个决策都有取舍,没有银弹。单 agent 够用就别上多 agent,本地函数够用就别上 MCP,能简单就别复杂。这些判断比任何具体实现都重要,是 agent 能真正好用的关键。