Spring Cloud 微服务日志收集小记:Docker 部署 EFK(Elasticsearch + Filebeat + Kibana)

64 阅读9分钟

由于最近我们线上服务节点越来越多,项目组员反馈通过远程ng代理获取每个节点日志文件的方式不方便排查线上问题,为了节省排查时间故而需要搭建一套efk服务供大家及时排查问题,我本人先基于docker完成环境的搭建,后续方便服务器的快速部署。

本文记录了在 Mac 本地开发环境中,使用 Docker 部署 EFK 日志收集系统的完整过程,包括踩过的坑和最终的解决方案。

一、背景

1.1 为什么需要集中式日志系统

在微服务架构下,一个请求可能经过网关、多个业务服务,日志分散在各个服务的日志文件中。当生产环境出现问题时:

  • 需要登录多台服务器查看日志
  • 需要根据时间戳手动关联多个服务的日志
  • 无法快速搜索和过滤
  • 无法统计错误趋势

集中式日志系统可以解决这些问题:所有服务的日志汇聚到一处,支持全文搜索、时间范围过滤、可视化分析。

1.2 ELK vs EFK:为什么不用 Logstash

传统的 ELK 架构:

应用日志 → Logstash → Elasticsearch → Kibana

本文采用的 EFK 架构:

应用日志 → Filebeat → Elasticsearch → Kibana

为什么选择 Filebeat 而不是 Logstash?

对比项LogstashFilebeat
资源占用重量级,JVM 运行,内存占用 1GB+轻量级,Go 编写,内存占用 50MB 左右
功能定位数据处理管道,支持复杂的过滤和转换日志采集器,专注于收集和转发
适用场景需要复杂数据处理(如多数据源聚合、格式转换)简单的日志收集场景
部署方式通常集中部署通常部署在每台应用服务器上

我的场景:

  • 目前服务节点刚刚增多且资源有限,我们先观测Filebeat能否支撑目前的场景
  • 日志格式统一(Logback 标准格式)
  • 只需要简单的字段提取,Filebeat 的 dissect 处理器足够
  • 不需要复杂的数据转换

所以选择更轻量的 Filebeat,省去 Logstash 这一层。

💡 如果日志量大、需要复杂处理,可以采用 Filebeat → Kafka → Logstash → ES 架构,Kafka 做缓冲,Logstash 做处理。

二、环境准备

2.1 技术栈版本

组件版本说明
Docker Desktop4.xMac 版
Elasticsearch7.17.217.x LTS 版本,稳定
Kibana7.17.21版本需与 ES 一致
Filebeat7.17.21版本需与 ES 一致

⚠️ 重要:ES、Kibana、Filebeat 三者版本必须一致,否则可能出现兼容性问题。

2.2 项目日志配置

我的 Spring Cloud 项目使用 Logback,日志配置如下:

日志路径格式

${LOG_PATH}/${APP_NAME}/${PORT}/${APP_NAME}.log

例如:/Users/wwccj/Library/Logs/wysiwyg-admin/8088/wysiwyg-admin.log

日志内容格式

2025-12-22 20:30:45 [main] INFO  c.example.Application - Application started

格式说明:日期时间 [线程名] 日志级别 日志类名 - 日志内容

三、Docker Compose 配置

3.1 目录结构

_docker/
├── compose/
│   └── docker-compose-efk.yml    # EFK 编排文件
└── config/
    └── efk/
        ├── elasticsearch.yml     # ES 配置
        ├── kibana.yml            # Kibana 配置
        └── filebeat.yml          # Filebeat 配置

3.2 docker-compose-efk.yml

version: '3.8'

