
做监控这行最怕的就是“系统已经崩了但监控大屏上还是一路绿灯”。传统监控看的是指标CPU、内存、磁盘、网络这些但业务卡顿、请求报错、安全攻击往往只会在日志里留下痕迹。日志量一大人工翻根本不现实这时候 Elastic Stack 9.3.0 这套组合拳就很能打了。这篇我不讲官方文档那套东西直接结合我自己的落地经验聊聊怎么用 Elasticsearch Kibana Filebeat 这套链路把日志异常检测真正跑起来并且让它不只是一堆花哨的图表而是能真的在出事之前“拉警报”。这篇文章适合谁看主要给两类人一类是刚接手日志平台、正在选型或者刚从 ELK 老版本迁移上来的运维和研发另一类是已经在用 Elastic Stack但只会查日志、不会用聚合和机器学习功能想知道异常检测到底怎么玩的人。我尽量说得直白一点能踩的坑提前帮你踩掉。1. 为什么非得用日志异常检测1.1 日志里到底藏了哪些“异常”很多刚开始做日志平台的团队第一步就是把日志从服务器上收上来然后在 Kibana 里做检索。比如查一个 user_id或者看某个时间段某个服务打了什么报错。这确实是 ELK 的基本功但说实话这种用法顶多算“日志集中查看”离“异常检测”还差着好大一截。我举几个真实场景你就明白为什么要上检测了。凌晨三点业务接口的调用量突然比平时低了百分之二十表面看没有任何报错但实际上是一条缓存依赖出了问题所有服务都在空转。这是“量变”带来的异常靠人肉翻日志根本发现不了。再比如某个应用持续在报数据库连接池耗尽但出现这个报错之前其实已经有一段超时爬坡的过程低峰期不明显一到大促流量上来问题就炸了。还有安全类场景某个外网 IP 在短时间内对登录接口发起大量请求特征和正常用户完全不同但这些请求在日志里是通过状态码 200 或者 302 回包的指标监控完全不会报警。这些情况都有一个共同特点单看一条日志都正常但把时间轴拉开、把频率做个统计异常就显形了。日志异常检测做的事情就是在百万千万条日志里自动找出这些“规律被打破”的位置而不是等某个关键字出现才去查。1.2 Elastic Stack 凭什么适合干这件事可能有人会问Prometheus Grafana 也能做告警为什么非得用 Elastic Stack两大原因。第一个是日志数据的形态。Prometheus 是拉取指标适合数值型时序数据但日志是文本是高度非结构化的。你要从日志里提取 error_code、response_time、client_ip 这些字段Elasticsearch 的全文检索和动态映射天然合适你要在文本里做复杂的正则解析和字段拆分Logstash 和 Ingest Pipeline 是干这个的。第二个原因是这个栈把“从采集到告警”全部打通了不需要自己拼拼凑凑。Filebeat 负责采Elasticsearch 负责存和算Kibana 负责看和管机器学习、告警、可视化全在一个界面里不用来回切系统。我最早接触这套东西也是从 5.x 版本开始的那时候机器学习还要单独装插件告警要靠 Watcher 写复杂的 JSON用起来是真心疼。现在的 9.3.0 版本监控、告警、异常检测都集成在 Kibana 里界面化操作对运维人员友好很多。如果你的集群从老版本升上来这个版本在查询性能和存储压缩上也有明显改善同样是存三个月日志磁盘占用能比老版本省不少。2. 核心细节解析与实操要点2.1 日志采集从原始日志里拿到干净数据一切检测都是建立在数据质量上的。采集端我主力用的是 Filebeat因为它轻量、占资源少监控多少个节点都不会把业务服务器拖垮。采集这一步有几个容易被忽略的点我先列出来。第一多行日志怎么处理。Java 应用打堆栈异常的时候一条异常其实是多行文本从Exception开头到at com.xxx.xxx中间一大段。如果 Filebeat 每行当成一条独立日志后续你就会看到一条异常被拆成几十条垃圾记录别说异常检测了连搜索都难用。Filebeat 里解决方式是配 multiline把不是以时间戳开头的行都归并到前一条。filebeat.inputs: - type: filestream enabled: true paths: - /var/log/app/app.log parsing: - type: multiline pattern: ^[0-9]{4}-[0-9]{2}-[0-9]{2} negate: true match: after这样处理之后一条堆栈日志就变成一个完整的事件后面做聚合统计时才不会被拆散。这里也有个小坑filestream类型是 7.x 之后的新输入老配置的log类型虽然还能用但新项目尽量用filestream新功能都在往它上面加。第二采集端的吞吐和背压。Filebeat 默认有一个 event queue网络抖动或者 ES 写入变慢时它会在本地暂存不会把数据丢掉。但你要注意磁盘空间如果 Filebeat 缓存目录所在的盘写满了它宁可丢新数据也不影响原有日志文件读取。生产环境我用的时候会专门挂一块独立盘给它的 data 目录避免被系统日志写满。第三多服务器采集时一定要带好环境标签。用 Docker 跑 Filebeat 的话通过 processors 给事件统一加上env和app_name字段这样后面做集群维度的异常检测时会非常方便。processors: - add_fields: target: project fields: env: production app_name: order-service2.2 解析与清洗把日志变成能算的字段日志不解析之前就是一个大文本字段Kibana 里满屏是 message看着很乱。但异常检测的核心是“算”不是“看”要把内容拆成字段才能算。比如访问日志至少要拆出timestamp、client_ip、method、request_path、status、response_time这些字段。有了response_time你才能算平均响应时间、P95、P99有了status你才能统计 5xx 的错误比例。解析我推荐优先用 Elasticsearch Ingest Pipeline而不是 Logstash。原因很简单少维护一个组件。Filebeat 采集之后直接把数据 POST 给 Elasticsearch数据进索引之前先经过 pipeline 处理链路最短故障点最少。只有在遇到极端复杂的格式、需要聚合多个数据源做富化的时候我才单独上 Logstash。写 pipeline 时最核心的是用 Grok 模式还是 Dissect 模式。Grok 基于正则有强大的匹配能力但 CPU 开销大Dissect 基于分隔符拆分速度极快但不支持模糊匹配。我的习惯是日志格式固定、没太多可选字段的用 Dissect有复杂前缀后缀、要兼容多种格式的用 Grok。以 Nginx 日志为例Grok 的写法大概是{ description: parse access log, processors: [ { grok: { field: message, patterns: [ %{IPORHOST:client_ip} - - \\[%{HTTPDATE:timestamp}\\] \%{WORD:method} %{URIPATH:request_path} HTTP/%{NUMBER:http_version}\ %{NUMBER:status} %{NUMBER:body_bytes} \%{DATA:referer}\ \%{DATA:user_agent}\ %{NUMBER:response_time} ] } } ] }Grok 调试是一定要先在 Kibana 里的 Grok Debugger 里面跑通的别直接在集群上试错。我犯过的错就是用了一个过于贪婪的正则导致极慢和解析失败。解析完字段之后还要处理两个问题时间戳的映射和字段类型。Nginx 日志里的时间格式是19/Sep/2024:15:00:00 0800Elasticsearch 默认不认这种格式要用dateprocessor 转成标准的日期类型并且指定timezone。否则你拿 Kibana 的时间筛选器一选数据全部对不上这是新手踩得最多的坑。{ date: { field: timestamp, target_field: timestamp, formats: [dd/MMM/yyyy:HH:mm:ss Z], timezone: Asia/Shanghai } }2.3 异常检测机制异常到底是怎么“算”出来的数据洗好了下面就到了核心异常检测。这块很多文章讲得云里雾里我尝试用最直白的话讲清楚几条路线。第一类是阈值告警最容易理解。设定一个固定值比如错误率超过 5% 就报警。这种方案简单直接但问题也显而易见业务有高峰低谷晚上十点的请求量和凌晨三点的请求量差了十倍用一个固定阈值不是误报就是漏报。所以只适合告警量和业务量都稳定的小场景。第二类是动态阈值用统计方法解决“不同时间不同基线”的问题。Elasticsearch 聚合把数据按分钟做分桶然后对每个桶计算近期平均值和标准差。当最新值偏离均值超过 3 倍标准差时判定异常。这个思想很像我们上学时学过的正态分布“超过均值加上三个标准差”就是小概率事件。它的优点是能适应业务周期性变化缺点是没有考虑“昨天同时段”的趋势对突发的缓慢爬坡不敏感。第三类是 Elastic 内置的机器学习异常检测这是我最推荐的方式。Kibana 里创建异常检测作业的时候可以直接选单指标single metric、多指标multi metric、群体分析population和日志速率分析log rate analysis等类型。其中日志速率分析特别适合用来做“日志异常检测”——它不盯某一项指标而是看整体日志流的模式变化某个日志类别突然变多或变少都会被打上异常分数。机器学习的原理说起来复杂用起来其实就是一个黑盒你在 Kibana 里选好索引和要检测的指标它自己会学习历史数据的规律建立一个基线模型然后不断拿新数据和基线比对输出一个 0 到 100 的 anomaly score。分数越高异常越明显。我最常用的是单指标里的 event rate去看某个服务的日志条数是否突增或者突降这条曲线能反映非常多的隐性故障。实际操作中我的经验是不要只依赖机器学习结合业务规则一起用。机器学习负责发现“你没想到的异常”规则负责保证“你明确知道的异常”一定报警。比如我知道某个银行接口一天只可能被调用几百次那超过一万次就是被刷了这种用规则更直接。机器学习做兜底规则做必现两边互补。3. 实操过程与核心环节实现3.1 环境搭建以 9.3.0 为例的部署思路部署方式我推荐用 Docker Compose启动快、干净、好迁移。如果你想模拟生产环境建议至少给 Elasticsearch 分配 4GB 以上内存否则跑一会儿节点就会因内存不足崩掉。这是我第一次搭集群时血的教训默认的-Xms1g -Xmx1g根本不够用弄了三台 2G 内存的虚机数据量一到百万级就频繁 Full GC。一个最简可用的docker-compose.yml大概长这样version: 3.9 services: elasticsearch: image: docker.elastic.co/elasticsearch/elasticsearch:9.3.0 container_name: es01 environment: - node.namees01 - cluster.namees-cluster - discovery.typesingle-node - bootstrap.memory_locktrue - ES_JAVA_OPTS-Xms4g -Xmx4g - xpack.security.enabledtrue - xpack.security.initial_master_passwordyourStrongPassword ulimits: memlock: soft: -1 hard: -1 volumes: - es_data:/usr/share/elasticsearch/data ports: - 9200:9200 kibana: image: docker.elastic.co/kibana/kibana:9.3.0 container_name: kib01 ports: - 5601:5601 environment: - ELASTICSEARCH_HOSTShttp://elasticsearch:9200 - ELASTICSEARCH_USERNAMEelastic - ELASTICSEARCH_PASSWORDyourStrongPassword depends_on: - elasticsearch volumes: es_data:注意这里的bootstrap.memory_locktrue建议开启防止内存被换到磁盘上会拖垮查询性能。生产环境不要用single-node至少三节点组成集群并且做证书认证但这些是后话测试环境先跑起来最重要。这里使用的是 basic 安全配置带账号密码这是新版本的默认要求不能跳过。3.2 配置索引生命周期管理让数据留存可控索引生命周期管理ILM是必配项不做的话Elasticsearch 会把磁盘写爆炸。做过日志运维的人都懂每天几十 GB 的日志三个月不清理节点存储就报警了。ILM 的核心思路是给一个索引模板关联上一个策略自动完成“热阶段 → 冷阶段 → 删除”的流转。在 Kibana 里可以界面化创建策略我拿配置举例热阶段保留 7 天索引达到 50GB 或写入 1 天就滚动。冷阶段移动到冷节点保留 30 天。删除阶段超过 90 天自动删除。创建索引模板的时候把index.lifecycle.name指定到已有的策略并且设置好index.lifecycle.rollover_alias。别名的问题容易出错如果索引没有挂到 alias 下面滚动永远不会触发日志只往一个索引里塞最终导致单个分片超大。这个问题排查起来特别隐蔽我建议模板配好之后先手动登录 ES 的 head 或者用_cat/indices?v看看索引是否按日期分段切割。PUT /_index_template/logs-nginx { index_patterns: [logs-nginx-*], template: { settings: { number_of_shards: 1, number_of_replicas: 1, index.lifecycle.name: logs-lifecycle, index.lifecycle.rollover_alias: logs-nginx } } }这样每天的日志都能自动进入新的索引查询的时候用logs-nginx-*模式一次性查全部。3.3 创建机器学学异常检测作业给日志上一道保险打开 Kibana进入 “Machine Learning” - “Anomaly Detection”点击 “Create job”。这里有很多模板我常用的是 “Single metric” 来监控日志数量的波动另外 “Log rate analysis” 用来检测日志内容的分类变化。以单指标作业为例选 Single metric然后在索引模式里选你存放访问日志的索引例如logs-nginx-*。指标类型选count按分钟聚合。作业会自动用历史数据做训练训练完成后当你进入该作业的 “Single Metric Viewer” 时就能看到一条日志数量的曲线和异常分数。前面说了异常分数是 0 到 100默认超过 50 就认为异常。这里有一个经验初始阶段不要盲目调低阈值否则机器人会把你淹没在告警里。先默认跑几天观察正常业务时分数一般在什么区间再根据自己的忍受度调整。创建好作业之后记得在 Kibana 的 “Stack Management” - “Alerts and Insights” 里创建一条规则。规则类型选 “Anomaly detection”指定刚才创建的作业设定当 anomaly score 超过 70 就触发。动作可以选发送邮件、Webhook 到企业微信/钉钉等。我记得 9.3 版本里面行动连接器配置做得比较顺手直接在 UI 里填 Webhook URL 就可以不用再去写脚本。3.4 告警与可视化把异常结果用一屏看全异常检测作业跑起来了告警也有了但如果老板问“系统最近怎么样”你总不能把机器学习作业的折线图甩过去。要做一张清晰的 Dashboard把几个关键视图放在一屏里。我一般会加这样几个板块日志总吞吐量时间序列用 Lens 或者 TSVB按分钟查看日志条数。异常分数趋势把机器学习作业的结果作为数据源直接拖一个 anomaly score 的矩形图。Top 错误路径表格按request_path和status做 group by从高到低排序直接看到哪个接口报错最多。客户端 IP 排行用client_ip做聚合看是否有某个 IP 异常高频访问。Kibana 的可视化很灵活不一定非要学复杂的 Vega直接用 Lens 拖拽就能完成绝大多数场景。做可视化的时候有一个细节时间范围步长和聚合粒度要匹配。如果你要监控“最近 15 分钟”按分钟聚合没问题如果你要“最近 30 天”分钟聚合会直接把浏览器卡死不渲染。这时候应该把时间桶改成每天或者每小时至少保证一个时间窗内只返回几十个数据点。4. 常见问题与排查技巧实录4.1 数据都进 ES 了但 Kibana 里看不到这个错误我碰过不下五次。Kibana 的 Discover 页面默认是有时间筛选器的如果你索引里的timestamp字段没有正确映射或者解析出来是字符串时间筛选器就会把数据全部过滤掉。排查方法很简单先进 Stack Management - Index Patterns 看看timestamp是否显示为 Date 类型不是的话说明你的 pipeline 没有把时间转成日期类型回到 pipeline 里面做 date 格式化。还有一个可能是索引模式选错了比如日志索引叫logs-nginx-2024.09.01但你建的索引模式是logs-*注意通配符要能匹配上。4.2 机器学习作业一直显示“无数据”或者训练太久新装的集群不是立刻就能用机器学习的系统要先重建一些系统索引消耗一定时间。如果你创建作业时选择的索引模式没有匹配到任何数据也会一直无数据。另外训练的时间长短和数据量有关如果数据量很大可以先把检测窗口缩短跑通之后再拉长。有一个经验是机器学习作业创建之后它需要一定的历史数据作为 baseline如果这台集群刚上线数据还不够一天作业很难学到规律你最好先攒几天的数据再建作业效果会稳很多。4.3 误报和漏报之间怎么平衡这应该是大家问得最多的。先说结论没有一套配置能兼顾所有场景必须分业务来调。前端静态资源接口正常波动大阈值要给高核心交易接口比较平稳阈值可以给低。我的做法是把告警规则按影响面分成 P1、P2、P3 三级。P1 级别只看核心链路比如订单、支付、登录用较为严格的规则一旦异常立刻群通知P2 级别是通用服务用机器学习异常检测只通知到负责人P3 级别是外围服务只记录在案不打扰任何人。分层之后告警噪音大幅下降。还有一个细节告警里一定要附带上下文。比如“错误率突增”这条告警触发时把当前维度下的 Top 错误信息也放在通知内容里这样收到告警的人不用再打开 Kibana 查半天。Kibana 的告警信息可以用 mustache 模板动态引入字段我是直接把异常分数和最近错误摘要都拼进文案里。4.4 性能优化从采集端到 ES 全链路要注意什么日志量大的时候瓶颈一般出现在 Elasticsearch 写入端而不是采集端。常见的优化手段第一bulk 批量写入。Filebeat 默认就是一批量一批量发的如果用的是 Logstash记得设置pipeline.batch.size和batch.delay。第二索引分片数量要控制。很多新手喜欢一个索引分片越多越好事实上分片越多集群管理开销越大对于日志这种写入为主的场景单个索引 1~2 个分片往往性能更好。第三打开index.refresh_interval调大一些。默认一秒刷新意味着每秒都会产生一个 Segment对性能影响很大日志场景可以改成 10 秒甚至 30 秒让查询稍微延迟一点来换取写入吞吐。如果你发现磁盘 IO 长期打满还可以考虑把索引的translog.durability改成async这样写请求的响应速度快不少。代价是节点异常时可能丢失少量数据但日志场景大多能接受这个取舍看业务容忍度。总之日志平台本质是“先服务写入再服务查询”和常规数据库的优化思路是反着来的。4.5 集群状态变黄分片无法分配在 Kibana 的监控页里看到cluster health: yellow是常有的事原因通常是有副本分片没有被分配。日志数据量大、节点又少的情况下节点上只剩主分片副本分片全部 pending状态就变黄。最容易出现的场景是明明一个分片只有一个副本但集群节点就一台副本永远找不到地方放。测试环境一台节点直接用number_of_replicas: 0就行。生产环境要避免黄就需要保证副本分片能被分配到和主分片不同的节点上。排查这个问题的命令很简单curl -X GET localhost:9200/_cluster/allocation/explain?pretty这个接口会直接告诉你为什么某个分片没有被分配是磁盘空间不足还是节点属性不匹配还是已经卡在拷贝中。用过几次之后你会觉得这比在 Kibana 里点点点高效得多。5. 进阶玩法与我的个人体会日志异常检测做到基础告警这一步已经能覆盖大部分运维需求了。但我觉得这条路还能往前走几步。比如做“关联分析”把日志异常和系统指标异常关联起来。某台机器的磁盘占用率和日志报错率同时爬升大概率是磁盘满导致写盘失败这时候把error stage里聚合出的异常 IP 再反查防火墙和安全日志有可能发现攻击链条是很有意思的延伸。另外一个方向是用 Elastic Stack 做容量规划。日志数据本身就是业务流量的数字化投影统计每天的请求量增长趋势配合机器学习里的预测功能可以大致估算三个月后的日志存储需求和集群资源瓶颈。这比拍脑袋定服务器配置靠谱得多。回到工具本身我个人最大的感受是Elastic Stack 现在功能越来越全但对运维人员的心智负担也在变大。做好日志异常检测真正难的不是学会了怎么点按钮、怎么写 pipeline而是你对自己的业务日志有多了解。一个不懂接口调用链路的运维配再多告警规则也只是碰运气。所以我的建议是搭建这套系统的同时多和开发聊日志格式规范尽量推动应用统一日志输出 JSON 格式这对后续所有分析和检测都是最省力的一步。最后分享一个小的调参技巧。创建单指标异常检测作业时Kibana 会让你选“时间桶跨度”之类的高级参数。默认它会自动放一个比较宽的窗口。如果你发现异常检测对短时抖动不敏感可以把桶大小缩小到分钟级这样检测灵敏度会明显提高。当然缩得太小也会带来噪音增加需要结合业务周期慢慢试。工具是死的日志是活的把日志变成资产才算真正把这套平台用明白了。