ayl-duke-dev-28/incident-response

GitHub: ayl-duke-dev-28/incident-response

一个基于 Claude 和 FastAPI 的自主事件响应系统,在告警触发后几秒内完成 AI 分诊、自动修复和 Slack 简报生成,并在事件解决时自动输出复盘报告。

Stars: 0 | Forks: 0

# 自主事件响应 一个在告警触发的瞬间响应生产环境宕机的 AI 系统。告警输入 → 几秒内输出 Slack 简报和自动化修复,并在事件被标记为已解决的那一刻生成复盘报告。 ## 告警时会发生什么 1. **Webhook 触发 `POST /alerts`**,携带 Datadog/PagerDuty/通用样式的 JSON payload。 2. **认证**:共享 token(`X-Webhook-Token`)*或* HMAC 签名(`X-Datadog-Signature` / `X-PagerDuty-Signature` / `X-Webhook-Signature`) — 任何一种凭证即可。 3. **速率限制**:内存中的滑动窗口限制器,以 `(client_ip, service)` 为键。 4. **去重**:告警通过 `(service, metric, severity, 15分钟时间桶)` 进行指纹识别。同一时间桶内的重复项会作为时间线条目附加到现有事件中,而不是开启新的事件。 5. `/alerts` 在个位数毫秒内返回 `202 {"status": "accepted", "incident_id": "inc-..."}`,并将告警放入后台 worker 的队列中。 6. **Worker 运行编排器**,通过 `asyncio.gather` 并行执行三项操作: - **分诊 agent** — 提取过去 90 分钟的 commits,让 Claude 根据时间、文件、消息和 diff 大小对最多 3 个可疑项进行排名,并返回每个可疑项的置信度及推理。 - **Runbook agent** — 让 Claude 从 `./runbooks/*.md` 中挑选最佳匹配的 runbook(包含标题 + 标签 + 正文第一行,不发送完整内容以节省 token);在置信度低于 0.3 时返回 `None`,以避免“过度自信但错误”的匹配。 - **影响评估 agent** — 提取错误率 + 请求率序列 + 活跃用户数,让 Claude 估算出受影响的整数用户估计值 + 峰值错误率。 - 在分诊触发之前,`history.py` 会根据 service + 关键词 + 近期程度对过往复盘报告进行评分,并将排名前 K 的结果作为 few-shot 上下文传递给分诊 prompt,**并且**作为“以往类似事件”部分传入简报。 7. **Slack 简报**组合完成(确定性模板,无 LLM)并发布到 incidents 频道。每当检索器找到命中结果时,会包含一个“以往类似事件”区块,其中包含日期、复盘报告链接、相似度得分和根因片段 —— 这样on-call 工程师无需打开 wiki 就能看到“我们以前修过这个问题,方法如下”。 8. **Runbook 修复** — 如果匹配的 runbook 声明了一个 `## Automated actions` JSON 区块,白名单内 `auto: true` 的步骤将通过 shell 执行器运行,并作为 threaded reply 发布结果。默认的 `REMEDIATION_MODE=mock` 永远不会触碰系统。 9. **事件状态**在每一步之后都会持久化到 SQLite,因此在中途重启不会丢失任何内容。 当有人触发 `POST /alerts/{id}/resolve` 时: - Claude 会生成一份**无指责的复盘报告**(摘要 → 影响 → 时间线 → 根因 → 检测 → 缓解 → 带有 P0/P1/P2 的行动项 → 经验教训)并保存至 `./postmortems/YYYY-MM-DD-inc-*.md`。 - 一条 Slack threaded reply 会将复盘报告链接回原始简报。 外部调用(Anthropic、GitHub REST、Slack webhook、Datadog)均通过带有 jitter 的指数退避重试进行封装。每个集成在相同的接口背后都有一个 `Mock*` 和一个真实客户端 —— 整个流程可离线运行并进行测试。 ## 端点 | 方法 | 路径 | 用途 | |---|---|---| | `POST` | `/alerts` | 将事件加入队列。返回 `202 {status, incident_id}`。 | | `POST` | `/alerts/{id}/resolve` | 标记为已解决;生成复盘报告;thread reply。返回完整事件。 | | `GET` | `/incidents/{id}` | 当前状态(状态、分诊报告、时间线、复盘报告路径)。 | | `GET` | `/healthz` | 存活检测。 | | `GET` | `/readyz` | 存活检测 + 当前 worker 队列深度。 | ## 快速开始 ``` python -m venv .venv && source .venv/bin/activate pip install -e ".[dev]" # or ".[dev,otel]" for OpenTelemetry export cp .env.example .env # set ANTHROPIC_API_KEY at minimum uvicorn incident_response.main:create_app --factory --reload --port 8080 ``` 触发一个测试告警: ``` curl -X POST http://localhost:8080/alerts \ -H "x-webhook-token: change-me" \ -H "content-type: application/json" \ -d '{ "id": "ddg-9273", "title": "Checkout 5xx > 5%", "service": "checkout", "severity": "sev2", "triggered_at": "2026-07-02T21:05:00+00:00", "metric": "http.error_rate", "value": 0.184 }' # → 202 {"status":"accepted","incident_id":"inc-ddg-9273"} ``` 分诊完成后(通常需要 2–5 秒)获取事件: ``` curl http://localhost:8080/incidents/inc-ddg-9273 ``` 解决并自动生成复盘报告: ``` curl -X POST http://localhost:8080/alerts/inc-ddg-9273/resolve \ -H "x-webhook-token: change-me" \ -H "content-type: application/json" \ -d '{"resolution_note": "rolled back a1b2c3d"}' ``` 复盘报告会生成在 `./postmortems/YYYY-MM-DD-inc-ddg-9273.md`。 ## 对接真实系统 在 `.env` 中切换模式环境变量: | 环境变量 | 模式 | 说明 | |---|---|---| | `GITHUB_MODE` | `mock`, `rest` | `rest` 需要 `GITHUB_TOKEN` + `GITHUB_REPO` | | `SLACK_MODE` | `mock`, `webhook`, `bot` | `webhook` 需要 `SLACK_WEBHOOK_URL`;`bot` 需要 `SLACK_BOT_TOKEN` 并允许通过 `chat.update` 实现流式简报更新 | | `METRICS_MODE` | `mock`, `datadog` | `datadog` 需要 `DATADOG_API_KEY` + `DATADOG_APP_KEY` | | `REMEDIATION_MODE` | `mock`, `shell` | `shell` 仅运行在 `REMEDIATION_ALLOWED_COMMANDS` 中且带有 `auto: true` 的步骤 | 添加其他提供商(GitLab、Prometheus、ServiceNow)只需在 `integrations/` 中增加一个新类,并在对应的 `build_*_client` 工厂中加入一行代码。 ## Runbook 库 位于 `./runbooks/` 中的 Markdown 文件。Frontmatter 驱动搜索;一个可选的 JSON 区块用于声明修复步骤。 ``` --- title: Checkout service elevated error rate tags: [checkout, http_5xx] --- ## 首要操作 1. Confirm the alert in Datadog. 2. Check the last 3 deploys... ## 自动化操作 ```json [ {"name": "flip pricing cache off", "command": "feature-flag set checkout.pricing_cache off", "auto": true}, {"name": "rollback last deploy", "command": "deploy rollback checkout --confirm"} ] ``` ``` - 只有带有 `"auto": true` **且** 第一个 token 存在于 `REMEDIATION_ALLOWED_COMMANDS` 中的步骤才会执行。 - 其他所有内容都会被报告为 `skipped_not_auto` / `skipped_not_allowed` —— 完整的审计跟踪,杜绝静默跳过。 - 默认的 `REMEDIATION_MODE=mock` 总是返回 `dry_run` —— 可以安全地指向 runbook 库而无需先开启执行权限。 - 结果(已执行、dry_run、已跳过、已失败)会作为 threaded Slack reply 发布在事件简报中。 ## 生产环境加固 | 关注点 | 所在位置 | |---|---| | 重试/退避(外部调用) | `retry.py` — Anthropic、REST GitHub、Slack webhook、Datadog 上的装饰器 | | 包含 `incident_id`/`trace_id` 关联的结构化 JSON 日志 | `logging_config.py` — 基于 ContextVar,formatter 自动附加 | | OpenTelemetry(可选) | `telemetry.py` — 如果设置了 `OTEL_EXPORTER_OTLP_ENDPOINT`,则自动为 FastAPI + httpx 植入监控,否则无操作 | | HMAC webhook 认证 | `security.py` — Datadog (base64)、PagerDuty (multi-key `v1=`)、通用 (hex) | | 速率限制 | `rate_limit.py` — 基于 `(ip, service)` 的滑动窗口,可注入时钟 | | 异步 worker(快速 202) | `queue.py` — `asyncio.Queue` + 通过 FastAPI lifespan 启动/停止的 worker | | 告警去重 | `dedup.py` — 带有 TTL 的有界 LRU,每个 `(service, metric, severity, bucket)` 对应一个指纹 | | Runbook 修复 | `executor.py` — 默认为 dry-run,`ShellExecutor` 白名单可选开启 | | 用于分诊 + 简报的历史复盘 RAG | `history.py` — service + 关键词 + 近期程度评分;命中结果会反馈给分诊 prompt,**并且**作为带有复盘链接的“以往类似事件”区块出现在 Slack 简报中 | | 流式 Slack 简报更新 | `agents/brief.py::compose_streaming_brief` + `_stream_triage` — 初始为占位符,然后在每个 agent 完成时执行 `chat.update`(仅限 bot-token 模式) | | 修复后验证循环 | `verification.py` — 在自动执行后轮询错误率,将已恢复 / 正在改善 / 仍然偏高的情况发布到事件 thread 中 | ## 测试 ``` pytest ``` **80 个测试,无需网络。**全程使用 `FakeLLM` 和 mock 适配器。 ## 布局 ``` src/incident_response/ main.py FastAPI app, lifespan-managed worker, auth + rate limit + dedup orchestrator.py Alert → dedup → triage → brief → remediate → resolve → post-mortem models.py Immutable pydantic domain models (frozen where safe) db.py SQLite persistence (one JSON blob per incident) config.py Env-driven pydantic-settings runbooks_loader.py Markdown frontmatter parser retry.py Exponential-backoff-with-jitter decorator logging_config.py JSON logs with ContextVar correlation telemetry.py Optional OpenTelemetry setup (guarded imports) security.py Datadog / PagerDuty / generic HMAC verification rate_limit.py Sliding-window in-memory limiter dedup.py Fingerprinting + bounded TTL LRU queue.py In-process async worker executor.py Runbook remediation (mock + allow-listed shell) history.py Post-mortem RAG (keyword + service + recency scoring); feeds triage prompt + brief verification.py Post-remediation recovery loop agents/ llm.py Anthropic wrapper (retried) + FakeLLM for tests triage.py Suspect-commit ranking runbook.py Runbook selection impact.py User-impact estimate brief.py Deterministic Slack brief composer postmortem.py LLM post-mortem generator with deterministic fallback integrations/ github.py Recent commits (Mock + REST, retried) slack.py Post + thread (Mock + Webhook, retried) metrics.py Error rate / rps / active users (Mock + Datadog, retried) tests/ pytest suite (80 tests, no network) runbooks/ Example markdown runbooks (checkout, redis, auth) postmortems/ Written at resolve time ``` ## 运维人员注意事项 - **重启安全性**:SQLite 会在每一步之后写入,但内存队列*并不*具备持久性 —— 在分诊过程中强行终止会丢失排队中的告警。可将 `queue.py` 替换为 arq/Celery/Dramatiq(相同的接口)以实现至少一次的持久性。 - **多实例**:速率限制器和去重索引都在内存中。在水平扩展时,请使用 Redis 为其提供后端支持。 - **成本控制**:每个事件三次 Claude 调用(分诊、runbook、影响评估),加上解决时的一次调用(复盘报告)。按照 Sonnet 4.6 的定价,根据 runbook 库的大小,每个事件的成本约为 ~$0.05–0.15。 - **复盘报告生成器的故障模式**:如果 Claude 返回格式错误的 JSON,`agents/postmortem.py` 会回退到确定性模板,而不是完全丢弃复盘报告。 - **尚未实现的功能**:不支持跨服务的事件合并、无 on-call 轮换查询、无 Jira/Linear 工单创建、无针对可疑 commit 的 PR 批注。以上每一项都可以作为现有适配器模式背后独立的集成来实现。
标签:AIOps, 事故管理, 告警响应, 安全规则引擎, 用户代理, 自动化运维, 计算机取证, 逆向工具