在分布式系统中,服务实例数以百计、容器频繁启停,传统 SSH 逐台查看日志的方式已完全不可行。日志聚合是 DevOps 可观测性三大支柱之一,将分散在各节点上的日志集中采集、存储、索引与查询,帮助工程师快速定位问题并建立告警体系。
本文系统讲解日志聚合核心原理,对比 ELK Stack、Grafana Loki 与 Fluent Bit 三款主流工具的生产部署方式,涵盖完整配置示例、性能优化与结构化日志实践。
一、日志聚合基础:从 stdout 到集中存储
容器化环境中,应用通常将日志写入 stdout/stderr,由容器运行时重定向到宿主机文件系统。单节点下 kubectl logs 尚可应对,但在 Kubernetes 多节点集群中,Pod 随时可能被重调度,本地日志随之丢失。
典型数据流:App stdout/stderr → Container Runtime → Node Agent → Storage Backend → Query UI
采集代理有两种模式:
- DaemonSet 模式:每节点运行一个代理,读取容器运行时落盘的日志文件,适合大规模集群。
- Sidecar 模式:每个业务 Pod 注入日志容器,读取共享 emptyDir 中的日志,适合多租户场景。
生产环境推荐 DaemonSet 模式部署 Fluent Bit:
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluent-bit
namespace: logging
spec:
selector:
matchLabels:
k8s-app: fluent-bit-logging
template:
metadata:
labels:
k8s-app: fluent-bit-logging
spec:
serviceAccountName: fluent-bit
containers:
- name: fluent-bit
image: fluent/fluent-bit:3.0
volumeMounts:
- name: varlog
mountPath: /var/log
- name: varlibdockercontainers
mountPath: /var/lib/docker/containers
readOnly: true
- name: fluent-bit-config
mountPath: /fluent-bit/etc/
volumes:
- name: varlog
hostPath:
path: /var/log
- name: varlibdockercontainers
hostPath:
path: /var/lib/docker/containers
- name: fluent-bit-config
configMap:
name: fluent-bit-config
注意点:挂载 /var/log 与 /var/lib/docker/containers;日志目录设置 readOnly: true;ServiceAccount 配置 RBAC 权限以读取 Pod 元数据。
二、ELK Stack 生产部署
ELK 是成熟的企业级日志方案,覆盖全文检索、字段聚合与可视化报表,适合中大型团队。
2.1 组件说明
| 组件 | 职责 | 生产建议 |
|---|---|---|
| Elasticsearch | 分布式搜索与存储后端 | 三节点起步,独立数据盘,禁用 Swap |
| Logstash | 日志解析、过滤与格式转换 | 非必要可省略,Fluent Bit 替代 |
| Kibana | 可视化查询与仪表盘 | 配置反向代理与认证 |
| Filebeat | 轻量日志采集代理 | DaemonSet 部署,限制 256Mi/500m |
Elasticsearch 节点角色应分离:Master-eligible nodes(至少三节点防脑裂)、Data nodes(承载写入查询)、Coordinating nodes(网关层)。
ES 集群关键配置:
cluster.name: prod-logging
node.name: ${HOSTNAME}
node.roles: [ data, master ]
path.data: /data/elasticsearch
path.logs: /var/log/elasticsearch
discovery.seed_hosts:
- es-master-1.logging.svc.cluster.local
- es-master-2.logging.svc.cluster.local
- es-master-3.logging.svc.cluster.local
cluster.initial_master_nodes:
- es-master-1
- es-master-2
- es-master-3
xpack.security.enabled: true
xpack.security.transport.ssl.enabled: true
JVM 堆内存设为物理内存 50% 且不超过 30GB:
-Xms16g
-Xmx16g
-XX:+UseG1GC
2.2 Filebeat 部署与 ILM
Filebeat 采集节点日志直接推送 ES,无需 Logstash:
filebeat.inputs:
- type: container
paths:
- /var/lib/docker/containers/*/*.log
processors:
- add_kubernetes_metadata:
host: ${NODE_NAME}
matchers:
- logs_path:
logs_path: /var/lib/docker/containers/
output.elasticsearch:
hosts: ["https://es-coordinator.logging.svc:9200"]
username: filebeat_writer
password: ${FILEBEAT_ES_PASSWORD}
ssl:
certificate_authorities: ["/etc/ssl/certs/ca.crt"]
index: "logs-%{[kubernetes.namespace]}-%{+yyyy.MM.dd}"
setup.ilm.enabled: true
setup.ilm.rollover_alias: "logs"
setup.ilm.policy_name: "logs-30d-retention"
索引生命周期管理自动迁移或删除旧索引:
PUT _ilm/policy/logs-30d-retention
{
"policy": {
"phases": {
"hot": {
"min_age": "0ms",
"actions": {
"rollover": { "max_size": "50GB", "max_age": "1d" }
}
},
"warm": {
"min_age": "3d",
"actions": {
"shrink": { "number_of_shards": 1 },
"forcemerge": { "max_num_segments": 1 }
}
},
"delete": {
"min_age": "30d",
"actions": { "delete": {} }
}
}
}
}
三、Grafana Loki:云原生低成本方案
Loki 只索引标签(元数据),不索引日志原始内容,存储成本通常仅为 ES 的 1/5 到 1/10。查询语法与 PromQL 一脉相承,适合 Prometheus + Grafana 生态。
3.1 Loki 部署配置
auth_enabled: false
server:
http_listen_port: 3100
grpc_listen_port: 9096
common:
path_prefix: /loki
storage:
s3:
endpoint: s3.amazonaws.com
region: us-east-1
bucketnames: loki-chunks-prod
access_key_id: ${AWS_ACCESS_KEY}
secret_access_key: ${AWS_SECRET_KEY}
ring:
kvstore:
store: memberlist
replication_factor: 3
schema_config:
configs:
- from: "2024-01-01"
store: tsdb
object_store: s3
schema: v13
index:
prefix: loki_index_
period: 24h
compactor:
working_directory: /loki/compactor
retention_enabled: true
retention_delete_delay: 2h
limits_config:
retention_period: 720h
ingestion_rate_mb: 64
TSDB 索引替代旧版 BoltDB;对象存储持久化 chunk;本地磁盘仅作 WAL 缓冲。
3.2 Promtail 采集配置
server:
http_listen_port: 9080
grpc_listen_port: 0
clients:
- url: http://loki-gateway.logging.svc.cluster.local/loki/api/v1/push
batchwait: 1s
batchsize: 1048576
positions:
filename: /run/promtail/positions.yaml
scrape_configs:
- job_name: kubernetes-pods
kubernetes_sd_configs:
- role: pod
pipeline_stages:
- cri: {}
- json:
expressions:
level: level
service: service_name
- labels:
level:
service:
relabel_configs:
- source_labels: [__meta_kubernetes_pod_node_name]
target_label: __host__
- action: labelmap
regex: __meta_kubernetes_pod_label_(.+)
- source_labels: [__meta_kubernetes_namespace]
target_label: namespace
- source_labels: [__meta_kubernetes_pod_name]
target_label: pod
kubernetes_sd_configs 自动发现 Pod,cri stage 解析 containerd/CRI-O 日志格式,提取字段作为标签。
3.3 LogQL 查询与告警
日志查询:{namespace="prod",app="api"} |= "error" | json | line_format "{{.msg}}"
度量查询:sum(rate({namespace="prod"} |= "timeout" [5m])) by (service)
告警规则:
groups:
- name: api-errors
interval: 1m
rules:
- alert: HighErrorRate
expr: |
sum(rate({namespace="prod", app="api"} |= "ERROR" [5m])) by (service) > 0.1
for: 2m
labels:
severity: critical
annotations:
summary: "服务 {{ $labels.service }} 错误率超过阈值"
Ruler 自动评估 LogQL 度量查询,条件成立时在 Alertmanager 生成告警。
四、Fluent Bit:高性能统一采集代理
Fluent Bit 以 C 语言编写,内存通常小于 30MB,支持数十种输入与输出插件,可同时向 ES、Loki、Kafka、S3 等后端推送数据。
4.1 核心配置
Fluent Bit 使用 INI 格式,分 SERVICE、INPUT、FILTER、OUTPUT 四段:
[SERVICE]
Flush 1
Log_Level warning
HTTP_Server On
HTTP_Listen 0.0.0.0
HTTP_Port 2020
Parsers_File parsers.conf
storage.path /var/log/flb-storage/
storage.sync normal
storage.max_chunks_up 128
[INPUT]
Name tail
Tag kube.*
Path /var/log/containers/*.log
Parser docker
DB /var/log/flb_kube.db
Mem_Buf_Limit 50MB
Skip_Long_Lines On
[FILTER]
Name kubernetes
Match kube.*
Kube_URL https://kubernetes.default.svc:443
Kube_CA_File /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
Kube_Token_File /var/run/secrets/kubernetes.io/serviceaccount/token
Merge_Log On
Keep_Log Off
[FILTER]
Name modify
Match kube.*
Rename kubernetes_pod_name pod
Rename kubernetes_namespace_name namespace
Rename kubernetes_container_name container
[OUTPUT]
Name loki
Match kube.*
Host loki-gateway.logging.svc.cluster.local
Port 80
Labels job=fluentbit,env=prod
Line_Format json
Drop_Records_On_Error On
[OUTPUT]
Name es
Match kube.*
Host es-coordinator.logging.svc
Port 9200
Index fluentbit
Suppress_Type_Name On
HTTP_User fluentbit
HTTP_Passwd ${ES_PASSWORD}
tls On
tls.verify On
tls.ca_file /etc/ssl/certs/ca.crt
4.2 性能调优
[SERVICE]
Flush 1
Workers 4
storage.path /var/log/flb-storage/
storage.sync full
storage.backlog.mem_limit 100MB
[INPUT]
Name tail
Buffer_Max_Size 1MB
Buffer_Chunk_Size 512k
[OUTPUT]
Name loki
Match *
Workers 2
Batch_Wait 1s
Batch_Size 1000000
Workers 启用输出并发写入;storage.* 启用文件系统缓冲防 OOM。
4.3 多行日志合并
Java 异常、Go panic 等多行日志需合并:
[INPUT]
Name tail
Path /var/log/app/*.log
Multiline On
Parser_Firstline multiline_java
Tag app.java
[PARSER]
Name multiline_java
Format regex
Regex /^(?<time>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}[.,]?\d{0,3}) (?<level>\w+) (?<msg>.*)/
Time_Key time
Time_Format %Y-%m-%d %H:%M:%S.%L
Parser_Firstline 定义多行日志起始规则,不匹配时追加到上一条记录。
五、结构化日志最佳实践
非结构化日志只能依赖全文检索,无法按字段过滤或关联 Trace ID。结构化日志(通常为 JSON)是云原生应用的基础要求。
5.1 字段规范
{
"timestamp": "2026-09-01T08:15:30.123456789Z",
"level": "ERROR",
"service": "payment-gateway",
"trace_id": "abc123def456",
"span_id": "span789",
"message": "charge request failed",
"error": {
"type": "PaymentTimeout",
"code": 504,
"detail": "upstream processor did not respond within 3000ms"
},
"http": {
"method": "POST",
"path": "/v1/charge",
"status_code": 504,
"duration_ms": 3002
},
"environment": "prod",
"host": "api-7f8d9c6b5-xv2p4"
}
timestamp 使用 RFC3339Nano;level 统一为 DEBUG/INFO/WARN/ERROR/FATAL;trace_id / span_id 对接 OpenTelemetry。
5.2 Go 应用输出示例
package main
import (
"log/slog"
"os"
"time"
)
type PaymentError struct {
Type string `json:"type"`
Code int `json:"code"`
Detail string `json:"detail"`
}
type HTTPInfo struct {
Method string `json:"method"`
Path string `json:"path"`
StatusCode int `json:"status_code"`
DurationMs int64 `json:"duration_ms"`
}
func main() {
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelInfo,
}))
logger.Error("charge request failed",
slog.String("service", "payment-gateway"),
slog.String("trace_id", "abc123def456"),
slog.Any("error", PaymentError{
Type: "PaymentTimeout",
Code: 504,
Detail: "upstream processor did not respond within 3000ms",
}),
slog.Any("http", HTTPInfo{
Method: "POST",
Path: "/v1/charge",
StatusCode: 504,
DurationMs: time.Duration(3002 * time.Millisecond).Milliseconds(),
}),
)
}
5.3 动态日志级别
func getLogLevel() slog.Leveler {
switch os.Getenv("LOG_LEVEL") {
case "DEBUG":
return slog.LevelDebug
case "WARN":
return slog.LevelWarn
case "ERROR":
return slog.LevelError
default:
return slog.LevelInfo
}
}
生产突发流量时通过环境变量热更新提升日志级别,减少磁盘与网络压力。
六、日志分析与告警规则配置
6.1 关键查询模式
Kibana 查询:service:"payment-gateway" AND level:"ERROR" AND http.duration_ms:>2000
Loki 等效查询:
{service="payment-gateway"}
|= "ERROR"
| json
| http_duration_ms > 2000
| line_format "{{.message}} trace={{.trace_id}}"
Top 10 错误类型聚合:
topk(10, sum by (error_type) (
rate({namespace="prod"} |= "ERROR" | json [5m])
))
6.2 告警规则
groups:
- name: log-anomaly
interval: 2m
rules:
- alert: LogVolumeSpike
expr: |
sum(rate({namespace="prod"}[5m])) > 3 * avg_over_time(
sum(rate({namespace="prod"}[5m]))[1h:]
)
for: 5m
labels:
severity: warning
annotations:
summary: "日志写入量异常突增"
- alert: CriticalSecurityEvent
expr: |
sum(rate({namespace="prod"} |~ "(?i)(unauthorized|privilege escalation)" [5m])) > 0
for: 0m
labels:
severity: critical
annotations:
summary: "检测到高危安全事件"
6.3 Elasticsearch Watcher
ELK 7.x 以下可通过 Watcher 实现灵活告警:
{
"trigger": { "schedule": { "interval": "5m" } },
"input": {
"search": {
"request": {
"indices": ["logs-prod-*"],
"body": {
"size": 0,
"query": {
"bool": {
"must": [
{ "match": { "level": "ERROR" } },
{ "range": { "timestamp": { "gte": "now-5m" } } }
]
}
},
"aggs": {
"by_service": {
"terms": { "field": "service.keyword" }
}
}
}
}
}
},
"condition": {
"script": { "source": "ctx.payload.hits.total > 100" }
},
"actions": {
"send_webhook": {
"webhook": {
"method": "POST",
"url": "https://alert.example.com/webhook",
"body": "{\"text\":\"ERROR count exceeded: {{ctx.payload.hits.total}}\"}"
}
}
}
}
ES 8.x 推荐迁移至 Kibana Alerting,与 RBAC 集成更紧密。
6.4 归档与合规
金融、医疗行业要求日志保留 3-5 年以上,应迁入对象存储冷备:
- Transition:
Days: 30
StorageClass: STANDARD_IA
- Transition:
Days: 90
StorageClass: GLACIER
- Expiration:
Days: 2555
Elasticsearch 可通过 ILM 将旧索引合并后存储于 S3 Repository,或使用 Searchable Snapshots 降低磁盘压力。
常见问题(FAQ)
Q1:ELK 和 Loki 在生产中如何选择?
A1:如需强大全文搜索、复杂聚合仪表盘与机器学习,ELK 更成熟,但资源成本与运维负担较高。若团队已使用 Prometheus + Grafana,且对存储成本敏感,Loki 的无索引架构更合适,LogQL 可满足 90% 以上的查询与告警需求。
Q2:Fluent Bit 和 Fluentd 有什么区别?
A2:Fluent Bit 是 C 语言轻量级采集代理,适合 Node Agent 大规模部署。Fluentd 是 Ruby 转发器,过滤与缓冲插件更丰富,适合作为聚合层部署在集群边界。通常采用 Fluent Bit 采集 + Fluentd 转发的两层架构。
Q3:日志系统本身的高可用如何保障?
A3:采集层 DaemonSet 天然节点级冗余;ES 需三节点 Master 与两副本 Shard;Loki 通过 Memberlist 环与 S3 后端实现无状态扩缩容。建议将 logging namespace 设为 system-cluster-critical 优先级,配置独立节点池防业务挤占。
Q4:日志中打印敏感信息如何脱敏?
A4:采集层使用 Fluent Bit 的 lua 过滤器正则替换,或应用层通过 slog 的 ReplaceAttr 动态屏蔽:
function mask_sensitive(tag, timestamp, record)
local msg = record["log"]
if msg then
msg = string.gsub(msg, '"phone":"%d+"', '"phone":"***"')
msg = string.gsub(msg, '"token":"[^"]+"', '"token":"***"')
record["log"] = msg
end
return 1, timestamp, record
end
挂载至 ConfigMap 并在 Fluent Bit 配置中引用,即可在落盘后端前完成脱敏。
总结
日志聚合是现代可观测性体系的基石。ELK Stack 提供企业级搜索与分析,适合查询灵活度要求高的场景;Grafana Loki 以标签索引与对象存储带来极致成本优势,是云原生高性价比之选;Fluent Bit 以超高性能与丰富插件生态成为统一采集代理的事实标准。
无论采用哪种方案,都应遵循以下原则:
- 强制输出结构化日志,统一字段规范,关联 Trace ID 实现全链路追踪。
- 在采集端完成解析与脱敏,减轻后端存储与计算压力。
- 配置索引生命周期或保留策略,避免日志无限膨胀拖垮集群。
- 建立围绕 ERROR 速率、日志量异常与安全事件的告警规则,变被动排查为主动发现。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。