资讯详情

从SSH翻日志到EFK:小团队日志监控落地实践

📅 2026/10/13 12:54:37 | 华诺云谱 👁 阅读
从SSH翻日志到EFK:小团队日志监控落地实践
先说我这边最真实的感受。机器从几台涨到十几台之后排查线上问题的方式还是老一套ssh 上去tail -f 看实时日志再 grep 关键字。运气好十分钟定位运气不好要翻几个小时更别提遇到“过去一小时到底报了多少 5xx”“这个报错在哪些机器上出现过”这类需要聚合统计的问题基本只能靠猜。后来我花了两天时间搭了一套 EFK 日志监控系统——Elasticsearch 负责存储和检索Fluent Bit 负责采集Kibana 负责可视化和查询入口。整套跑起来之后查日志的效率提升非常明显顺带把告警这件事也解决了。这篇文章就把我从选型到落地的完整过程复述一遍适合正在纠结要不要上日志平台、或者准备动手搭建的小团队参考。1. 先从“日志都看不到”说起EFK要解的题和方案取舍1.1 EFK三个组件各自扛什么活儿EFK 这个名字里的 F 现在通常指 Fluent Bit 或 Fluentd整套系统由三个核心组件组成Elasticsearch 负责把日志集中存下来并提供全文检索和聚合统计能力Fluent Bit 以轻量级代理的形式部署在各台业务机器上把本地日志文件一条条读出来解析成结构化的字段再送给 ElasticsearchKibana 则是给人类操作的面板在浏览器里完成日志搜索、图表绘制和看板组装。三个组件的关系可以理解成Fluent Bit 是各地的“收件员”负责把东西从各个角落收集起来Elasticsearch 是“仓库管理员”按照门类把东西摆放好还能快速找到Kibana 是“展示窗口”让你一眼看清仓库里都有什么、出了什么状况。这套结构的好处是职责单一、各管一段哪一环出问题都容易定位。1.2 为什么是EFK而不是ELK、Loki或商业方案聊到日志平台不少人第一反应是 ELK也就是 Elasticsearch Logstash Kibana。ELK 当然成熟但 Logstash 是 JVM 系的常驻内存动辄几百 MB 起步如果每台机器都装一个光是采集这一层就吃掉不少资源。我当时给每台业务机分配的资源本来就不宽裕所以选型时直接跳过了 Logstash改用 C 语言编写的 Fluent Bit。它常驻内存只有几 MB 到几十 MB在低配机器上跑毫无压力而且吞吐量并不差。也有人推荐 Grafana Loki。这个方案的定位不太一样Loki 更擅长按标签索引日志适合“根据机器名、服务名快速过滤”的场景全文检索和复杂聚合能力相对弱一些。而我们需要的是从任意关键字去检索日志还要做状态码分布、错误趋势这类统计Elasticsearch 的倒排索引和聚合框架显然更顺手。至于商业方案功能确实完整但授权成本对小团队来说偏高而且日志这种数据量大的场景按量计费会比较肉疼。综合下来EFK 是成本、能力和维护复杂度三者之间最平衡的选择。下面的表格是我当年选型时做的一个简单对比供参考方案资源占用全文检索聚合分析运维成本ELKLogstash高强强中高EFKFluent Bit低强强中Loki低弱弱中商业方案视产品而定强强低但付费2. 版本与参数先行安装前的环境规划这块是最容易翻车的2.1 大版本怎么选7.17还是8.x这是我这轮踩过最实在的坑先说结论如果追求低门槛快速落地选 7.17.x 系列如果是新项目、愿意顺手把安全基线做好选 8.x。两个大版本之间最大的差别集中在 Elasticsearch 和 Kibana 上——8.x 默认强制开启安全特性访问 ES 需要走 HTTPS并且必须有用户名密码或 API KeyKibana 连接 Elasticsearch 也要带着账号信息。7.x 默认不开启这些内网环境可以直接用 HTTP 连对新手友好得多。组件之间的版本必须尽量对齐最省事的规则是 Elasticsearch 和 Kibana 用同一大版本、同一小版本号。比如都选 7.17.x或者都选 8.x。Fluent Bit 相对独立一些它的 elasticsearch 输出插件对 ES 7 和 8 都兼容但连接参数不一样7.x 可以不配账号密码8.x 就必须在配置里显式声明用户和密码否则数据会写入失败。另一点要注意的是Elasticsearch 7.x 和 8.x 都内置了 JDK不需要额外安装 Java网上那些让你先装 Java8 再装 ES 的旧教程基本可以忽略。2.2 单机还是集群不同阶段的合理规模小团队刚开始建日志平台日均日志量在几个 GB 以内的阶段完全没必要一上来就搞三节点集群。一台 8核16G 的机器给 Elasticsearch 分 4G 堆内存跑个单机实例完全够用这也是我当时的选择。单机部署的关键配置是discovery.type: single-node少了这句ES 会因为找不到其他节点而反复尝试选主启动过程会非常慢。等日志量涨上来之后扩容方向也不是直接加节点那么简单。优先考虑的是拆分索引、做生命周期管理而不是盲目加机器。真到了需要横向扩展的时候再按照数据节点、专用主节点的角色去规划集群。对于刚入门的朋友我的建议很直接用一个单机实例先把链路跑顺把日志规范和索引模板做好这比一开始就铺一个复杂集群有价值得多。2.3 内核参数与账号准备Elasticsearch 对 Linux 内核参数有两个硬性要求不满足它直接拒绝启动。第一个是虚拟内存映射数量上限需要vm.max_map_count至少 262144否则启动时会报 “max virtual memory areas vm.max_map_count [65530] is too low”。第二个是文件描述符限制建议把ulimit -n调到 65535否则高并发写入时容易出现句柄不足。还有一点别忽略ES 明文禁止用 root 用户直接运行必须创建一个普通用户来启动进程。我当时建了一个专门的用户数据目录和日志目录的所有权限都交给它避免后续因为权限问题反复折腾。另外ES 的 JVM 堆内存默认值不一定合适建议修改jvm.options里的堆大小设置。通用经验是给物理内存的一半左右但不要超过 31GB否则压缩指针优化会失效。比如机器总共 16G给 ES 的堆设成 4G 到 8G 之间都比较合理剩余内存留给操作系统页缓存页缓存对读取性能的帮助也很明显。3. 三件套落地ES、采集端、Kibana的安装与基础连通3.1 Elasticsearch单机部署要点Elasticsearch 的安装本身不复杂下载对应大版本的 tar 包解压即可。我建议固定到一个版本比如 7.17.x避免后续升级时接口差异导致配置频繁变动。下面是单机部署时比较稳妥的步骤下载并解压到统一目录可以放在/opt/elasticsearch。编辑config/elasticsearch.yml设置集群名、节点名、数据目录、日志目录和监听地址。编辑config/jvm.options把-Xms和-Xmx设置成相同值避免堆大小动态伸缩带来的性能抖动。切换到普通用户执行bin/elasticsearch -d后台启动。elasticsearch.yml里最核心的几个配置项如下cluster.name: log-cluster node.name: node-1 path.data: /data/es-data path.logs: /logs/es-logs network.host: 0.0.0.0 http.port: 9200 discovery.type: single-node注意network.host一旦设置成非 localhost 地址ES 会认为你要对外提供服务此时如果没有配 discovery 相关参数启动时会对网络环境做一系列自检容易卡在引导检查上。单机场景直接加discovery.type: single-node是最省心的做法。启动之后用curl -s http://localhost:9200验证能返回带版本信息的 JSON 就说明 ES 已经活过来了。如果启动失败优先看日志目录里的日志文件不要只看控制台输出很多关键错误都写在日志里。3.2 采集端Fluent Bit的安装与最小配置Fluent Bit 的安装推荐直接用官方提供的安装源一条命令装完编译好的二进制省去自己编译的麻烦。装完后先不要急着配置各种解析规则先把最小链路跑通用一个简单的输入插件发测试数据输出到 Elasticsearch确认整个采集通道是通的。最小配置可以长这样[SERVICE] Flush 1 Log_Level info [INPUT] Name dummy Tag test.log [OUTPUT] Name es Match * Host 127.0.0.1 Port 9200 Index test-logdummy 插件会定时生成一条测试日志如果这条数据能顺利出现在 Elasticsearch 里说明 Fluent Bit 与 ES 的连接没有问题。这一步值得耐心验证因为我见过不少人配置没问题最后卡在网络不通、或者 ES 开启安全认证后忘记填账号密码导致数据一直进不来。启动 Fluent Bit 后可以通过命令查看它是否正常运行再去 ES 侧查一下有没有生成对应的索引。发现索引有数据再开始接真实日志文件。3.3 Kibana接入与ES联通Kibana 安装同样解压即用关键配置集中在config/kibana.yml里。7.x 版本最少需要设置server.host和elasticsearch.hosts8.x 版本还需要配上elasticsearch.username和elasticsearch.password否则 Kibana 连不上 Elasticsearch。server.host: 0.0.0.0 server.port: 5601 elasticsearch.hosts: [http://127.0.0.1:9200] # 如果是8.x需要额外配置 # elasticsearch.username: kibana_system # elasticsearch.password: 你的密码启动 Kibana 后访问5601端口第一次进入需要初始化一些内部索引。等看到 Kibana 的欢迎页说明 ES 和 Kibana 之间已经建立了正常的连接后面就可以开始设计日志的索引模式了。4. 从一行日志到结构化字段采集链路的关键配置4.1 用tail插件纳管日志文件正式采集真实日志时最常用的是tail输入插件。它做的事情本质上是模仿 Linux 的tail -F持续跟踪文件新增内容再按行读取。下面是一个比较完整的配置[INPUT] Name tail Path /var/log/web/access.log Tag web.access Mem_Buf_Limit 5MB Skip_Long_Lines On DB /var/log/fluent-bit/web-access.db这里有个容易忽视的点DB参数。它会维护一个本地数据库文件记录当前已经读到了哪个文件的哪个位置。有了它Fluent Bit 重启之后能直接接着上次的位置继续读不会把整份日志重新灌一遍。没有这个文件一旦采集器重启线上日志可能被重复采集Elasticsearch 里就会出现大量重复文档查询结果也会失真。文件轮转也是日志采集中绕不开的问题。业务日志一般都会按天或按大小切割旧文件被改名新文件接管。Fluent Bit 的 tail 插件会通过文件 inode 来识别文件身份轮转发生后它能自动切换到新文件继续读这部分不用我们额外干预。真正麻烦的是旧文件已经被改名为.log.1之类的后缀如果Path通配符没有覆盖到这些文件轮转后的残余内容可能不会被采集这里建议在 Path 里就把轮转文件的通配符一并写进去。Skip_Long_Lines On也很重要它会把超长日志行直接跳过而不至于撑爆 Fluent Bit 的内存缓冲区。某些业务日志充斥着超长的调用栈没有这个保护采集进程内存会一路涨上去。4.2 正则解析把日志拆成结构化字段采集器把日志行读进来后默认只是一个字符串存在message字段里。如果不做解析日志进了 Elasticsearch 也就是一整段文本搜索虽然能用但做状态码统计、耗时聚合这类操作就无从下手。因此我们需要用 Parser 来做结构化。以常见的 Web 服务访问日志为例一行日志大概是这个格式192.168.1.10 - - [10/Dec/2024:10:30:15 0800] GET /api/v1/users HTTP/1.1 200 532 - Mozilla/5.0可以定义如下的正则解析器[PARSER] Name web_access Format regex Regex ^(?remote_addr[^ ]*) (?remote_user[^ ]*) \[(?time_local[^\]]*)\] \(?request[^\]*)\ (?status[^ ]*) (?body_bytes_sent[^ ]*) \(?http_referer[^ ]*)\ \(?http_user_agent[^ ]*)\$ Time_Key time_local Time_Format %d/%b/%Y:%H:%M:%S %z然后在 FILTER 里把 message 字段交给这个解析器处理[FILTER] Name parser Match web.* Key_Name message Parser web_access解析成功之后日志从一整行字符串变成了remote_addr、status、request、http_user_agent等独立字段后续在 Kibana 里就能按状态码做饼图、按 IP 做分布统计。注意Time_Key和Time_Format这两个配置它们决定 Elasticsearch 里timestamp的取值来源。如果不指定会默认用采集时刻作为日志时间这会导致日志时间与业务实际时间不一致排查问题时会非常混乱。解析失败的情况也很常见正则里多一个空格或少一个引号都会让整行数据无法匹配。可以给 Fluent Bit 开启 debug 日志或者先用stdout输出插件把解析结果打到控制台看。我先用测试文件跑确认每个字段都拆对了再正式切换线上路径。4.3 索引模板决定字段类型与分片策略日志数据进了 ES索引命名一般是按天滚动比如web-access-2024.12.10。字段类型默认是 ES 自动推断的但自动推断有一个隐患第一次写入数据时某个字段是什么类型就固定下来了后面再想改就得重建索引非常痛苦。比如状态码status字段如果映射成了long做聚合没问题但如果你希望把它当作字符串来做精确匹配就会很别扭。所以强烈建议提前创建索引模板声明关键字段的类型。可以这样定义一个模板PUT /_template/web-access-template { index_patterns: [web-access-*], settings: { number_of_shards: 1, number_of_replicas: 0 }, mappings: { properties: { timestamp: { type: date }, remote_addr: { type: ip }, request: { type: keyword }, status: { type: keyword }, body_bytes_sent: { type: long }, http_user_agent: { type: text } } } }在单机阶段主分片数设为 1、副本数设为 0 是合理的。副本数在单节点上没有任何意义反而会多存一份数据、多占用磁盘。等以后扩展成多节点集群再通过模板把副本数调整到 1 即可。类型设计上有个建议request这类需要精确匹配、聚合统计的字段用keywordhttp_user_agent这种需要全文搜索的长文本用text。这两种字段的索引方式和检索场景完全不同选错后面再改很费劲。4.4 输出到ES索引命名与连接细节最后一步是把数据输出到 Elasticsearchoutput 插件写法如下[OUTPUT] Name es Match * Host 127.0.0.1 Port 9200 Index web-access Index_Strftime_On On Time_Key timestampIndex_Strftime_On On会按天自动在索引名后面拼接日期生成类似web-access-2024.12.10的索引这也是日志系统最常规的做法方便后续做生命周期管理到期整体删除某一天的索引即可。Time_Key指定 ES 中的时间字段名Fluent Bit 会把解析出来的时间写入该字段。如果是连接 ES 8.xoutput 里要额外加上用户名和密码[OUTPUT] Name es Match * Host 127.0.0.1 Port 9200 Index web-access Index_Strftime_On On HTTP_User elastic HTTP_Passwd your_password配置无误后通过curl http://localhost:9200/_cat/indices?v应该能看到按天生成的索引再配合 Kibana 创建索引模式就能看到结构化字段了。5. 让监控真正“响起来”Kibana可视化和ElastAlert2告警规则5.1 建立索引模式与KQL检索Kibana 里第一步是创建索引模式Index Pattern匹配规则填web-access-*Kibana 会自动识别到所有满足条件的索引并把timestamp设为默认时间字段。这一步做好之后Discover 页面就能查日志了。Kibana 的检索语法是 KQL简单但好用。比如查所有 5xx 请求status 500查某个接口的慢请求request: /api/v1/users AND response_time 2000查某个来源 IP 的异常请求remote_addr: 192.168.1.10 and status: 403KQL 的字段匹配本质是精确匹配和范围查询的组合比之前用 grep 一条条翻日志高效太多。更关键的是当字段类型设置合理时检索性能也稳定。我发现很多人在这一步会忽略一个细节如果字段是text类型但没有设置keyword子字段KQL 里的精确匹配会匹配不到或者行为不符合预期。所以 4.3 节里强调的类型设计在实际检索时会直接体现出来。5.2 可视化报表与DashboardKibana 的图形化功能在日志监控里非常实用。我在 Dashboard 上组合了三个核心图表一个是状态码分布的柱状图按小时统计能快速看到 5xx 是否集中爆发一个是请求量的趋势折线图配合上一期的数据对比能发现流量异常还有一个是请求耗时 Top 榜定位最慢的几个接口。创建图表用的基本都是聚合逻辑状态码分布就是按status.keyword做 terms 聚合请求量趋势是按timestamp做 date_histogram。Dashboard 组合好之后每天早晨扫一眼就知道系统整体是什么状态排查问题不用再逐台机器翻了。5.3 ElastAlert2告警规则类型与配置实例Kibana 本身的告警功能完整版才有开源版里比较方便的还是 ElastAlert2。它是老牌 ElastAlert 的维护分支对较新版本的 ES 兼容性更好也支持不少告警通知渠道。先安装pip install elastalert2然后编写主配置指定 ES 地址、规则目录等rules_folder: /opt/elastalert/rules run_every: minutes: 1 buffer_time: minutes: 5 es_host: 127.0.0.1 es_port: 9200 writeback_index: elastalert_status use_ssl: False verify_certs: Falserun_every表示多久跑一次规则查询buffer_time表示每次查询回溯的时间窗口。这两个参数决定了告警的实时性和准确性的平衡设置太短容易漏数据设置太长又可能延迟过久。常用的规则类型是frequency它在指定时间窗口内命中 N 次就触发告警。比如我想知道 5 分钟内 error 日志出现超过 10 次name: 高频错误告警 type: frequency index: app-log-* num_events: 10 timeframe: minutes: 5 filter: - query: query_string: query: level: ERROR alert: - command realert: minutes: 30这个规则的意思是在任意一个 5 分钟窗口内如果app-log-*索引里有超过 10 条level: ERROR的日志就触发一次告警。realert是关键配置它控制同一规则触发后的冷却时间比如这里设 30 分钟意味着触发之后 30 分钟内即便再次满足条件也不会重复报警能有效减少打扰。5.4 告警实践如何避免半夜被轰炸告警系统上线后最怕的不是没告警而是告警太多让人产生“狼来了”的疲劳感。我踩过这个坑第一版规则太敏感一个服务重启就能触发所有错误规则半夜手机一直在响醒来发现全是误报。后来总结了几条经验。第一realert必须设没有冷却时间任何规则都会变成告警轰炸机。第二能聚合的告警尽量聚合ElastAlert2 支持aggregation可以把多个相同类型的命中合并成一条比如按机器名聚合避免同一问题在十台机器上产生十条告警堆满通知渠道。第三也是最实用的建议上线前先查一下规则命中的情况用 ES 的查询语句模拟一遍。比如上面的高频错误规则先用app-log-*索引查近 5 分钟有多少level: ERROR如果原本就有 100 条那这条规则一上线就会立刻触发说明阈值太低。第四通知渠道建议直接走群机器人的 webhook。配置 webhook 的告警方式实际上就是在 ElastAlert2 的 alert 里填一个 URL并在配置里带上自定义消息模板。这样告警信息会直接推到运维群里相关同事都能看到比邮件更及时。6. 长期运行的底气索引生命周期、时区与性能调优复盘6.1 索引生命周期管理磁盘不爆的底线日志系统跑起来之后最怕的事情就是磁盘被索引占满。日志数据天然只增不减而且增长速度快如果不做清理少则几周多则一两个月就能把磁盘耗尽。ES 提供了索引生命周期管理ILM可以为索引定义阶段策略到期自动删除。一个简单的 30 天删除策略可以这样定义PUT _ilm/policy/30d_delete { policy: { phases: { hot: { min_age: 0ms, actions: {} }, delete: { min_age: 30d, actions: { delete: {} } } } } }然后在索引模板里加上这个策略settings: { number_of_shards: 1, number_of_replicas: 0, index.lifecycle.name: 30d_delete }之后每天新建的索引会自动带上这个策略到 30 天就会自动删除。实际使用中建议按“必要的日志保留时长”来定策略比如审计类日志留 90 天业务 access log 留 30 天错误日志留 7 天。保留时间太短查不到历史数据太长则磁盘消耗快。磁盘容量也别算死正常情况应该按日均日志量的 30 倍估算再留 20% 冗余。6.2 时区问题跨越8小时的真实体验日志监控刚搭好的时候我发现一个奇怪的现象Kibana 里看到的日志时间总比业务实际时间慢 8 小时。原因有两个层面一是 Elasticsearch 存储时间默认用 UTC日志解析时如果Time_Format里带了时区信息ES 会把它转成 UTC 存起来二是 Kibana 默认界面上也按 UTC 显示如果不改显示时区看到的就是 UTC 时间。解决方法是把 Kibana 的默认时区改到东八区位置在Stack Management - Advanced Settings - Timezone选择本地时区即可。同时日志格式上建议从一开始就统一使用带时区的时间格式比如2024-12-10T10:30:1508:00这样无论采集端怎么解析都不会出现歧义。这一点看起来不起眼但实际影响非常大。我见过有人排查问题因为日志时间和实际时间相差 8 小时把一个半小时前的问题误判成刚刚发生整个排查方向都错了。建议搭好系统后第一时间专门验证时间是否对齐。6.3 性能调优从写入提速到查询稳定日志场景的特点是写入量大、查询相对零散针对写入侧调优效果最直接。ES 默认的refresh_interval是 1 秒意味着每秒都会生成新的可见段日志这种数据根本不需要秒级实时可见可以调大到 30 秒甚至 60 秒写入性能提升非常明显代价只是查询可见性延迟几十秒对排查问题没有本质影响。修改方式可以直接加到索引模板里settings: { refresh_interval: 30s }Fluent Bit 侧的Flush参数也值得关注它控制缓存数据多久刷一次到 ES默认 1 秒。如果日志量很大可以适当调大批量大小减少与 ES 的通信次数。配合 ES 端的index.translog.durability: async写入吞吐还能再往上拉一截但相应的故障恢复时间会变长需要权衡。查询侧的问题通常是字段映射不合理导致的。比如把耗时字段映射成text再去做 range 查询ES 只能把所有文档扫一遍性能自然差。这个坑在前期模板设计时避免最有效后面改映射等于重建索引。6.4 常见故障排查速查表最后把这几类高频问题整理成表格基本就是我这一路下来碰到的和帮别人解决的现象常见原因处理办法ES 启动失败vm.max_map_count不足用 root 运行堆内存不足调内核参数创建普通用户调整jvm.options日志没有进入 ESFluent Bit 没启动DB 文件指针问题ES 认证配置缺失查看 Fluent Bit 进程确认 DB 文件位置补齐用户名密码字段检索结果不对text 类型无法精确匹配字段映射已被固化查看映射使用.keyword子字段必要时重建索引时间显示差 8 小时Kibana 时区未设置日志格式未带时区修改默认时区统一日志时间格式磁盘增长过快ILM 策略未生效副本数过多检查索引策略单机副本数改为 0采集进程内存持续上涨日志行超长Mem_Buf_Limit 设置过大解析失败堆积开启Skip_Long_Lines调小缓冲区检查解析规则我自己的体会是EFK 的搭建门槛其实不高真正的分水岭在于日志治理。每台机器的日志格式如果不统一解析规则就会越来越碎片化维护成本会指数级上升。所以建议从第一天就定好日志规范统一时间格式、统一字段分隔方式、统一业务标识。等这套系统稳定跑上一两个月再回头看曾经那种 ssh 上去 grep 的日子你会觉得真的回不去了。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