services:
  # 初始化服务 - 设置数据卷权限
  efk-init:
    # 使用轻量级 Alpine Linux 镜像
    image: alpine:latest
    # 容器名称
    container_name: wysiwyg-efk-init
    command: >
      sh -c "
      mkdir -p /es-data /es-logs /kibana-data /filebeat-data &&
      chown -R 1000:1000 /es-data /es-logs &&
      chown -R 1000:1000 /kibana-data &&
      chmod -R 755 /es-data /es-logs /kibana-data /filebeat-data &&
      echo 'EFK volume permissions initialized'
      "
    # 挂载数据卷
    volumes:
      - wysiwyg-elasticsearch-data:/es-data
      - wysiwyg-elasticsearch-logs:/es-logs
      - wysiwyg-kibana-data:/kibana-data
      - wysiwyg-filebeat-data:/filebeat-data
    networks:
      - wysiwyg-network

  # Elasticsearch服务 - 负责日志的存储和检索
  elasticsearch:
    image: elasticsearch:7.17.21
    container_name: wysiwyg-elasticsearch
    # 容器主机名
    hostname: elasticsearch
    # 端口映射:9200用于REST API,9300用于节点通信
    ports:
      - "9200:9200"
      - "9300:9300"
    # 环境变量配置
    environment:
      # 单节点模式,适合开发和测试
      - discovery.type=single-node
      # JVM 内存设置
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
      # 禁用安全功能(生产环境应开启)
      - xpack.security.enabled=false
      # 集群名称
      - cluster.name=wysiwyg-efk-cluster
      # 锁定内存,提高性能
      - bootstrap.memory_lock=true
    # 数据持久化配置
    volumes:
      # 数据文件
      - wysiwyg-elasticsearch-data:/usr/share/elasticsearch/data
      # 日志文件
      - wysiwyg-elasticsearch-logs:/usr/share/elasticsearch/logs
      # 配置文件:只读
      - ../config/efk/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml:ro
    networks:
      - wysiwyg-network
    ulimits:
      memlock:
        # 内存锁定软限制(无限制)
        soft: -1
        # 内存锁定硬限制(无限制)
        hard: -1
      nofile:
        # 文件描述符软限制
        soft: 65536
         # 文件描述符硬限制
        hard: 65536
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:9200/_cluster/health || exit 1"]
      # 检查间隔
      interval: 30s
      # 超时时间
      timeout: 10s
      # 重试次数
      retries: 5
      # 启动后等待时间
      start_period: 60s
    # 重启策略
    restart: unless-stopped

  # Kibana
  kibana:
    image: kibana:7.17.21
    container_name: wysiwyg-kibana
    hostname: kibana
    ports:
      - "5601:5601"
    environment:
      - ELASTICSEARCH_HOSTS=http://elasticsearch:9200
      # 设置为中文界面
      - I18N_LOCALE=zh-CN
    volumes:
      # Kibana 数据持久化
      - wysiwyg-kibana-data:/usr/share/kibana/data
      # 配置文件
      - ../config/efk/kibana.yml:/usr/share/kibana/config/kibana.yml:ro
    # 服务依赖关系
    depends_on:
      elasticsearch:
        condition: service_healthy
    networks:
      - wysiwyg-network
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:5601/api/status || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 60s
    restart: unless-stopped

  # Filebeat
  filebeat:
    image: elastic/filebeat:7.17.21
    container_name: wysiwyg-filebeat
    hostname: filebeat
    # 以 root 用户运行(需要读取日志文件权限)
    user: root
    # 启动命令:-e 输出到标准错误,-strict.perms=false 放宽权限检查
    command: filebeat -e -strict.perms=false
    volumes:
      - ../config/efk/filebeat.yml:/usr/share/filebeat/filebeat.yml:ro
      - wysiwyg-filebeat-data:/usr/share/filebeat/data
      # 挂载本地日志目录
      - /Users/wwccj/Library/Logs:/logs:ro
    depends_on:
      elasticsearch:
        condition: service_healthy
    networks:
      - wysiwyg-network
    restart: unless-stopped

# 数据卷定义 - 用于数据持久化
volumes:
  wysiwyg-elasticsearch-data:
    driver: local
    name: wysiwyg-elasticsearch-data
  wysiwyg-elasticsearch-logs:
    driver: local
    name: wysiwyg-elasticsearch-logs
  wysiwyg-kibana-data:
    driver: local
    name: wysiwyg-kibana-data
  wysiwyg-filebeat-data:
    driver: local
    name: wysiwyg-filebeat-data

