ghost-bw/AutoIncident-Bot
GitHub: ghost-bw/AutoIncident-Bot
基于 n8n 的事件驱动 DevOps 工作流,通过 LLM 自动诊断 CI/CD 失败根因并创建去重 Jira 工单,将事件响应时间压缩到一分钟以内。
Stars: 0 | Forks: 0
# 🤖 DevOps 事件响应自动化 — n8n 工作流
## 📌 概述
这个 n8n 工作流为失败的部署/CI 运行实现了**完整的事件响应生命周期**自动化。一旦构建或部署失败,该工作流就会自动触发——无需任何人手动操作任何工具——它会:
1. 下载并清理失败日志。
2. 将日志发送给 AI 模型 (Groq) 以诊断**根本原因**。
3. 将事件永久存储到数据库 (Supabase) 中。
4. 搜索过去的事件,查看以前是否发生过完全相同的问题。
5. 检查该运行是否**已经存在** Jira ticket,以避免重复。
6. 要么创建一个新的 Jira ticket,要么复用现有的。
7. 在这两条路径上——无论是一个全新的事件还是已经在跟踪中的事件——都通过 **Slack 和 Email** 通知团队。
核心动机:缩短从“某些东西坏了”到“合适的人正在查看正确的信息”之间的时间,同时避免针对同一故障发出嘈杂、重复的警报。
## 🧩 高层架构
```
Webhook (CI/CD failure trigger)
└─▶ If (is this a real failure?)
├─(false)──▶ (stops, no action)
└─(true)───▶ HTTP Request ──▶ Wait
│
(loops back into the log pipeline)
▼
Download Logs
▼
Compression (decompress)
▼
Code in JavaScript (clean logs)
▼
AI Agent (Groq Chat Model + Memory + Tool)
▼
Parse AI Output
│
┌───────────────────────────┼────────────────────────────┐
▼ ▼ ▼
SupabaseNode Search Similar Incidents Check Existing Incidents
(store incident) ▼ ▼
Incidents Summary Code in JavaScript2
▼ ▼
Incident Summary Parse If1
┌─────┴─────┐
true false
▼ ▼
Jira Ticket Exists
▼ ▼
Merge Jira Key Send a message3 (Slack)
▼ ▼
HTTP Request1 Send a message2 (Email)
▼
Send a message (Slack)
▼
Send a message1 (Email)
```
**与先前版本的关键更新:** `If1` 的两个分支现在都会通过 **Slack 和 Email** 通知团队,而不仅仅是各自使用单一渠道。无论是创建了新 ticket 还是复用了现有 ticket,团队都能获得同等的可见性。
## 🗂️ 逐节点说明
### Webhook
- **类型:** Webhook (触发器)
- **方法:** POST
- **目的:** 整个工作流的入口。GitHub Actions 监听器(或监听 `workflow_run` / `check_run` / `deployment_status` 事件的仓库级 GitHub webhook)会在构建/部署失败的那一刻调用此 URL。
- **重要性:** 使整个流水线变为事件驱动且即时——无需任何人手动去发现故障。
### If
- **类型:** IF (条件)
- **条件:** `{{$json.body.status}}` 等于 `failed`
- **目的:** 过滤传入的 payload,使工作流仅针对**实际的失败**继续执行。
- **True 分支:** 继续进入事件流水线。
- **False 分支:** 进入死胡同——成功的运行将被忽略,从而降低噪音。
### HTTP Request
- **类型:** HTTP Request
- **目的:** 发起与失败运行相关的调用——例如,请求 CI 提供商准备/生成此运行的可下载日志归档。
- **重要性:** 某些 CI/日志 API 需要此“触发”步骤,然后实际的日志文件才可供下载。
### Wait
- **类型:** Wait
- **目的:** 简要暂停,为日志生成留出时间在工作流尝试下载文件之前完成——防止出现竞态条件。
- **循环回溯:** 其输出路由回以 `Download Logs` 开始的日志收集分支。
### 下载 Logs
- **类型:** HTTP Request (GET)
- **目的:** 下载失败运行的实际日志归档(通常是来自 GitHub Actions API 的 `.zip` 文件)。
### 6. `Compression` *(操作: Decompress)*
- **类型:** Compression
- **目的:** 解压下载的日志归档,以便后续节点可以读取原始日志文本。
- **关键设置:** `Input Binary Field = data`, `Output Prefix = data`。
### Code in JavaScript
- **类型:** Code (JavaScript)
- **目的:** 清理原始日志文本——剥离 ANSI 颜色,仅提取有意义的行(错误、异常、超时、失败),并限制长度,以免其溢出 AI 模型的上下文窗口。
- **重要性:** 在到达 LLM 之前从噪音中提取信号,保持诊断的快速、低成本和准确。
### 8. `AI Agent` (附带 `Groq Chat Model`, `Memory`, `Tool`)
- **类型:** AI Agent 节点
- **子组件:**
- **Groq Chat Model** — 执行推理的 LLM 后端(通过 Groq 进行快速推理)。
- **Memory** — 让代理在各个推理步骤之间保留上下文。
- **Tool** — 赋予代理能力,如果它在最终确定诊断之前需要更多数据,可以调用外部工具/函数。
- **目的:** 工作流的“大脑”——提取清理后的错误日志并生成结构化的根本原因诊断:什么坏了、严重程度如何、模型的置信度如何,以及建议的修复方法是什么。
### Parse AI Output
- **类型:** Code / Set (JSON 解析)
- **目的:** AI Agent 将其诊断作为 JSON *字符串* 返回(通常包裹在 Markdown 代码块中)。该节点剥离代码块并将其解析为实际字段——`root_cause`、`severity`、`confidence`、`impact`、`suggested_fix`、`preventive_action`、`estimated_resolution_time`。
- **这是扇出点:** 其输出馈送到三个独立的分支——存储事件、搜索过去相似的事件以及检查是否已存在 Jira ticket。
### SupabaseNode
- **类型:** HTTP Request (POST 到 Supabase REST API)
- **目的:** 将此事件(根本原因、严重程度、仓库、run_id、时间戳等)永久记录到 Supabase (Postgres) 表中——这是未来“相似事件”搜索和仪表板的记录系统。
### 搜索相似 Incidents
- **类型:** HTTP Request (向 Supabase 发起 GET/POST)
- **目的:** 查阅历史事件记录以寻找具有相似根本原因的过去问题,这样团队就不必重新去发现已经存在的修复方法。
### Incidents 摘要
- **类型:** Code (JavaScript)
- **目的:** 将原始的过去相似事件列表浓缩成简短的、人类可读的摘要。
### Incident Summary Parse
- **类型:** Code (JavaScript)
- **目的:** 将摘要文本格式化为最终形态——即稍后作为 `jiraDescriptionExtra` 注入到 Jira ticket 描述中的确切字符串,从而避免 Jira 节点内部出现 JSON 转义问题。
### 检查现有 Incidents
- **类型:** HTTP Request (Supabase 查询)
- **目的:** **去重检查**——在创建新的 Jira ticket 之前,查询事件表中是否已存在具有相同 `run_id` 的记录。
- **重要性:** 如果没有这一步,重试的 webhook(例如 GitHub 重新发送失败事件)每次都会创建一个重复的 Jira ticket。这正是使工作流具有幂等性的关键。
### Code in JavaScript2
- **类型:** Code (JavaScript)
- **目的:** 解释 `Check Existing Incidents` 的结果——将其规范化为可供 `If1` 进行分支的简单布尔值/标志,并在找到现有的 Jira key 时将其保留传递。
### If1
- **类型:** IF (条件)
- **条件:** 该 `run_id` 是否已经存在 Jira ticket?
- **True 分支 →** 没有现有 ticket → 创建一个 (`Jira`)。
- **False 分支 →** ticket 已存在 → 跳过创建,使用现有的 ticket key 进行通知 (`Ticket Exists`)。
### Jira
- **类型:** Jira Software (Create Issue) — 实现为直接针对 Jira REST API 的 HTTP Request POST。
- **目的:** 创建一个新的 Jira ticket,其中包含 AI 的根本原因分析、严重程度、建议的修复方法以及过去相似事件的摘要 (`jiraDescriptionExtra`)。
- **Body:** 直接引用预计算的描述字符串,而不是在行内拼接/转义引号,从而保持 JSON body 有效。
### 合并 Jira Key
- **类型:** Code / Set
- **目的:** 从 Jira 的 API 响应中提取新创建的 Jira issue key,并将其合并回数据对象中,以便在之后发送的通知消息中使用。
### HTTP Request1
- **类型:** HTTP Request
- **目的:** 与新创建的 ticket 相关的附加 API 调用——例如,更新自定义字段、将 ticket 链接到 commit/PR,或将日志文件附加到 Jira issue 中。
### 20. `Send a message` (Slack — 新 ticket 路径)
- **类型:** Slack (Post Message)
- **目的:** 向 Slack 频道(例如 `#devops-alerts`)发布消息,宣布创建了**新**的事件 ticket,包括 Jira key 和根本原因摘要。
### 21. `Send a message1` (Email — 新 ticket 路径)
- **类型:** Email/Gmail
- **目的:** 发送确认新 ticket 的电子邮件通知,为没有活跃关注 Slack 的人同步 Slack 警报。
### Ticket Exists
- **类型:** Code / Set
- **目的:** 为“重复”路径准备通知 payload——引入**现有的** Jira key,从而让团队知道“此事已经在跟踪中”,而不是收到令人困惑的重复警报。
### 23. `Send a message3` (Slack — 现有 ticket 路径)
- **类型:** Slack (Post Message)
- **目的:** 在 Slack 上发帖,让团队知道此故障与一个已经开启的事件相匹配,并链接到现有的 Jira ticket。
### 24. `Send a message2` (Email — 现有 ticket 路径)
- **类型:** Email/Gmail
- **目的:** 针对“ticket 已存在”路径的并行电子邮件通知,同步 `Send a message1` 的内容,但指向现有的 ticket。
## 🔁 为什么工作流在 `Parse AI Output` 之后分叉成两条路径
| 路径 | 目的 |
|---|---|
| **Supabase / 搜索过去相似事件** | 历史记录保存并为 AI/团队提供上下文(“我们以前见过这个吗?”) |
| **检查现有事件 → If1** | 当同一个失败事件触发多次时(例如 webhook 重试),防止创建重复的 Jira ticket |
这两条路径独立运行——一条是为了**从历史中学习**,另一条是为了**不用冗余的 ticket 骚扰团队**。
## 🔔 为什么现在每个分支都同时发送 Slack *和* Email
该工作流的早期版本会根据触发的分支发送仅限 Slack 或仅限 Email 的消息。这造成了可见性不一致——只看邮件的人可能会错过一个全新的事件,而只看 Slack 的人可能会错过关于旧事件相匹配的确认。现在,两个分支都通过**这两个渠道**进行通知,因此无论 ticket 是新建的还是复用的,任何团队成员的默认渠道都不会成为盲区。
## 🛰️ 失败是如何实际到达 Webhook 的
此工作流需要一个具有规范化结构的 JSON payload:
`repository`, `workflow`, `run_id`, `run_number`, `branch`, `commit`, `actor`,`status`, `event`, `run_url`。
有两种受支持的方法可以自动将失败转化为这种格式:
### 方法 A — 每个工作流的监听器 (`workflow_run` 触发器)
每个仓库包含一个小型的 `.github/workflows/incident-listener.yml` 文件,用于监听特定的工作流名称:
```
on:
workflow_run:
workflows: ["ResumePro CI"]
types: [completed]
```
设置简单,但需要显式列出每个 CI 工作流名称,并且在创建新流水线时需要重新添加名称。
### 方法 B — 仓库级 GitHub Webhook(推荐,通用)
在仓库的 **Settings → Webhooks** 下配置一次,订阅:
`Workflow runs`, `Statuses`, `Check runs`, `Check suites`, 以及 `Deployment statuses`。这会自动监视仓库中的**每一个**工作流/CI 工具/部署集成——无需 YAML 文件,无需名称列表,以后添加新流水线时也无需进行编辑。需要在 n8n 中使用一个 `Switch` 节点(在签名验证之后)根据 `X-GitHub-Event` 标头进行分支,并将每种事件类型不同的 payload 结构规范化为流水线其余部分所期望的相同字段。
## 🛠️ 前置条件 / 所需凭证
| 凭证 | 使用方 |
|---|---|
| GitHub PAT | `Download Logs`, `HTTP Request` |
| GitHub Webhook Secret | 对传入的 webhook payload 进行签名验证 |
| Groq API Key | `Groq Chat Model` (在 AI Agent 内部) |
| Supabase URL + Service Key | `SupabaseNode`, `Search Similar Incidents`, `Check Existing Incidents` |
| Jira API Token | `Jira`, `HTTP Request1` |
| Slack Bot Token | `Send a message`, `Send a message3` |
| Gmail/SMTP 凭证 | `Send a message1`, `Send a message2` |
## ⚠️ 已知问题及已应用的修复
- **Webhook 重试时出现重复的 Jira ticket** —— 通过在创建 ticket 之前添加 `Check Existing Incidents` → `Code in JavaScript2` → `If1` 去重闸门。
- **固定/模拟数据警告** —— 确保在生产运行之前清除 `Jira` 和 Code 节点上的固定测试数据。
- **重试期间 Supabase 插入出现 409 错误** —— 通过对事件表使用 **upsert**(插入或更新)而不是普通的 insert 来解决,这样重试的 webhook 会更新现有行,而不是因主键冲突而报错。
- **不一致的通知覆盖范围** —— 通过在新建 ticket 和现有 ticket 分支上同时添加 Slack **和** Email(而不是每个分支仅一个渠道)来解决。
## 🚨 错误处理
此工作流与一个独立的 **`Error Handler - Global`** 工作流配对,通过 **Workflow Settings → Error Workflow** 附加。如果这里的任何节点意外失败(认证失败、超时、速率限制等),该工作流将自动触发并向专门的 `#n8n-errors` Slack 频道发布警报——从而确保这种自动化的中断不会悄无声息地失败。
## ✅ 总结
该工作流将原始的 CI/CD 失败信号转化为经过全面诊断、去重并清晰沟通的事件——而无需人工去挖掘日志、编写 ticket 或决定通知谁。AI Agent 负责诊断,Supabase 作为事件记忆,`If1` 去重闸门让 Jira 摆脱重复噪音,现在两个通知分支都能让整个团队——Slack 和电子邮件 alike——获得同等充分的信息。
标签:AI运维, n8n工作流, 数据可视化, 自动化运维