0.前言
当AI的浪潮席卷整个互联网行业以后,作为一个10年工作经验的老运维,说实话,迷茫了很久,当得知AI可以写代码,可以测试,可以操作windows,可以操作Linux之后,一开始是抗拒和恐惧。AI如此强大,那我们这些平平无奇的工程师该何去何从,但是当真正使用AI之后,发现其实我们可以把AI当成一个工作或者生活中的帮手,让AI给我们一些合理的建议,或者让AI在合理的边界内完成一些工作,以此提高我们的工作效率。那么今天我们就通过AI来搭建一个AI告警分析管道,帮助我们排查系统故障的具体原因。
1.为什么做这个
其实没有什么特别的原因,主要是因为我是一个SRE,对这个比较了解,AI如果跑偏了,可以把AI拉回来,嘿嘿。
2.架构总览
┌──────────────┐ ┌───────────────┐ ┌──────────────┐ ┌──────────────┐
│ Prometheus │────▶│ Alertmanager │────▶│ Go Webhook │────▶│ Ollama │
│ (告警规则) │ │ (路由分发) │ │ :9091 │ │ qwen2.5:7b │
└──────────────┘ └───────────────┘ └──────┬───────┘ └──────────────┘
│
┌─────────────┼─────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ MySQL │ │ 企业微信 │ │ Prometheus│
│ 持久化 │ │ 通知 │ │ /metrics │
└──────────┘ └──────────┘ └──────────┘
核心链路:Prometheus 按规则触发告警 → Alertmanager 路由到 Go Webhook → Webhook 异步调用 Ollama 做根因分析 → 结果写 MySQL + 推企业微信 + 暴露 Prometheus 指标供 Grafana 可视化。
3.部署详情
| IP | 操作系统 | 作用 |
|---|---|---|
| 192.168.62.1 | windows | 部署ollama,webhook |
| 192.168.62.146 | Linux | 部署监控告警平台 |
因为是测试,所以没有使用特别多的资源,大模型用的是qwen2.5:7b,还是靠的CPU推理,大家可以在该基础上提高配置,应该能得到更快更准确的答案。
4.小试牛刀log_classfier
在整体运行整个监控告警系统之前,先测试下ollama+qwen2.5:7b的效果,于是基于go语言写了一个log_classfier程序,读取日志文件,然后根据日志文件信息推测异常原因,核心代码如下:
const systemPrompt = `你是一个资深的运维/SRE告警分析专家。用户会给你一条系统告警日志,请判断它属于哪个类别、评估严重等级,并给出一句话原因。
要求:
1. 只输出一个 JSON 对象,不要任何多余的文字、不要 markdown 代码块标记。
2. 字段定义:
- "category":类别,只能从以下枚举中选一个:磁盘存储 / CPU内存 / 网络 / 应用错误 / 数据库 / 安全 / 中间件 / 其他
- "severity":严重等级,只能从 critical / warning / info 中选一个
- "reason":一句话说明为什么归到这个类别(中文,不超过30字)
3. 各类别精确边界(务必严格区分,不要按关键词惯性归类):
- 磁盘存储:仅宿主机磁盘使用率/空间超标(如 disk usage 95%、No space left on device)。
- CPU内存(系统资源):仅宿主机层面的 CPU/内存/磁盘IO 指标超标,如 load average 过高、内存占用率%、swap 耗尽。注意:进程内的堆/栈溢出不算此类。
- 网络:网络连通性、延迟、丢包、DNS、端口不通(非特定中间件实例的连接超时)。
- 应用错误:应用程序自身异常——OutOfMemoryError/heap/stack 溢出、panic、exception、业务报错、5xx、进程崩溃重启。只要异常发生在某个服务进程内(带 PID/服务名),就选此类,而不要选 CPU内存。
- 数据库:关系型/主存储的异常,仅限 MySQL / PostgreSQL / Oracle / MongoDB 等的慢查询、死锁、连接数满、主从延迟。
- 中间件:缓存/消息队列/网关的异常,仅限 Redis / Kafka / RabbitMQ / Nginx / Elasticsearch 等的连接超时、拒绝、集群异常。Redis 相关问题一律归此处,不要归数据库。
- 安全:认证失败、暴力破解、越权访问、漏洞扫描、异常登录。失败的暴力破解尝试给 warning;登录成功或已入侵给 critical。
- 其他:以上都不匹配的兜底类。
4. 铁律(优先级最高,违反即判错):
- 见到 "OutOfMemory/heap/panic/exception" 且带 PID 或服务名 → 应用错误,绝不选 CPU内存。
- 见到 "redis/kafka/mq/nginx/elasticsearch" 的连接类问题 → 中间件,绝不选 数据库。
- 仅当是宿主机的 load average / 内存占用率% / 磁盘占用率% 数值超标 → 才选 CPU内存。
5. 示例输出:{"category":"磁盘存储","severity":"warning","reason":"根分区使用率超过阈值"}`
.........
.........
// classify 调用本地 ollama 的 chat 接口,把一条日志归为一类
func classify(logLine, model, baseURL string) (result, error) {
reqBody := ollamaReq{
Model: model,
Stream: false,
Format: "json",
Temperature: 0,
Messages: []chatMsg{
{Role: "system", Content: systemPrompt},
{Role: "user", Content: logLine},
},
}
data, _ := json.Marshal(reqBody)
req, err := http.NewRequest("POST", baseURL+"/api/chat", bytes.NewReader(data))
if err != nil {
return result{}, err
}
req.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 120 * time.Second}
resp, err := client.Do(req)
if err != nil {
return result{}, err
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
var or ollamaResp
if err := json.Unmarshal(body, &or); err != nil {
return result{}, fmt.Errorf("解析响应失败: %s, 原始: %s", err, string(body))
}
var r result
if err := json.Unmarshal([]byte(or.Message.Content), &r); err != nil {
// 退化:模型没返回纯 JSON,把原文塞进 reason 返回,不丢数据
return result{Category: "解析失败", Severity: "info", Reason: or.Message.Content}, nil
}
return r, nil
}
............
以上代码的主要功能就是定义prompt约束模型的输出边界:固定类别枚举 + 强制 JSON 输出,以及调用ollama大模型的业务逻辑,经过反复测试,加上AI配合做代码优化,很快就可以得出比较正确的异常原因和修复建议。
5.真实的监控告警
接下来就可以部署prometheus+alertmanager来模拟真实的告警场景了,其中prometheus负责采集监控数据,alertmanager负责告警,并将告警信息推送给webhook,之后webhook根据告警内容,基于大模型的分析,判断异常原因并给出处理建议,为了统计常见的告警,还将历史异常数据持久化处理,webhook核心代码如下:
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
)
const analysisPrompt = `你是一个资深的 SRE/AIOps 根因分析专家。用户会给你一条 Prometheus 告警,请完成:
1. 归类(类别 + 严重等级)
2. 分析最可能的根因(2-3条,按可能性从高到低排列)
3. 给出建议的排查/修复步骤(2-3条可执行的操作)
要求:
1. 只输出一个 JSON 对象,不要任何多余文字,不要 markdown 代码块。
2. 字段定义:
- "category":从以下枚举选一:磁盘存储 / CPU内存 / 网络 / 应用错误 / 数据库 / 中间件 / 安全 / 其他
- "severity":critical / warning / info
- "summary":一句话概括这个告警(中文,不超过40字)
- "root_causes":字符串数组,2-3条最可能的根因,每条不超过30字
- "actions":字符串数组,2-3条可执行的建议操作,每条不超过30字
3. 类别边界:
- 磁盘存储:仅宿主机磁盘使用率/空间超标
- CPU内存:仅宿主机 load/mem%/disk% 指标超标。进程内 OOM/heap → 应用错误
- 应用错误:进程内异常(OutOfMemoryError/panic/exception/5xx/崩溃重启),带 PID/服务名
- 数据库:MySQL/PG/Oracle/Mongo 慢查询/死锁/连接满
- 中间件:Redis/Kafka/RabbitMQ/Nginx/ES 连接/拒绝/集群问题
- 安全:认证失败/爆破/越权/扫描
- 网络:连通性/延迟/丢包/DNS/端口不通
4. 示例输出:
{"category":"CPU内存","severity":"critical","summary":"db-master-01 CPU负载18.5,超过阈值8.0已达5分钟","root_causes":["存在慢查询或全表扫描拖高CPU","业务高峰期并发请求激增","节点上其他容器争抢CPU资源"],"actions":["登录节点执行 top 查看 Top 进程","检查数据库慢查询日志确认瓶颈SQL","若为周期性突发考虑临时扩容或限流"]}`
func analyzeWithOllama(alertText, model, baseURL string) (analysisResult, string, error) {
reqBody := ollamaReq{
Model: model,
Stream: false,
Format: "json",
Temperature: 0,
Messages: []chatMsg{
{Role: "system", Content: analysisPrompt},
{Role: "user", Content: alertText},
},
}
data, _ := json.Marshal(reqBody)
req, err := http.NewRequest("POST", baseURL+"/api/chat", bytes.NewReader(data))
if err != nil {
return analysisResult{}, "", err
}
req.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 180 * time.Second}
resp, err := client.Do(req)
if err != nil {
return analysisResult{}, "", err
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
var or ollamaResp
if err := json.Unmarshal(body, &or); err != nil {
return analysisResult{}, "", fmt.Errorf("解析 Ollama 响应失败: %s, 原始: %s", err, string(body))
}
rawContent := or.Message.Content
var ar analysisResult
if err := json.Unmarshal([]byte(rawContent), &ar); err != nil {
return analysisResult{
Category: "解析失败",
Severity: "info",
Summary: rawContent,
}, rawContent, nil
}
return ar, rawContent, nil
}
func formatAlert(a alertItem) string {
alertName := a.Labels["alertname"]
if alertName == "" {
alertName = "未知告警"
}
severity := a.Labels["severity"]
instance := a.Labels["instance"]
job := a.Labels["job"]
desc := a.Annotations["description"]
if desc == "" {
desc = a.Annotations["summary"]
}
if desc == "" {
desc = "无描述"
}
return fmt.Sprintf("[%s][%s] %s | 实例: %s | 任务: %s\n描述: %s\n状态: %s | 开始时间: %s",
severity, alertName, alertName, instance, job, desc, a.Status, a.StartsAt)
}
其中prompt是可以持续优化的,可以让大模型更加明确自己的职责,已经异常的真正原因。
6.告警通知
当出现告警,大模型分析完成之后,会自动推送给企微,告警信息模板如下:
🚨 AIOps 告警通知
> 告警名称: HighCPUUsage
> 状态: 🔥 触发
> 等级: critical
> 实例: 192.168.62.146:9100
> 时间: 2026-08-06T18:30:00+08:00
AI 分析结果
> 类别: CPU内存 | 等级: critical
> 概要: master节点CPU使用率持续超过80%
可能根因
> 1. kube-apiserver 处理大量请求导致CPU飙升
> 2. 节点上运行的Pod异常消耗CPU资源
> 3. etcd 写入压力传导至 API Server
建议操作
> 1. ssh 登录节点执行 top 定位高CPU进程
> 2. kubectl top nodes 和 kubectl top pods 对比资源消耗
> 3. 检查 kube-apiserver 审计日志确认请求来源
---
AIOps 智能告警分析系统
resolved(告警恢复)事件走另一条路径:更新数据库状态 + 发简洁恢复通知,不调 AI——恢复事件不需要分析,省一次推理。
7.总结
因为是第一次尝试,代码使用的go标准库+ollama+MySQL,后续会采用更加合理的方式开发,比如使用Eino框架,而日志采集也会使用更加真实复杂的业务场景,虽然这次代码写的很乱,但还是上传到了github:仓库地址,希望后续自己可以给大家分享更有意思的项目,那么今天就到这里了。
