日志采集与存储:ELK/EFK 架构与实战配置

日志采集与存储,是整个性能分析体系的基石。你想想看,如果连日志都收不全、存不好,后面再牛的分析工具也是白搭。这一章,我就带你看看生产环境中常用的 ELK 和 EFK 架构,以及我在实际项目中踩过的那些坑。

ELK vs EFK:选哪个?

ELK 是 Elasticsearch、Logstash、Kibana 的缩写。EFK 则是把 Logstash 换成了 Fluentd。说白了,核心区别就是采集器的选择。

对比维度 Logstash Fluentd
资源消耗 较高,Java 进程,默认占用 1GB+ 内存 较低,Ruby/C 混合,内存占用约 50-200MB
插件生态 丰富,200+ 插件,社区活跃 也很丰富,1000+ 插件,Kubernetes 原生支持好
配置复杂度 配置语法灵活,但容易写复杂 配置简洁,基于 JSON 或 YAML
性能 吞吐量高,但需要调优 JVM 吞吐量中等,但稳定,适合容器化部署
适用场景 传统架构、需要复杂数据处理的场景 云原生、Kubernetes、边缘采集场景

我个人习惯是:如果团队对 Java 栈熟悉,且日志量在每天 TB 级以下,用 Logstash 没问题。但如果跑在 Kubernetes 上,或者对资源敏感,我会优先选 Fluentd。记得有一次,我在一个 16GB 内存的节点上同时跑了 Logstash 和业务应用,结果 OOM 了……后来换成 Fluentd,内存直接降到 150MB。

Logstash 配置实战

Logstash 的配置分三段:input、filter、output。我直接给一个生产级的例子。

input {
  beats {
    port => 5044
    ssl => true
    ssl_certificate => "/etc/logstash/certs/logstash.crt"
    ssl_key => "/etc/logstash/certs/logstash.key"
  }
}

filter {
  grok {
    match => { "message" => "%{COMBINEDAPACHELOG}" }
  }
  date {
    match => [ "timestamp", "dd/MMM/yyyy:HH:mm:ss Z" ]
    target => "@timestamp"
  }
  mutate {
    remove_field => [ "message", "original" ]
  }
}

output {
  elasticsearch {
    hosts => ["https://es-node1:9200", "https://es-node2:9200"]
    index => "nginx-access-%{+YYYY.MM.dd}"
    user => "logstash_writer"
    password => "${ES_PASSWORD}"
    ssl => true
    cacert => "/etc/logstash/certs/ca.crt"
  }
}
小技巧:grok 正则调试很痛苦。我建议先用 Kibana 自带的 Grok Debugger 测试,或者用 stdout { codec => rubydebug } 输出到控制台看解析结果。别问我怎么知道的——我曾经花了一下午调一个时间戳格式,最后发现是时区没设对。

Fluentd 配置实战

Fluentd 的配置更简洁,适合容器化环境。下面是一个采集 Docker 容器日志的例子。

<source>
  @type tail
  path /var/log/containers/*.log
  pos_file /var/log/fluentd-containers.log.pos
  tag kubernetes.*
  format json
  read_from_head true
</source>

<filter kubernetes.**>
  @type kubernetes_metadata
  @id filter_kube_metadata
</filter>

<match kubernetes.**>
  @type elasticsearch
  host "#{ENV['ES_HOST']}"
  port "#{ENV['ES_PORT']}"
  user "#{ENV['ES_USER']}"
  password "#{ENV['ES_PASSWORD']}"
  logstash_format true
  logstash_prefix k8s-logs
  flush_interval 5s
</match>
注意:Fluentd 的 pos_file 很重要。它记录了文件读取位置,防止重启后重复采集。我曾经因为忘记配这个,导致 ES 集群被重复数据撑爆。嗯,血的教训。

日志分片与索引策略

日志存进 Elasticsearch 后,怎么管理?核心就是分片和索引策略。分片决定了数据的并行处理能力,索引策略决定了查询效率和存储成本。

分片设计原则

  • 分片数 = 节点数 × 1-2:太多分片会导致集群管理开销大,太少则无法利用多节点并行。
  • 每个分片大小控制在 20-50GB:超过 50GB 后,查询和恢复都会变慢。
  • 主分片数在创建索引时确定,无法修改:所以一开始就要规划好。我一般按未来 6 个月的数据量估算。

索引生命周期管理(ILM)

手动管理索引太累了。Elasticsearch 提供了 ILM,可以自动完成索引的创建、滚动、冷热迁移和删除。

PUT _ilm/policy/logs_policy
{
  "policy": {
    "phases": {
      "hot": {
        "min_age": "0ms",
        "actions": {
          "rollover": {
            "max_size": "50GB",
            "max_age": "1d"
          },
          "set_priority": {
            "priority": 100
          }
        }
      },
      "warm": {
        "min_age": "7d",
        "actions": {
          "allocate": {
            "require": {
              "data_type": "warm"
            }
          },
          "forcemerge": {
            "max_num_segments": 1
          },
          "shrink": {
            "number_of_shards": 1
          }
        }
      },
      "delete": {
        "min_age": "30d",
        "actions": {
          "delete": {}
        }
      }
    }
  }
}
核心思路:热节点写入,7 天后移到温节点合并段,30 天后自动删除。这样既保证了写入性能,又控制了存储成本。我在一个日增 500GB 日志的项目中,靠这套策略把存储成本降了 60%。

架构总览:一张图看懂

下面这张图展示了完整的日志采集与存储链路。从应用产生日志,到最终在 Kibana 上可视化,每一步都有讲究。

日志采集与存储架构图 应用服务器 Nginx / Java / Python 容器 / K8s Docker / Pod 日志 网络设备 Syslog / SNMP 日志采集器 Logstash (Beats / TCP / Syslog) Fluentd (Tail / HTTP / Forward) 消息队列(可选) Kafka / Redis / RabbitMQ Elasticsearch 集群 热节点 / 温节点 / 冷节点 ILM 自动管理索引 Kibana 可视化 Dashboard / 告警 / 分析 冷存储 S3 / HDFS / NAS 说明:采集器负责收集日志,可选消息队列削峰填谷,最终存入 ES 集群,通过 ILM 策略自动管理索引生命周期

避坑指南

  • 分片数别拍脑袋定:我曾经给一个索引设了 30 个分片,结果集群只有 3 个节点,每个节点 10 个分片,查询慢得要死。后来改成 6 个分片,性能翻倍。
  • ILM 的 rollover 条件别设太激进:比如 max_size: 10GB 且 max_age: 1h,会导致每小时生成一个新索引,分片数爆炸。我一般设 50GB 或 1 天。
  • Logstash 的 JVM 堆大小要调:默认 1GB 不够,但也不要超过物理内存的 50%。我习惯设 -Xms4g -Xmx4g,配合 pipeline.workers 参数调整并发。
  • Fluentd 的 buffer 要配:不加 buffer 的话,ES 挂了数据就丢了。用 @type file 做持久化 buffer,保证数据不丢。
我的经验:日志采集链路中,最脆弱的往往是网络。我建议在采集器和 ES 之间加一层 Kafka,既能削峰,又能解耦。即使 ES 挂了,数据也不会丢。等 ES 恢复后,Kafka 自动回放,完美。