日志平台我这些年搭过不少,但真正把大模型塞进告警链路,是最近这一年让我觉得最有意思的事。以前做日志收集,基本就是 Kafka 做缓冲、ELK 做存储检索、Kibana 画几个 dashboard,告警全靠正则和阈值,误报多、漏报也多。后来我把 Ollama 和 OpenClaw 加进去,做了一套带智能分析与自动处置的日志告警平台,整个系统的“手感”完全不一样了。这篇文章就把这套架构从设计到落地的过程完整记录下来,包括组件选型、Topic 设计、模型调用、Agent 编排,以及我踩过的一些坑,希望对正在做运维监控或日志平台的朋友有帮助。
这套方案适合什么样的人?如果你手里已经有一套甚至几套日志系统,但告警还在靠人肉盯屏幕,或者你刚准备从零搭一套日志平台,希望一步到位带上 AI 能力,那这篇文章都可以给你一个可参考的落地路径。我会尽量把每个环节为什么这么做讲清楚,而不是只丢出一堆配置文件。
1. 整体架构设计与选型思路
1.1 传统日志平台的瓶颈在哪里
大多数团队的第一套日志平台都是这个路子:Filebeat 采集日志推到 Kafka,Logstash 消费写入 Elasticsearch,Kibana 做展示,再配几个 rule 做关键字或者阈值告警。这套组合本身没什么问题,尤其在日志量上来之后,Kafka 的削峰填谷能力几乎是必需品,ES 的全文检索能力也让日志排查方便很多。
但真正到了告警环节,传统规则引擎的问题就暴露了。规则告警本质上是“你预先知道要报什么”,所以只能覆盖那些已经见过的、能抽象成规则的异常。比如你写了一条“日志里出现 OutOfMemoryError 就告警”的规则,那 NPE 可能漏掉,连接池耗尽可能漏掉,线上偶发的死锁可能也漏掉。更难受的是日志里大量“看起来不一样但其实同类”的异常,比如数据库慢查询、外部接口超时、上游返回异常状态码,它们没有统一的固定关键字,规则就非常难写。
另一个痛点是告警之后怎么办。传统平台走到“通知到人”就结束了,真正处理还是靠开发去看日志、查上下文、判断影响面、再决定是否重启或者回滚。这个过程耗时很长,而且严重依赖值班同学的经验。我的目标很简单:让告警链路把“日志变成结论、结论变成动作”,而不是只做消息传递。
1.2 四个组件各司其职
这个平台的核心思路是把日志链路拆成四个角色,各自负责一件事,边界尽量清晰。我一开始也想过用 Flink 做实时分析,或者直接用 ES Watcher,但后来发现把“语义理解”和“自动执行”交给大模型这一层,灵活度会高很多。
| 组件 | 在平台中承担的角色 | 解决的核心问题 |
|---|---|---|
| Kafka | 日志消息总线,承接所有日志数据 | 削峰填谷,解耦采集端与消费端,避免 ES 被打垮 |
| ELK | 日志存储、检索、可视化 | 提供快速检索和时序聚合能力,是排查问题的“数据库” |
| Ollama | 本地大模型推理服务 | 理解日志语义,聚合相似异常,生成可执行的处置结论 |
| OpenClaw | 智能体编排框架 | 连接大模型与运维工具,自动执行通知、工单、重启等操作 |
这套组合里,Kafka 和 ELK 解决的是“数据能存能查”的问题,Ollama 解决的是“日志能看懂”的问题,OpenClaw 解决的是“看懂之后能干活”的问题。四者各管一段,互不依赖,任何一层挂了都不至于让整条链路瘫痪。
1.3 一条日志的完整旅程
我直接用一条线上报错日志来走一遍流程,大家感受一下这条链路的全貌。假设应用日志里出现了一条Connection pool exhausted的异常。
- 应用服务器上的 Filebeat 读取日志文件,识别到这是一条异常日志,把它序列化成 JSON,发送到 Kafka 的
app-logTopic。 - Logstash 从 Kafka 消费这条日志,做 grok 解析、字段类型转换、时间标准化,然后写入 Elasticsearch。
- 与此同时,一个独立分析消费者也会从 Kafka 拿到这条日志,但它不急着写 ES,而是进入一个“异常候选队列”。
- 分析服务把最近 1 分钟内同类异常的上下文聚合起来,调用 Ollama 上的本地大模型做语义判断,输出一个结构化结论:“连接池耗尽,推测是数据库连接未释放,影响订单服务,建议扩容或重启连接池”。
- 这个结论传给 OpenClaw,OpenClaw 根据预设的 Skill 找到对应的处置动作,发通知、创建工单、触发重启或者调用预案接口。
- 处置动作执行完后,把整个过程写回 Elasticsearch,团队可以在 Kibana 里完整看到“异常日志 -> AI 结论 -> 自动处置 -> 执行结果”的闭环。
这条流程看着简单,但每一步都会遇到很多实际问题。下面几章我按部署、管道、智能告警、问题排查四个维度展开讲。
2. 环境准备与组件部署
2.1 Kafka 部署的关键点
很多教程还在用 ZooKeeper 方式来部署 Kafka,但新版本已经建议直接使用 KRaft 模式。KRaft 把元数据管理从 ZooKeeper 里收编回 Kafka 自身,部署和运维都简单很多,尤其小团队不熟悉 ZooKeeper 的情况下,少一个组件就少一份维护负担。我在新环境里都是用 Kafka 3.6 以上版本配 KRaft 模式。
单机部署时,核心配置可以精简成这样:
# config/server.properties process.roles=broker,controller node.id=1 controller.quorum.voters=1@localhost:9093 listeners=PLAINTEXT://0.0.0.0:9092,CONTROLLER://0.0.0.0:9093 advertised.listeners=PLAINTEXT://192.168.1.10:9092 log.dirs=/data/kafka-logs num.partitions=3 default.replication.factor=1 offsets.topic.replication.factor=1 auto.create.topics.enable=true生产环境里,offsets.topic.replication.factor和default.replication.factor一般要设成 2 或 3,避免 broker 节点挂掉导致消费位点丢失。advertised.listeners是新手最容易配错的地方,客户端连不上 Kafka 基本都是这个地址配了回环地址或者内网 IP 不通。
创建 Topic 的时候,我建议把分区数和副本数显式指定一下,不要依赖自动创建。这里我尽量做到用一张表表达清楚核心参数:
| 配置项 | 推荐值 | 说明 |
|---|---|---|
--topic | app-log | 日志 Topic,按系统/模块命名 |
--partitions | 3~12 | 根据日志量动态评估,宁少勿多 |
--replication-factor | 2~3 | 生产环境至少 2 副本 |
--retention.ms | 604800000 | 7 天,保留时间视需求调整 |
--compression.type | lz4 | 日志 JSON 可压缩,节省磁盘 |
Topic 创建命令:
kafka-topics.sh --bootstrap-server localhost:9092 \ --create \ --topic app-log \ --partitions 6 \ --replication-factor 2 \ --config retention.ms=604800000 \ --config compression.type=lz42.2 ELK 栈部署的关键点
ELK 的版本统一非常关键,Elasticsearch、Logstash、Kibana 三者版本如果不一致,经常会遇到协议或者插件不兼容的问题。我吃过一次亏,ES 用的 8.11,Logstash 用的是 7.17,结果 output 阶段死活连不上 ES。之后我统一锁版本,宁可不升级也不要做版本混搭。
部署方式上,我推荐先用 Docker Compose 快速起一套,验证链路通了再考虑生产环境的高可用形态。ES 的 JVM 堆内存一般设置为物理内存的一半,但不要超过 31GB,因为超过这个值后 JVM 的压缩指针会失效,反而浪费内存。Logstash 的 JVM 堆配置默认是 1GB,消费 Kafka 大流量日志时明显不够,要记得调:
# logstash/jvm.options -Xms4g -Xmx4gKibana 基本不用调什么参数,最重要的反而是登录后的安全配置。如果启用了 ES 的安全认证,Kibana 需要配置elasticsearch.username和elasticsearch.password,否则会一直红着。
2.3 Ollama 本地部署
Ollama 的定位是“本机跑大模型的极简工具”,安装完以后一个命令就能把模型拉下来并提供本地 API,非常适合这种做内部日志分析的工具链。它默认监听11434端口,接口风格和 OpenAI 兼容,调用起来很顺手。
模型选择是这一层的关键。日志分析的特点是上下文不会特别长,但需要较好的指令遵循能力。我在实际项目里优先试过两类模型:一类是qwen2.5:7b,中文指令理解好,输出结构化 JSON 稳定;另一类是llama3.1:8b,英文日志理解更强。如果你机器的显存不大,可以考虑qwen2.5:3b这类更小的量化版本,但结论质量会有明显下降。
安装模型并验证服务:
ollama pull qwen2.5:7b ollama run qwen2.5:7b "你是谁"跑通之后,直接通过 API 调用:
curl http://localhost:11434/api/chat \ -H "Content-Type: application/json" \ -d '{ "model": "qwen2.5:7b", "messages": [{"role": "user", "content": "分析这段日志的异常类型"}], "stream": false }'这里有一个容易被忽略的点:Ollama 默认会把模型常驻内存,如果你同时加载多个 7B 模型,很容易把内存吃满。建议一次只保留一个活跃模型,或者在部署时做好模型调度的脚本,否则会影响其他服务的稳定性。
2.4 OpenClaw 部署与对接
OpenClaw 在这个平台里的定位是智能体编排层,简单说就是给大模型装上一双手。它负责解析 Ollama 输出的结论、匹配预先定义的 Skill、执行具体动作。部署方式官方文档写得很清楚,推荐用安装脚本安装,也可以指定 Git 方式从仓库检出源码。这里我以 Linux 环境为例:
curl -fsSL https://openclaw.example.com/install.sh | bash安装完成后,配置文件里需要把默认的模型提供方改成 Ollama,这样 OpenClaw 才能调到本地模型。配置文件核心内容如下:
model: provider: ollama base_url: http://localhost:11434 model_name: qwen2.5:7b skills: notify: type: http url: https://internal-alert.example.com/send restart: type: shell command: systemctl restart order-serviceOpenClaw 的 Skill 机制非常实用,它把每一个运维动作抽象成结构化工具,大模型根据上下文选择调用哪个工具、传入哪些参数。比如模型判断“需要重启连接池”,它会生成一个调用restartSkill 的意图,OpenClaw 再根据配置去执行。这个过程中,模型不需要关心目标机器的真实地址,也不需要知道内部系统协议细节,全部由 Skill 层屏蔽掉。
3. 日志采集管道与 Topic 设计
3.1 日志接入层:Filebeat 为主
采集端我首选 Filebeat,因为它轻量、资源占用低,而且非常擅长处理“读文件定位偏移”这件事。日志文件切分、追加、轮转这些都是 Filebeat 默认处理好的,不需要自己写脚本。如果你在 Java 应用里直接使用 logback/log4j 的 Kafka appender,也可以,但这样会让业务应用和 Kafka 强耦合。Filebeat 作为旁路采集的好处是业务无感知,不侵入代码。
Filebeat 配置成输出到 Kafka,重点要改几个地方:
filebeat.inputs: - type: filestream id: order-service-log paths: - /data/logs/order-service/*.log parsers: - ndjson: target: "" overwrite_keys: true multiline: type: pattern pattern: '^\d{4}-\d{2}-\d{2}' negate: true match: after output.kafka: hosts: ["192.168.1.10:9092"] topic: "app-log" partition: round_robin: reachable_only: true codec.json: pretty: false required_acks: 1 compression: lz4multiline配置特别重要。Java 异常日志经常是一条主消息带一长串堆栈,如果不做多行合并,一行一个堆栈块会被拆成几十条日志,后面的智能分析基本没法看。我的经验是用时间戳开头的特征做合并,正则匹配到新日志开头时,就把之前积累的多行合并成一条完整消息。
3.2 Kafka Topic 与分区设计
Topic 的命名建议按“类型-系统”来,比如app-log、nginx-access-log、security-log。分开 Topic 的好处是可以单独设置保留时间和消费速率,比如访问日志保留 3 天就够了,业务异常日志需要保留 15 天。
分区数不能拍脑袋。分区太少,消费者并发上不去;分区太多,会带来文件句柄和副本同步开销。我一般先按“目标单分区吞吐量”估算:如果单分区能扛住 5MB/s,而每天日志峰值为 300MB/s,那至少需要 60 个分区。当然这是偏保守的算法,实际还要考虑下游 Logstash 和 ES 的消费能力。小规模场景下,日志量在每天几十 GB 的话,6 个分区通常够用。
这里顺便讲一个很多同学问过的问题:Kafka 如何延迟 30 分钟消费?Kafka 本身没有原生延迟消费 API,最常见的方案是借助外部存储做时间轮。思路很简单:消费者先收到消息后不处理,而是把消息 ID 和预计执行时间写进 Redis ZSet,分数就是执行时间戳;然后一个调度任务每秒查一次 ZSet,把到期的消息塞回 Kafka 的“真实处理 Topic”,真正的消费者再去消费那个 Topic。延迟 30 分钟,就是把到期时间设为当前时间加 1800 秒。这样虽然绕了一圈,但逻辑清楚,而且不会长期占用 Kafka consumer 线程。
3.3 Logstash 消费与加工
Logstash 从 Kafka 消费时,Input 插件是kafka,核心配置如下:
input { kafka { bootstrap_servers => "192.168.1.10:9092" topics => ["app-log"] group_id => "logstash-elk" auto_offset_reset => "latest" consumer_threads => 6 codec => "json" } } filter { grok { match => { "message" => "%{TIMESTAMP_ISO8601:log_time}\s+%{LOGLEVEL:level}\s+%{JAVACLASS:class}\s+%{GREEDYDATA:content}" } } date { match => ["log_time", "ISO8601"] target => "@timestamp" } mutate { remove_field => ["message", "original"] } } output { elasticsearch { hosts => ["http://192.168.1.20:9200"] index => "app-log-%{+yyyy.MM.dd}" data_stream => "false" } }消费线程数consumer_threads最好和 Topic 分区数保持一致。比如你给app-log建了 6 个分区,那这里就配 6 个线程,再多也白搭,分区被分配完以后多出来的线程只会空转。
Grok 解析是 Logstash 最耗计算资源的部分,正则写不好日志解析率会非常低。我在实践中的建议是先尽量让业务日志输出成 JSON 格式,Filebeat 直接解析 JSON,Logstash 就省掉 Grok 这一步,解析效率和稳定性都高很多。只有那些实在改不了格式的第三方日志,才用 Grok 兜底。
4. 智能告警与自动化处置链路
4.1 从规则告警到智能告警的演进
先别急着上大模型。我见过不少团队一上来就想用 LLM 把所有告警干掉,结果模型没调好,基础告警反而漏了。我的建议是分两层:底层保留传统规则告警,专门处理已知、高确定性场景;上层用智能分析处理那些“规则描述不清”的长尾场景。
传统规则告警可以用 Logstash 输出到一个独立的告警 Topic,也可以直接用轻量组件做。比如这个规则就很有代表性:2 分钟内同一服务的 ERROR 日志超过 20 条,触发告警。这种场景大模型处理反而笨重,规则处理既快又准。
智能告警的工作重心放在这些事上:聚合相似日志、识别异常之间的关联、给出根因猜测、建议处置动作。为了让 Ollama 处理得过来,我们不能把每一条原始日志都丢给模型,那样成本太高、响应也太慢。正确姿势是先做“候选集生成”:通过关键字聚类或时序异常检测,把可疑日志片段提取出来,再丢给大模型去理解和归纳。
4.2 用 Ollama 做日志语义分析
我实现了一个 Python 写的分析服务,它消费 Kafka 里的异常日志候选集,按固定的时间窗口聚合成一组样本,然后传给 Ollama。下面是一个简化版的核心代码:
import json import requests from collections import defaultdict OLLAMA_URL = "http://localhost:11434/api/chat" MODEL_NAME = "qwen2.5:7b" def analyze_logs(logs): prompt = build_prompt(logs) resp = requests.post(OLLAMA_URL, json={ "model": MODEL_NAME, "messages": [{"role": "user", "content": prompt}], "stream": False, "format": "json" }) result = resp.json()["message"]["content"] return json.loads(result) def build_prompt(logs): log_text = "\n".join([f"[{log['level']}] {log['content'][:500]}" for log in logs]) return f""" 你是日志分析专家。以下是最近 {len(logs)} 条异常日志: {log_text} 请输出 JSON,字段如下: - error_type: 异常类型 - root_cause: 可能的根因 - impact: 受影响业务 - action: 建议处置动作 - confidence: 0~1 置信度 只输出 JSON。 """这里用了 Ollama 的format: "json"参数,强制模型输出合法 JSON,方便后续代码直接解析。我在测试中发现,如果不加这个参数,模型偶尔会在 JSON 里夹带解释性文字,解析时会炸。Prompt 里也要明确“只输出 JSON”,双保险。
Ollama 在文本生成上的延迟是个现实问题。7B 模型在消费级 GPU 上生成几百个 token 可能要几秒到十几秒,这在高频告警链路里是没法接受的。所以我只对候选集做分析,而且给每个任务设置超时时间。如果模型返回太慢,宁可放弃这次分析,也不能阻塞整个消息管道。
4.3 OpenClaw 负责告警执行闭环
Ollama 分析完以后,输出的结论还只是“文字”。真正要落地,需要 OpenClaw 把结论变成动作。我在 OpenClaw 里注册了三种典型的 Skill:通知类、工单类、处置类。每个 Skill 在配置里声明名称、描述、入参格式,OpenClaw 的 Agent 会根据模型结论自动选择调用哪个。
一个告警通知 Skill 的配置示例:
skills: notify_alert: description: 发送告警通知到值班群 input: title: string content: string level: string exec: type: http url: https://internal-notify.example.com/send method: POST headers: Content-Type: application/json body: title: "{{input.title}}" content: "{{input.content}}" level: "{{input.level}}"OpenClaw 收到 Ollama 的结构化结论后,会尝试把action和impact映射到 Skill 入参。比如模型输出action: restart_order_service, OpenClaw 匹配到restart_serviceSkill,再结合impact里的服务名,拼出执行命令。这里需要有一个安全的“护栏机制”,我强烈建议在自动执行前加一层确认或者审批。尤其像重启、回滚这类高危操作,至少第一次运行时走人工确认,稳定之后再逐步放开。
另一个实用小技巧:把整个“日志 -> 分析 -> 执行”的完整记录都写回 Elasticsearch。我在 ES 里单独建了一个ai-alert-history索引,每条记录包含原始异常日志、模型结论、执行动作、执行结果和耗时。这样后续做效果评估、模型调优或者复盘都很方便,也可以用来不断改进 Skill 的匹配规则。
5. 常见问题与排查技巧实录
5.1 Kafka 消息积压与 OOM
Kafka 链路最常见的表现是:日志已经写进 Topic 了,但下游 Logstash 消费不过来,堆积越来越严重。优先用命令看消费组的 Lag:
kafka-consumer-groups.sh --bootstrap-server localhost:9092 \ --describe \ --group logstash-elk如果看到LAG持续增长,基本就是 Logstash 消费能力不足。先看 Logstash 的 JVM 堆有没有频繁 GC,再把consumer_threads调到和分区数一致,如果还不行,就要考虑扩容分区或者优化 Filter 正则。千万不要在积压时重启 Logstash,重启过程会触发 Rebalance,反而可能让消费暂停更久。
Kafka 进程本身 OOM 的情况也多见。Kafka 的堆内存默认是 1GB,但日志量大的环境至少要给 4GB 以上。更重要的是,Kafka 主要内存消耗其实不在 JVM 堆,而在页缓存。如果日志消息体积大且没有开启压缩,消息在发送到 socket 缓冲区时会占用大量非堆内存。所以我坚持在 Filebeat 和 Producer 侧都开启 LZ4 压缩,这也是减少 OOM 最有效的手段之一。
5.2 ELK 索引爆炸与查询变慢
日志平台跑到半年以后,ES 的索引数量和磁盘占用就成了头号问题。如果没有索引生命周期管理,索引会无限膨胀。我在 ES 里配置了 ILM Policy,按天滚动索引,保留 30 天,超过 30 天自动删除:
PUT _ilm/policy/log-policy { "policy": { "phases": { "hot": { "min_age": "0ms", "actions": { "rollover": { "max_size": "30gb", "max_age": "1d" } } }, "delete": { "min_age": "30d", "actions": { "delete": {} } } } } }查询变慢的另外一个常见原因是 mapping 字段爆炸。Kafka 里的 JSON 日志如果字段不固定,ES 会动态生成大量字段,时间长了每个查询都要扫描几万个字段。我建议在 Logstash 里把mutate插件把无关注释字段去掉,或者给索引设置dynamic: false,只保留明确 mapping 的字段。
5.3 Ollama 性能与模型选择
很多人在本地部署 Ollama 后遇到的最大问题是“模型响应太慢”。这要分两种看:如果跑的是 7B 模型但只有 CPU,每个 token 生成可能要几百毫秒,多个请求并发时基本就不可用了。至少要有 8GB 显存的 GPU,才谈得上实时分析。如果机器只是偶尔分析一批日志,CPU 模式也能跑,但一定要做好请求队列和超时熔断。
模型文件下载缓慢的问题,我采取了一个更可控的办法:在能够正常访问官方模型库的机器上提前把模型文件拉下来,然后通过 Ollama 本地文件导入。不依赖部署时的在线下载,后续上线也更快。如果你的环境完全离线,也可以通过Modelfile从本地 GGUF 文件直接构建模型,这样整个部署链路不依赖任何外部网络。
5.4 OpenClaw 与内部系统对接的坑
OpenClaw 对接内部系统时,最大的坑不是模型,而是网络和鉴权。很多内部接口都在内网环境,OpenClaw 部署机必须能访问到这些地址。我把这些地址统一收敛到一个网关层,不让 OpenClaw 直接暴露在业务网络里,安全性和可维护性都好很多。
另外,Skill 的描述直接影响模型判断。刚开始我的 Skill 描述写得很简单,比如“重启服务”,结果模型经常把参数传错。后来我把每个 Skill 的输入参数、触发条件、使用场景都写清楚,再配合 few-shot 示例,调用准确率明显提升。其实大模型在这个链路里的角色更像一个“调度员”,调度员看不懂工具说明书,动作一定会错。
5.5 快速排查速查表
| 现象 | 可能原因 | 解决思路 |
|---|---|---|
| Kafka 持续积压 | Logstash 消费能力不足 | 调大consumer_threads,检查 GC,增加分区 |
| ES 查询越来越慢 | 索引太多或字段爆炸 | 配置 ILM,设置dynamic: false |
| Ollama 响应超时 | 模型过大或请求并发过高 | 换小参数量模型,加响应超时和队列 |
| OpenClaw 调错 Skill | Skill 描述不清晰 | 丰富描述,添加示例,收敛入参格式 |
| 告警重复轰炸 | 没有做聚合去重 | 在智能分析层做时间窗口聚合 |
| 日志解析乱码 | 多行日志没合并 | 配置 Filebeat multiline,或改 JSON 日志 |
这套速查表是我们团队排障时的第一份参考,基本覆盖了 80% 的日常问题。更多细节需要结合实际压测和数据量来调。
我在实际部署这套平台的过程中,最大的体会是:不要把大模型当成银弹,先让 Kafka 和 ELK 这条基础管道稳如磐石,再往上叠加智能分析。前期可以先用规则告警兜底,把 Ollama 的结论作为参考信息推送给值班人员,等人力确认稳定了,再逐步放开 OpenClaw 的自动处置能力。另外,ES 里一定要保留完整的告警决策记录,这是后续优化模型和 Skill 最重要的素材。这套平台搭好之后,我能明显感觉到告警处理从“被动救火”变成了“有章法地自动应对”,值班同学的压力也小了很多。