6月5日 22:29
Docker 容器日志怎么聚合?ELK、Loki 和 EFK 怎么选
容器一多,日志分散在各处——docker logs 只能看单个容器的输出,排查问题要一个一个翻。日志聚合把所有容器的日志集中到一个地方,搜索、过滤、告警一站搞定。
Docker 日志驱动:日志的入口
Docker 支持多种日志驱动,决定容器日志的去向:
bash# 查看当前日志驱动 docker info --format '{{.LoggingDriver}}' # 默认是 json-file
yaml# docker-compose.yml 全局配置 services: app: logging: driver: json-file options: max-size: "10m" # 单个日志文件最大 10MB max-file: "3" # 最多保留 3 个文件
json-file 默认不限制大小——跑久了磁盘会被日志撑满。务必配 max-size 和 max-file。
其他日志驱动
| 驱动 | 去向 | 适用场景 |
|---|---|---|
json-file | 本地文件 | 默认,简单场景 |
local | 本地文件(压缩) | 省磁盘 |
journald | systemd journal | CentOS/Ubuntu 系统日志 |
fluentd | Fluentd | 接入日志聚合栈 |
syslog | syslog 服务 | 传统运维 |
生产环境推荐用 fluentd 或 local 驱动——前者直接接入日志聚合,后者比 json-file 省磁盘。
ELK Stack:经典方案
Elasticsearch + Logstash + Kibana,功能最全但最重:
yaml# docker-compose.yml services: elasticsearch: image: elasticsearch:8.12.0 environment: - discovery.type=single-node - xpack.security.enabled=false ports: - "9200:9200" volumes: - es_data:/usr/share/elasticsearch/data logstash: image: logstash:8.12.0 volumes: - ./logstash/pipeline:/usr/share/logstash/pipeline ports: - "5044:5044" kibana: image: kibana:8.12.0 ports: - "5601:5601" environment: - ELASTICSEARCH_HOSTS=http://elasticsearch:9200 app: image: myapp:latest logging: driver: fluentd options: fluentd-address: localhost:24224 tag: myapp volumes: es_data:
问题:ELK 最少需要 4GB 内存才能跑起来。小团队或开发环境用太重了。
EFK Stack:轻量替代
用 Fluentd 替代 Logstash,更省资源:
yamlservices: fluentd: image: fluent/fluentd:v1.16 volumes: - ./fluentd/conf:/fluentd/etc ports: - "24224:24224" environment: - FLUENTD_CONF=fluent.conf
conf# fluentd/conf/fluent.conf <source> @type forward port 24224 </source> <match **> @type elasticsearch host elasticsearch port 9200 logstash_format true logstash_prefix fluentd <buffer> @type file path /var/log/fluentd/buffer flush_interval 5s </buffer> </match>
容器配置 logging.driver: fluentd 后,所有 stdout/stderr 输出自动发到 Fluentd,Fluentd 转存到 Elasticsearch。
Grafana Loki:最轻量的选择
Loki 只索引标签不索引日志内容,存储成本比 Elasticsearch 低 10 倍以上:
yaml# docker-compose.yml services: loki: image: grafana/loki:2.9.0 ports: - "3100:3100" command: -config.file=/etc/loki/local-config.yaml promtail: image: grafana/promtail:2.9.0 volumes: - /var/log:/var/log - /var/lib/docker/containers:/var/lib/docker/containers:ro - ./promtail/config.yml:/etc/promtail/config.yml command: -config.file=/etc/promtail/config.yml grafana: image: grafana/grafana:10.3.0 ports: - "3000:3000" environment: - GF_SECURITY_ADMIN_PASSWORD=admin
Promtail 从 Docker 容器日志目录读取日志,推送到 Loki。Grafana 查询和可视化。
Loki 的查询语法(LogQL)比 Kibana 的 KQL 简单:
logql{container_name="myapp"} |= "error" | json | line_format "{{.message}}"
意思是:从 myapp 容器日志中过滤包含 "error" 的行,解析 JSON,只显示 message 字段。
日志结构化:让聚合更有效
非结构化日志在聚合平台里只能做全文搜索。结构化日志可以按字段过滤、聚合统计:
python# Python 结构化日志(JSON 格式) import logging import json class JSONFormatter(logging.Formatter): def format(self, record): return json.dumps({ "timestamp": self.formatTime(record), "level": record.levelname, "message": record.getMessage(), "service": "user-service", "trace_id": getattr(record, "trace_id", None), })
javascript// Node.js 用 pino 直接输出 JSON const logger = require('pino')({ level: 'info', formatters: { level: (label) => ({ level: label }) }, }) logger.info({ userId: 123, action: 'login' }, 'User logged in')
选择决策
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 开发/测试 | docker logs + json-file | 够用 |
| 小团队(<10 服务) | Grafana Loki + Promtail | 轻量,1GB 内存够 |
| 中大型团队 | EFK Stack | 功能全、社区大 |
| 需要全文搜索 | ELK Stack | Elasticsearch 全文检索最强 |
| 已有 Grafana | 直接加 Loki | 不用额外装 Kibana |
| 日志量巨大 | Loki(只索引标签) | 存储成本最低 |
起步建议:先用 Loki + Grafana,轻量够用。等日志量大到需要全文搜索时再迁移到 ELK。