networks:
  wysiwyg-network:
    driver: bridge
    name: wysiwyg-network
    ipam:
      config:
        - subnet: 172.20.0.0/16

关键点说明:

  1. efk-init 服务:ES 和 Kibana 以 UID 1000 用户运行,Docker 命名卷默认由 root 创建,需要先修改权限
  2. 健康检查:Kibana 依赖 ES 健康后才启动,避免启动顺序问题
  3. 日志目录挂载:将 Mac 本地日志目录挂载到容器的 /logs

3.3 filebeat.yml(核心配置)

# 输入配置 - 定义要采集的日志文件
filebeat.inputs:
  # wysiwyg-admin 服务日志
  - type: log
    enabled: true
    paths:
      - /logs/wysiwyg-admin/*/*.log
    # 多行日志合并(Java 堆栈跟踪)
    multiline.pattern: '^\d{4}-\d{2}-\d{2}'
    multiline.negate: true
    multiline.match: after
    multiline.max_lines: 500
    # 自定义字段
    fields:
      app_name: wysiwyg-admin
      log_type: application
    fields_under_root: true
    ignore_older: 24h
    close_inactive: 5m

  # wysiwyg-gateway 服务日志
  - type: log
    enabled: true
    paths:
      - /logs/wysiwyg-gateway/*/*.log
    multiline.pattern: '^\d{4}-\d{2}-\d{2}'
    multiline.negate: true
    multiline.match: after
    multiline.max_lines: 500
    fields:
      app_name: wysiwyg-gateway
      log_type: application
    fields_under_root: true
    ignore_older: 24h
    close_inactive: 5m

  # wysiwyg-flow 服务日志
  - type: log
    enabled: true
    paths:
      - /logs/wysiwyg-flow/*/*.log
    multiline.pattern: '^\d{4}-\d{2}-\d{2}'
    multiline.negate: true
    multiline.match: after
    multiline.max_lines: 500
    fields:
      app_name: wysiwyg-flow
      log_type: application
    fields_under_root: true
    ignore_older: 24h
    close_inactive: 5m

# 处理器配置 - 解析日志内容
processors:
  # 使用 dissect 解析日志格式
  - dissect:
      tokenizer: "%{log_time} [%{thread}] %{level} %{logger} - %{log_message}"
      field: "message"
      target_prefix: ""
      ignore_failure: true
  
  # 时间戳处理(重要!见下文坑点说明)
  - timestamp:
      field: "log_time"
      layouts:
        - '2006-01-02 15:04:05'
      timezone: "Asia/Shanghai"
      ignore_failure: true
  
  # 移除冗余字段
  - drop_fields:
      fields: ["agent", "ecs", "host", "input", "log_time", "message"]
      ignore_missing: true

# Elasticsearch 输出配置
output.elasticsearch:
  hosts: ["elasticsearch:9200"]
  index: "wysiwyg-logs-%{+yyyy.MM.dd}"

# 索引模板配置
setup.template.name: "wysiwyg-logs"
setup.template.pattern: "wysiwyg-logs-*"
setup.template.enabled: true
setup.template.settings:
  index.number_of_shards: 1
  index.number_of_replicas: 0

# 关闭 ILM(开发环境)
setup.ilm.enabled: false

# Kibana 配置
setup.kibana:
  host: "kibana:5601"

# Filebeat 自身日志
logging.level: info
logging.to_files: true
logging.files:
  path: /usr/share/filebeat/logs
  name: filebeat
  keepfiles: 7

关键配置说明:

  1. multiline 配置:Java 异常堆栈是多行的,需要合并到一条日志
  2. dissect 处理器:比 grok 更轻量,适合固定格式的日志解析
  3. drop_fields:移除 Filebeat 默认添加的冗余字段(agent、host 等)
  4. fields_under_root:自定义字段放在根级别,而不是 fields.xxx

四、启动部署

4.1 启动命令

cd _docker/compose
​
# 1. 初始化数据卷权限
docker-compose -f docker-compose-efk.yml up efk-init
​
# 2. 启动服务
docker-compose -f docker-compose-efk.yml up -d elasticsearch kibana filebeat
​
# 3. 查看状态
docker-compose -f docker-compose-efk.yml ps
​
# 4. 查看日志
docker-compose -f docker-compose-efk.yml logs -f filebeat

4.2 验证服务

# 检查 ES
curl http://localhost:9200/_cluster/health?pretty

# 检查索引
curl http://localhost:9200/_cat/indices?v

五、Kibana 配置

5.1 创建索引模式

  1. 打开 http://localhost:5601

  2. 左侧菜单 → Stack Management索引模式

  3. 点击 创建索引模式

  4. 输入 wysiwyg-logs-*

  5. 时间字段选择 @timestamp

  6. 点击创建

    1766494927711

5.2 配置时区(重要!)

  1. 左侧菜单 → Stack Management高级设置
  2. 搜索 timezone
  3. dateFormat:tz 设置为 Asia/Shanghai【一般默认是浏览器时间,一开始我开了代理导致kibana时间和es时区对不上我还以为没有输出数据】

5.3 自定义 Discover 列

在 Discover 页面,点击左侧字段列表的 + 添加常用列(这块我建议可以把error日志和常用服务名字添加标签):

  • app_name - 服务名
  • level - 日志级别
  • logger - 类名
  • log_message - 日志内容

具体图解:

  1. 添加错误日志等其他常用筛选标签

image-20251223210640468

  1. 添加好之后点击保存

image-20251223211039385

  1. 之后查看日志直接打开即可按照保存的条件查询

image-20251223211150047

  1. 如果查看到错误日志后点击查看周围文档即可看到错误日志附近的几个日志信息,方便排查前后发生了什么

1766495688539

5.4 创建错误监控 Dashboard

图表1:ERROR 日志趋势

  1. 左侧菜单 → Visualize Library创建可视化
  2. 选择 Lens
  3. 搜索框输入:level: ERROR
  4. @timestamp 拖到画布
  5. 图表类型选择折线图
  6. 保存为 ERROR日志趋势

image-20251223212427117转存失败,建议直接上传图片文件

图表2:各服务错误分布

  1. 创建可视化 → 选择 Lens
  2. 搜索框输入:level: ERROR
  3. app_name 拖到画布
  4. 图表类型选择饼图
  5. 保存为 各服务错误分布

image-20251223212724628

创建 Dashboard

  1. 左侧菜单 → Dashboard创建仪表板
  2. 添加刚才创建的图表
  3. 保存为 wysiwyg日志监控

六、常用查询语法

# 查看所有错误日志
level: ERROR

# 查看某服务的日志
app_name: wysiwyg-admin

# 组合查询
level: ERROR AND app_name: wysiwyg-gateway

# 搜索异常关键字
log_message: *NullPointerException*

# 搜索特定类的日志
logger: *UserController*

# 时间范围
# 使用右上角的时间选择器

七、最终效果

7.1 日志结构

精简后的日志结构:

{
  "@timestamp": "2025-12-22T12:30:45.000Z",
  "app_name": "wysiwyg-admin",
  "level": "INFO",
  "thread": "main",
  "logger": "c.example.Application",
  "log_message": "Application started",
  "log_type": "application",
  "log": {
    "file": {
      "path": "/logs/wysiwyg-admin/8088/wysiwyg-admin.log"
    }
  }
}

7.2 查询效果

  • 支持按服务筛选
  • 支持按日志级别筛选
  • 支持全文搜索
  • 支持时间范围查询
  • Dashboard 实时展示错误趋势

ps:我自己就随便用日志文件搞了一些报错,主要是看这个监控效果

1766496476197

八、总结

  1. 主要是重温了下之前es的学习过程、一步一步搭建了一下本地级别的es,欢迎各路大佬指点
  2. 对于es内部的一些机制和集群的搭建还需要在我们内部服务中实践,这块后续还要在学习下

另外附上我个人的项目链接,大家可以参考_docker目录完成efk的搭建

gitee.com/jwc666666/w…