凌晨两点被电话叫醒说某个核心接口大面积超时。爬起来打开电脑结果发现日志平台自己先趴了——采集端堆着十几个 G 没发出去的文件处理端磁盘写满直接退出检索端查一条报错要转二十秒。故障排查最怕的不是没日志而是你需要日志的那一刻它刚好不工作。这件事之后我把整套日志链路推倒重来ELK 和 FELK 这两套架构的取舍也是那段时间被反复验证出来的。ELK 是 Elasticsearch、Logstash、Kibana 三个组件的组合负责日志收集、存储检索和可视化分析FELK 是在前面加一个 Filebeat 做轻量采集把 Logstash 后移成处理层。它们要解决的核心问题就一个把散落在几十上百台机器上的、格式各异的、每天几十上百 G 的文本变成能秒级检索、能画图、能报警的结构化数据。这套东西适合谁运维和 SRE 要做故障定位后端开发想追一次请求的全链路安全同学要留痕审计甚至业务方想看接口耗时分布——只要你手上超过五台机器、日志还在靠tail -f和grep那就该认真考虑这套方案了。下面我按采集、传输、处理、存储、可视化、排障的顺序把每个环节的真实做法和踩过的坑掰开讲。1. 先把架构讲透ELK 与 FELK 差在哪一个字母1.1 四个组件各自的职责边界很多人上手就把四个组件全装上配置文件抄一份跑通了就完事。但真出问题的时候你会发现根本不知道该去哪个环节看。所以第一步必须把职责切清楚边界模糊的架构迟早会在半夜给你惊喜。Elasticsearch是存储和检索引擎本质是一个分布式的倒排索引数据库。它负责把文本切成词条建索引、按分片存储、提供近实时的查询能力。它的强项是检索弱项是写入放大——每写一条数据要建索引、要刷盘、要同步副本所以它天然不适合当消息队列用。Logstash是数据处理管道输入端接各种数据源中间做解析、清洗、富化输出端写往各种目的地。它真正的价值在中间的 filter 段把一个2024-05-11 03:22:11 ERROR [order-service] traceIdabc123 ...的原始行拆成 timestamp、level、service、traceId、message 五个独立字段。它的弱项是资源占用JVM 起底就是几百兆到上 G 内存装在业务机器上会被开发和运维一起骂。Kibana是可视化前端负责查询界面、图表、仪表盘和告警。它本身不存数据所有查询都转发给 Elasticsearch所以 Kibana 慢通常是 Elasticsearch 慢。Filebeat是轻量采集器Go 写的常驻内存几十兆。它只干一件事盯着日志文件把新增的行读出来加上元数据发出去。它支持背压感知——下游不通就自己慢下来不会把业务机器拖死。理解了这四个东西的性格架构选择就变成了一道简单的匹配题谁重谁轻谁该放在离数据源近的地方谁该放在离存储近的地方。1.2 为什么把 Logstash 往后挪一位传统 ELK 的做法是把 Logstash 直接装在业务机器上Filebeat 出现之后主流做法变成了 FELK业务机上只留 FilebeatLogstash 集中部署在中间层。这个调整的理由很实在。第一是资源隔离Logstash 的 JVM 一旦 GC 抖动业务进程跟着遭殃我见过一次因为 Logstash 堆内存不足反复 Full GC把同机的服务响应时间从 20ms 拖到 800ms。第二是配置管理采集配置散在几十台机器上改一个字段要全量推一遍集中到 Logstash 之后改一处就生效。第三是解耦业务机器不需要知道日志最终去哪它只负责发出去后面接 Kafka 也好、直接进 Logstash 也好对采集端是透明的。代价也有多了一跳网络传输端到端延迟从秒级变成几秒级Filebeat 的解析能力弱复杂的多行合并、字段提取只能往后放。但对绝大多数场景来说这点延迟换来的是稳定性和可维护性非常划算。需要说明的是业内对 FELK 的字母展开并不完全统一有的场景指 Filebeat 打头的这套链路有的场景指的是 Fluentd 系的轻量采集方案还有的把 Kafka 也算进去叫 FELK 的变体。你看到别人说 FELK 时先问清楚他指的是采集端用谁别急着对号入座。1.3 架构选型的三个判断维度不要迷信业界最佳实践选型的依据只能是你的实际约束。我一般从三个维度来判断。判断维度小型场景20 台机器20GB/天中型场景20~200 台20~200GB/天大型场景200 台200GB/天采集端Filebeat 直发 LogstashFilebeat 发 KafkaFilebeat 发 Kafka分区按业务划分处理层单机 Logstash2~4 核2~3 节点 Logstash 集群独立 Logstash 集群按业务拆管道缓冲层不需要或磁盘队列兜底Kafka 3 节点起Kafka 集群 副本数 2~3存储层单节点 ES1 副本可省3 节点 ES1 副本5 节点以上冷热分层典型痛點磁盘容易写满Logstash 成为瓶颈分片数失控、集群状态频繁抖动小场景硬上 Kafka 是典型的过度设计运维成本翻倍收益几乎为零。大场景图省事直连那 Kafka 存在的意义就是在流量峰值和 ES 抖动之间当缓冲垫省掉它等于把风险全压给存储层。判断标准其实就一条你的日志峰值写入速率和 ES 的稳定写入能力之间差距有多大。差距小于两倍磁盘队列能扛差距大于五倍老老实实上缓冲层。2. 采集层设计Filebeat 不是装上就完事2.1 输入配置与多行日志合并的关键细节Filebeat 的配置文件看着简单但几个参数配错就会导致丢数据或者数据错乱。下面这份是我在生产环境跑了很久的模板做了删减只留关键项。filebeat.inputs: - type: filestream id: app-java-logs paths: - /data/logs/*/app.log parsers: - ndjson: target: add_error_key: true - multiline: type: pattern pattern: ^\d{4}-\d{2}-\d{2} negate: true match: after max_lines: 500 timeout: 5s fields: env: prod app: order-service log_type: application fields_under_root: true close.on_state_change.removed: true clean_removed: true processors: - add_host_metadata: when.not.contains.tags: forwarded - drop_fields: fields: [agent, ecs, input, host.architecture, host.os] ignore_missing: true output.kafka: hosts: [10.0.0.11:9092, 10.0.0.12:9092, 10.0.0.13:9092] topic: logs-app partition.hash: hash: [app, host.name] required_acks: 1 compression: gzip max_message_bytes: 10485760几个值得展开讲的点多行合并的negate: truematch: after组合。这条配置的意思是不匹配^\d{4}-\d{2}-\d{2}这个正则的行合并到上一行后面。Java 的堆栈信息、Python 的 traceback 都是这种形态——首行有时间戳后续行是缩进的异常详情。如果写反了变成negate: false整个日志会被切得七零八落你在 Kibana 里搜到的全是半截堆栈。max_lines和timeout必须显式设置。默认值在某些版本里是 500 行但如果你有超长堆栈超出的部分会被当作新事件发出去。timeout更重要它决定了多久没等到新行就认为这条日志结束了设太小会截断慢速输出的日志设太大则最后一条日志迟迟不发出。5 秒是个经验值日志密集的服务可以调到 2 秒。clean_removed这个参数容易被忽略。日志轮转后旧文件被删Filebeat 的注册表里还留着记录时间长了注册表文件会膨胀到几百兆启动变慢。打开它能自动清理。提示不要用type: log那是老版本配置。filestream是新版推荐方式对文件轮转和 inode 复用处理得更稳特别是容器环境下同一个路径反复创建文件的情况。2.2 字段设计与元数据规范日志平台建设初期最容易犯的错是把所有信息都塞进message一个字段里。等到要按服务维度聚合、按环境过滤、按 traceId 串联请求的时候你才发现自己在用grep的思维做检索。我的做法是在采集端就强制约定一套基础字段作为所有日志的身份证字段名类型说明是否必填envkeyword环境标识prod/staging/dev是appkeyword应用名用中划线命名是host.namekeyword主机名或容器名是log_typekeywordapplication/access/audit/gc是levelkeyword日志级别统一大写否traceIdkeyword链路追踪 ID否timestampdate事件发生时间是为什么这些字段都用keyword而不是text因为text会被分词器切开order-service可能被切成order和service两个词条聚合的时候你按应用名分组会得到一堆碎片。keyword是整串存储精确匹配和聚合都快。这条规则看似基础但我见过太多人在 mapping 定型之后才发现聚合结果不对只能重建索引。还有一点timestamp必须用日志本身的时间不能用采集时间。Filebeat 默认会用读到的时刻打时间戳如果日志里自带时间一定要用dateprocessor 或者 Logstash 的datefilter 覆盖掉。否则你补采历史日志的时候所有数据的时间戳都是现在时序图完全错乱。2.3 采集层的资源与背压控制Filebeat 虽然轻但在高吞吐场景下也需要调优。三个参数最关键queue.mem: events: 8192 flush.min_events: 512 flush.timeout: 1s output.kafka: bulk_max_size: 2048 worker: 4 max_procs: 2queue.mem.events是内存队列长度决定了下游卡顿时能缓多少事件。设太小下游一抖就丢数据filestream会停读但如果注册表在队列清空前更新还是有丢失风险设太大内存占用上去了业务机器压力增加。8192 到 16384 是常见区间。max_procs限制 Filebeat 能用几个 CPU 核业务机器上一定要设默认它会用满所有核抢 CPU。关于背压需要理解一个机制Filebeat 是至少一次投递语义它先把事件发出去收到确认才更新注册表位置。所以下游不可用时它不会丢数据而是停止读取文件。这意味着磁盘上的日志会持续堆积——这不是 Filebeat 的问题而是下游容量不足的暴露。真正该做的是加监控盯住filebeat_pipeline_events_retry_total和文件读取偏移量的差值超过阈值就告警。3. 传输与缓冲层Kafka 到底要不要上3.1 直连模式与缓冲模式的取舍直连模式下Filebeat 直接输出到 Logstash 的 beats 端口链路短、延迟低、部署简单。适用条件是日志量稳定、Logstash 有足够冗余、ES 集群健康。但真实世界里日志量从来不稳定。一次批量任务、一次大促、一次容器集体重启写入量瞬间翻十倍。直连模式下这个尖峰直接砸到 LogstashLogstash 的管道队列满了之后开始拒绝连接Filebeat 只能干等。而此时如果 Logstash 在做一次全量重启或者配置热加载中间那几分钟的数据就全靠 Filebeat 的内存队列兜着稍有不慎就是数据丢失。Kafka 在这里扮演的角色不是必须有的高端组件而是一个削峰填谷的蓄水池。它有三个实打实的好处解耦生产消费速度、支持多消费方同一份日志既能进 ES 也能进离线数仓、支持回溯消费位点可以重置重新处理历史数据。代价也明确多一套集群要运维多一层端到端延迟多一个可能出故障的组件。如果团队里没人熟悉 Kafka 的调优和故障处理上了它反而是增加了不确定性。3.2 Kafka 主题与分区规划决定上 Kafka 之后主题设计会直接影响后续的扩展性。我推荐按日志类型分主题而不是按应用分主题名分区数副本数保留时长说明logs-app623 天应用日志logs-access623 天网关/接入日志logs-audit337 天审计日志保留更久logs-gc311 天GC 日志量大价值低按类型分的原因不同类型日志的保留策略、处理复杂度、消费速度完全不同。审计日志要长期留存且不能丢GC 日志量大但可以快速丢弃混在一个主题里只能按最严格的标准来浪费存储。分区数的估算逻辑是单分区消费吞吐上限大约 10MB/s保守值你的峰值写入如果是 60MB/s那至少要 6 个分区。分区数只能增不能减所以宁可从宽。但也别盲目开 50 个分区多了会导致同时打开的文件句柄数暴增而且分区数一旦超过消费端并行度就是浪费。分区键的选择有个小技巧用apphost.name做 hash。这样同一个应用同一台机器的日志有序既保证了对同一份文件的内容不乱序又能让消费端多实例并行。3.3 容量估算的实际算法很多人做容量规划靠拍脑袋结果要么存储不够要么买多了浪费。给一套可以直接套用的算法。假设条件200 台业务机每台每天产生 1GB 原始日志副本数 1保留 30 天。第一步算日增量200 × 1GB 200GB/天。第二步算压缩后体积。纯文本日志的压缩比通常在 0.15 到 0.25 之间取 0.2200 × 0.2 40GB/天。这个压缩比指的是 ES 的索引压缩index.codec: best_compression如果用默认的 LZ4 大概是 0.35 到 0.45。第三步加副本40 × 2 80GB/天1 主 1 副。第四步算总容量80 × 30 2400GB约 2.4TB。第五步留冗余一般按 1.3 倍算2400 × 1.3 ≈ 3.1TB。这是磁盘的裸容量需求考虑到 ES 建议单节点磁盘使用率不超过 75%实际采购要 4TB 以上。分片数则按单分片 30~50GB来推算2400 / 50 48个主分片。如果按天建索引保留 30 天每天 2 个主分片就够了。这个数字看起来很保守但分片过多是 ES 集群性能下降的头号原因——每个分片都是一个 Lucene 实例都要占堆内存和文件句柄1000 个分片的集群光维持状态同步就很吃力。4. 处理层Logstash 管道怎么写才不拖后腿4.1 三段式管道的结构设计Logstash 的配置文件分input、filter、output三段看起来直白但结构组织得好不好直接决定了后期维护成本。我的习惯是拆文件inputs.conf、filters-app.conf、filters-common.conf、outputs.conf用path.config指向目录Logstash 会按文件名顺序合并。input { kafka { bootstrap_servers 10.0.0.11:9092,10.0.0.12:9092 topics [logs-app] group_id logstash-app codec json consumer_threads 4 decorate_events basic } } filter { # 时间解析把日志里的时间字符串转成 timestamp date { match [log_time, yyyy-MM-dd HH:mm:ss.SSS, ISO8601] target timestamp tag_on_failure [_dateparsefailed] } # 结构化解析 grok { match { message %{TIMESTAMP_ISO8601:ts}\s%{LOGLEVEL:level}\s\[%{DATA:thread}\]\s%{DATA:logger}\s-\s%{GREEDYDATA:msg} } tag_on_failure [_grokparsefailure] break_on_match true } # 字段规范化 mutate { uppercase { level } remove_field [ts, thread, host, agent, ecs] convert { cost_ms integer } } # 丢弃低价值日志降低存储压力 if [level] DEBUG and [env] prod { drop {} } } output { elasticsearch { hosts [http://10.0.1.21:9200, http://10.0.1.22:9200] index logs-app-%{YYYY.MM.dd} user ${ES_USER} password ${ES_PASSWORD} manage_template false pool_max 1000 flush_size 2000 idle_flush_time 1 } }specific细节里有两个关键点值得单独说。一是consumer_threads要和 Kafka 分区数匹配。6 个分区配 4 个线程会有 2 个分区分不到消费线程出现消费端看起来很闲但 lag 一直在涨的假象。正确做法是线程数等于或略小于分区数如果不够就扩 Logstash 实例多个实例同属一个group_id会自动分摊分区。二是flush_size和idle_flush_time的配合。flush_size: 2000表示攒够 2000 条批量写一次 ESidle_flush_time: 1表示就算没攒够1 秒也要写一次。前者影响吞吐批量大效率高后者影响延迟批量大但流量低的时候数据会卡住。低峰期日志量小如果只设flush_size不设idle_flush_time可能出现日志几分钟才出现在 Kibana 里。4.2 Grok 解析的性能陷阱Grok 是 Logstash 里最强大也最容易出问题的部分。它的原理是把%{PATTERN:field}展开成正则GREEDYDATA会展开成.*而.*在正则引擎里是贪婪匹配遇到长字符串时回溯次数会爆炸。一个典型反例grok { match { message %{GREEDYDATA:prefix} %{GREEDYDATA:suffix} } }这条规则在遇到几 KB 的日志行时正则引擎会尝试所有可能的分割点CPU 直接打满。我见过一次因为改了这么一行Logstash 的 CPU 从 30% 飙到 400%Kafka 堆积到几百万条。正确的做法是三条第一尽量用具体模式代替DATA和GREEDYDATA。%{IP:client_ip}、%{NUMBER:cost:int}、%{WORD:method}这些都有明确的字符集约束回溯空间小得多。第二把最常用的格式写在最前面并用break_on_match true。默认 Logstash 会把所有 match 都尝试一遍加上这个参数后一旦匹配成功就停。第三用自定义正则精确定位。比如固定格式的日志直接写锚定正则grok { match { message ^%{TIMESTAMP_ISO8601:ts}\s%{LOGLEVEL:level}\s\[%{DATA:thread}\]\s%{NOTSPACE:logger}\s-\s%{GREEDYDATA:msg}$ } }如果日志格式完全是自己控制的最省事的方案是直接输出 JSONcodec json一行解析完比任何 grok 都快。这条建议看着朴素但效果立竿见影——我让一个团队把日志改成 JSON 输出后Logstash 节点从 3 台减到 1 台CPU 还降了一半。4.3 落库前的字段瘦身与索引模板数据进 ES 之前还有两道处理要做很多人会漏掉。第一道是字段裁剪。Filebeat 默认会加一堆元数据agent.ephemeral_id、agent.id、ecs.version、host.os.*、input.type……这些字段在检索时基本没用但每个都要建索引、占存储。用mutate { remove_field [...] }干掉能省 10% 到 20% 的存储空间。第二道是索引模板。不要在 Logstash 里用默认的动态映射提前定义好模板{ index_patterns: [logs-app-*], template: { settings: { number_of_shards: 2, number_of_replicas: 1, index.refresh_interval: 30s, index.codec: best_compression, index.mapping.total_fields.limit: 1000 }, mappings: { dynamic: strict, properties: { timestamp: { type: date }, message: { type: text, fields: { raw: { type: keyword, ignore_above: 1024 } } }, level: { type: keyword }, app: { type: keyword }, env: { type: keyword }, traceId: { type: keyword }, cost_ms: { type: long } } } } }dynamic: strict是最反直觉但最有价值的设置。它意味着任何不在 mapping 里的字段写入时直接报错而不是自动猜测类型。这看起来很不友好但它能逼着你在上线前把字段定义清楚。一旦开了dynamic: true默认值某个服务偶尔往日志里塞一个userId12345字符串、另一个服务塞userId12345数字ES 就会因为类型冲突拒绝整批写入而这个错误在 Logstash 日志里只是一行 warning很容易被忽略。index.refresh_interval: 30s也是必调项。默认 1 秒刷新一次意味着每秒都要生成一个新的 segment写入压力大的时候段合并根本跟不上。日志场景对实时性要求没那么高30 秒完全够写入吞吐能提升 30% 以上。5. 存储与检索索引设计决定集群寿命5.1 分片规划与节点角色划分分片数怎么定前面给了估算方法这里补充几个实操中必须注意的约束。单分片大小控制在 10GB 到 50GB 之间。小于 10GB 说明分片太碎元数据开销占比高大于 50GB 会导致恢复慢、段合并压力大。日志场景如果是按天建索引日增量小于 20GB 的应用完全可以用周索引或者月索引减少分片总数。每个节点的分片数不要超过 20 × CPU 核数。这个经验公式的来源是 Lucene 的并发能力超过之后查询会排长队。一台 16 核的节点分片数上限大概 300 个。节点分工要明确。中小规模至少分三类角色master 节点3 个只负责集群状态不做数据、data 节点存数据、coordinating 节点接收查询请求并汇总结果。如果只有一个角色全混在一起一次大查询就能把 master 拖到集群失去响应。冷热分层是另一个值得早点做的设计。热节点用 SSD存最近 7 天的索引负责写入和高频查询温节点用大容量 HDD存 7 到 30 天的索引只读不写超过 30 天的直接删除或者归档到对象存储。一套 ILM 策略就能自动完成这件事{ policy: { phases: { hot: { min_age: 0ms, actions: { rollover: { max_primary_shard_size: 40gb, max_age: 1d }, set_priority: { priority: 100 } } }, warm: { min_age: 7d, actions: { forcemerge: { max_num_segments: 1 }, shrink: { number_of_shards: 1 }, set_priority: { priority: 50 } } }, delete: { min_age: 30d, actions: { delete: {} } } } } }rollover配合max_primary_shard_size比按天滚动更科学因为它根据实际大小决定何时切分日志量忽大忽小时不会出现某些索引只有 1GB、某些索引 100GB 的畸形。forcemerge在进入温阶段时把段数合并到 1能把存储压下来 20% 到 30%查询也更快。注意forcemerge很耗 IO一定要放在业务低峰期或者用限速。5.2 索引命名的坑索引命名看起来是小事但踩过坑的都知道疼。不要用%{YYYY.MM.dd}这种纯日期加应用名的方式因为同一应用的不同日志类型应用日志、访问日志、GC 日志会被写进同一个索引字段类型冲突的概率大增。推荐格式是logs-{类型}-{应用}-{日期}例如logs-app-order-service-2024.05.11。索引名不能包含大写字母不能包含\ / * ? |空格和逗号。这条规则看着无关紧要但如果你用%{[app]}做索引名恰好某个应用的app字段配置成了OrderService整批数据就会写入失败。保险做法是索引名里只用固定前缀加日期应用维度用字段过滤来区分。还有一个容易忽略的点索引名长度别超过 255 字节而且过长的索引名会让某些 API 调用失败。曾经有人用完整的主机名加路径做索引名结果集群的_cat/indices直接报错。6. 可视化层别把 Kibana 做成柱状图展览6.1 从能看到能定位的仪表盘设计我见过太多日志平台仪表盘做得花里胡哨饼图柱状图折线图应有尽有但真正出故障的时候没人看。原因很简单这些图回答不了哪儿出了问题。一个能用的仪表盘应该按照排查路径来组织我把它叫做三级下钻结构第一屏是全局健康度回答现在有没有异常。只放四个东西最近 24 小时各服务错误率排行表格、错误数量时间趋势折线图、当前告警数数字、日志写入速率面积图。判断标准是能否在 5 秒内看出哪个服务在出错。第二屏是服务详情从第一屏点击某个服务进来回答错在什么地方。展示该服务的错误类型分布按 message 聚合、错误随时间的分布、涉及的接口或模块 Top 10。这层用Saved Search配合过滤条件是最灵活的。第三屏是原始日志从第二屏点击某个错误类型进来展示具体日志行只保留时间、level、traceId、message 四列其余藏起来。这一屏的目标是让人能直接复制 traceId 去查全链路。这套结构的关键是每一屏都只回答一个问题不贪多。图表的数量控制在 6 到 8 个以内超过之后人的注意力就散了。字段设置上有个小技巧把message字段的Discover展示高度调大把level用颜色标记ERROR 红色、WARN 黄色把常用字段固定在左侧。这些设置在 Kibana 的 Index Pattern 里配配一次全组受益。6.2 该配哪些告警告警不在多在于准。日志平台的告警我分成三类告警类型触发条件通知渠道处理时限链路自身健康采集延迟 5 分钟、Kafka 消费 lag 100 万值班群15 分钟业务错误突增5 分钟内 ERROR 数量 平峰 3 倍值班群 短信5 分钟存储容量单节点磁盘使用率 80%、分片数 800值班群1 小时第一类最容易被忽略但最重要。日志链路自己坏了没人发现等到要查故障时才抓瞎。建议加一个独立的探针服务每分钟往日志文件写一条带固定标记的记录然后从 Kibana 查是否存在。这条记录端到端走完全链路缺了就是链路断了比任何内部指标都直观。第二类告警要防止误报。直接按ERROR 数量做阈值会在发布、重启、批量任务时疯狂刷屏。我的做法是配置一个基线取过去 7 天同一时段的中位数超过中位数的 3 倍才触发并且同一告警 10 分钟内只发一次。7. 常见问题排查实录踩过的坑才是真经验7.1 数据不落库的排查链路日志在文件里明明有Kibana 就是搜不到——这是被问得最多的问题。我整理了一条固定的排查路径从下游往上倒着查最快能定位到 90% 的问题。第一步查 ES 索引。执行GET _cat/indices/logs-app-*?vsindex:desc看最新索引的docs.count是不是在涨。不涨说明数据根本没进来往上游继续在涨说明数据进来了那可能是查询条件、时间范围或者 mapping 的问题——重点检查是不是message字段用了keyword类型导致搜不到分词内容。第二步查 Logstash 输出。GET _cat/thread_pool/write?v看write队列的 rejected 数或者直接看 Logstash 日志里有没有Could not index event to Elasticsearch。常见的报错有两类mapping 冲突前面提到的字段类型打架和illegal_argument_exception: Limit of total fields字段数超限。第三步查 Kafka 消费延迟。kafka-consumer-groups.sh --describe --group logstash-app看 LAG 列。lag 持续增长说明消费跟不上要么加实例要么检查是不是某条日志触发了 grok 性能问题。第四步查采集端。GET /_stats看 Filebeat 的output.events.acked和pipeline.events.published是否同步增长。看服务本身的监控页面或者日志确认 Filebeat 进程还在。曾经有一次Filebeat 因为所在主机磁盘 inode 耗尽配置文件都读不出来进程活着但什么也不做。第五步查日志文件本身。确认路径通配符有没有匹配到、文件权限对不对、有没有做日志轮转导致 Filebeat 跟丢了文件表现为harvester数量异常。这条链路的关键是从下游往上游倒着走因为每一层都能告诉你我收到没有。如果从上游往下查你得逐层确认发了和收到两个动作效率低一倍。7.2 常见问题速查表现象大概率原因处理方式Kibana 时间范围选最近 15 分钟查不到数据选最近 24 小时能查到timestamp用的是采集时间而非日志时间或者时区没处理检查 Logstash 的datefilter确认时区配置数据量突然翻倍日志被重复采集常见于容器重启后 inode 复用检查 Filebeat 注册表确认filebeat.registry.path是否持久化搜索一个 ID 搜不到但明明日志里有字段类型是text被分词器切碎了改用keyword类型或用field.raw子字段查询ES 写入变慢、CPU 高refresh 太频繁、分片太多、字段太多调refresh_interval合并小索引清理无用字段集群状态变黄且一直不恢复有副本分片未分配通常是节点数少于副本数 1增加数据节点或临时调低副本数Logstash 内存持续上涨直到 OOM管道队列积压或者某个 filter 有内存泄漏检查pipeline.batch.size排查正则和translatefilterKibana 打开很慢默认时间范围太大或者在跨大量分片做聚合缩小默认时间范围避免对高基数字段做 terms 聚合7.3 几条用血换来的避坑心得不要在日志里打敏感信息。密码、令牌、完整的手机号和身份证号一律脱敏。这件事的重要性不用强调但很多人是在数据已经躺在索引里几个月之后才发现。在 Logstash 里加一条mutate { gsub [message, (?i)password\S, password***] }成本极低。日志平台也要做容量演练。我做过一次日志洪峰演练故意让某个服务循环打日志结果两小时内就把 ES 磁盘写到 90%触发了只读锁。磁盘写满后的 ES 会进入read_only_allow_delete状态需要手动解除慌乱之下很容易操作失误。所以磁盘水位告警一定要提前80% 就该动手了。保留策略别一刀切。我见过有人所有索引都保留 90 天结果 GC 日志占了 60% 的存储。不同类型日志的价值密度差别巨大审计日志留一年都不多DEBUG 日志留三天都嫌久。按类型配置不同的 ILM 策略能省出一半硬件成本。升级前先在沙箱跑一遍全链路。ES、Logstash、Kibana 的版本兼容性有严格约束客户端版本和集群版本差太多会报各种诡异的错。而且升级 ES 大版本往往涉及索引格式变更旧索引可能读不了。沙箱验证的内容包括现有索引模板能否导入、现有管道能否启动、现有查询能否返回正确结果、仪表盘是否正常渲染。这套检查清单我列过一版四十分钟能跑完比在生产上发现问题再回滚强太多。我个人在实际操作中的体会是日志平台的价值不在于组件装了多少、图做得有多漂亮而在于出问题时你有多信任它。如果每次故障你都怀疑是不是日志又漏了那这套系统就没起到作用。把链路的每一段都加上独立的健康检查让数据从采集到展示全程可验证比任何花哨的功能都值钱。另外补充一个小技巧给每条日志的采集端固定写入一个带实例编号的source_id字段排查时能一眼看出是哪台机器哪条链路产生的数据比翻注册表快得多。