跳到主要内容

日志体系-ELK-Loki

1. 方案对比

特点适合
ELK(Elasticsearch + Logstash/Fluentd + Kibana)全文索引、查询强大型企业、深度搜索
EFK(Fluentd 代 Logstash)K8s 主流容器场景
PLG(Promtail + Loki + Grafana)只索引 label、便宜中小团队、Grafana 已有
Vector + 后端Rust 实现,超高性能任意场景
商业 SaaS(Datadog / 阿里 SLS)省心不想自维护

2. PLG(Loki)

2.1 部署

helm repo add grafana https://grafana.github.io/helm-charts
helm install loki grafana/loki-stack -n logging --create-namespace \
--set promtail.enabled=true \
--set grafana.enabled=true

2.2 Promtail 自动收集

DaemonSet 跑在每节点,读 /var/log/pods/ stdout 日志,打 label:

{namespace="frontend", pod="my-frontend-abc", container="web"}

应用只需 stdout 输出 JSON 即可。

2.3 LogQL 查询

# 看 namespace 所有日志
{namespace="frontend"}

# 按关键词过滤
{namespace="frontend"} |= "ERROR"

# JSON 解析后过滤字段
{namespace="frontend"} | json | level="error"

# 计数:5 分钟内错误数
sum(count_over_time({namespace="frontend"} |= "ERROR" [5m]))

# 按 pod 分组
sum by (pod) (rate({namespace="frontend"} |~ "ERROR" [5m]))

# 提取字段并聚合
{namespace="frontend"}
| json
| __error__ = ""
| line_format "}}.userId}} }}.action}}"

2.4 标签 vs 字段

Loki 只对标签建索引,不对内容。基数高的字段(userId)不能当 label,要存到日志内容里查询时解析。

3. ELK 栈

3.1 架构

应用 stdout → Filebeat / Fluentd → Logstash → Elasticsearch → Kibana

3.2 Filebeat(轻量)

# filebeat-kubernetes.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: filebeat
spec:
template:
spec:
containers:
- name: filebeat
image: docker.elastic.co/beats/filebeat:8.11.0
volumeMounts:
- name: varlog
mountPath: /var/log
- name: config
mountPath: /usr/share/filebeat/filebeat.yml
subPath: filebeat.yml
volumes:
- name: varlog
hostPath: { path: /var/log }
- name: config
configMap: { name: filebeat-config }

3.3 Elasticsearch 索引

按时间分索引:logs-2026.06.18。配 ILM(Index Lifecycle Management)自动 rollover + 删旧:

{
"policy": {
"phases": {
"hot": {
"actions": {
"rollover": { "max_size": "50GB", "max_age": "1d" }
}
},
"delete": {
"min_age": "7d",
"actions": { "delete": {} }
}
}
}
}

3.4 Kibana 查询

KQL(Kibana Query Language):

kubernetes.namespace: "frontend" AND level: "error"
http.response.status_code >= 500
@timestamp >= "2026-06-18T10:00:00"

4. 应用层规范

4.1 必须 JSON 结构化

// pino
const logger = require('pino')()
logger.info({
userId: 123,
requestId: 'abc',
duration: 234,
}, '用户登录')

输出:

{"level":30,"time":1718681400000,"userId":123,"requestId":"abc","duration":234,"msg":"用户登录"}

4.2 必备字段

  • timestamp / @timestamp
  • level
  • service
  • env
  • traceId
  • requestId
  • userId
  • message

4.3 不该写

  • 完整 token / 密码
  • 大对象 dump(数 MB body)
  • 大量 debug 日志(生产应 info+)

5. 容器 / K8s 日志

容器日志写 stdout / stderr → 容器运行时(containerd)→ /var/log/pods/<ns>_<pod>/<container>/0.log → DaemonSet 收集。

应用不要写文件:容器销毁数据丢,volume 挂复杂。

K8s 日志命令:

kubectl logs <pod> -n frontend -f
kubectl logs <pod> --previous # 上次崩的日志
kubectl logs -l app=frontend --tail=100 # 按 label 看多 pod
kubectl logs <pod> -c sidecar # 指定容器

6. 日志保留策略

数据推荐保留
INFO 日志7-14 天
ERROR 日志30-90 天
审计日志1-7 年(按合规)

存储成本:Loki < Elasticsearch(10x+ 差距)。

7. 关联:traceId

每条日志带 traceId:

import { trace } from '@opentelemetry/api'

logger.info({
traceId: trace.getActiveSpan()?.spanContext().traceId,
userId,
}, '操作完成')

Grafana 一键日志 → trace 跳转。

8. 告警

基于日志的告警(Loki / Elasticsearch Alerting):

# Loki 告警规则
- alert: HighErrorRate
expr: |
sum(rate({namespace="frontend"} |~ "ERROR" [5m])) > 10
for: 5m
annotations:
summary: "frontend 5 分钟内 ERROR > 50 条"

9. 故障排查

# 日志查不到
# 1. 应用是否 stdout 输出?
kubectl logs <pod> # 有就对

# 2. promtail / filebeat 是否运行
kubectl get ds -n logging

# 3. 后端是否接收
# Loki: 看 distributor / ingester pod 日志
# ES: curl localhost:9200/_cluster/health

10. 常见反模式

  • 应用写文件不写 stdout:容器销毁丢
  • info 级别写每个 SQL:日志量爆炸
  • userId 当 Loki label:基数爆
  • 打印完整 cookie / token:审计灾难
  • 不分级别:所有日志同优先级
  • 保留时间无限:磁盘满
  • 不关联 traceId:链路断
  • 每个 Pod 日志独立查:应该集中化

11. 延伸阅读