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工作流, 数据可视化, 自动化运维