Kubernetes 日志收集实战——EFK 或 Loki + Grafana 的完整搭建方案
Kubernetes 日志收集实战——EFK 或 Loki + Grafana 的完整搭建方案
适读人群:需要在 K8s 上搭建集中日志系统的工程师 | 阅读时长:约 16 分钟 | 核心价值:EFK 和 Loki 两套方案的完整配置,选型依据和踩坑记录
在 K8s 上排查问题,最痛苦的事情之一就是日志查不到。
服务有 20 个 Pod,分布在 8 台节点上,某个 Pod 崩了重启了,你去 kubectl logs 看,只能看到重启后的日志,崩溃前的日志随着容器一起消失了。
我第一次遇到这种情况是在生产环境,一个支付服务莫名其妙出了问题,Pod 重启前后各正常,就是那一分钟的日志没了,愣是没找到原因。
从那以后,集中日志系统成了我部署 K8s 集群的必选项。
两套方案选型对比
目前 K8s 上主流的日志收集方案有两类:
EFK(Elasticsearch + Fluentd/Fluent Bit + Kibana)
- 功能强大,支持全文检索、复杂查询
- 资源消耗大(Elasticsearch 至少需要 4GB 内存)
- 适合日志量大、需要复杂分析的场景
Loki + Promtail + Grafana
- 轻量,不对日志内容建索引,只索引 label
- 资源消耗小(Loki 512MB 内存就能跑)
- 与 Prometheus/Grafana 监控栈无缝集成
- 适合中小规模集群,已有 Grafana 的场景
我现在的生产集群用的是 Loki,原因很简单:我们已经有 Grafana 了,不想再维护一套 Kibana;而且 Elasticsearch 太重,集群资源有限。
方案一:Loki + Promtail + Grafana
安装 Loki Stack
helm repo add grafana https://grafana.github.io/helm-charts
helm repo update
# 方式一:安装完整的 Loki Stack(包含 Grafana)
helm install loki-stack grafana/loki-stack \
--namespace monitoring \
--create-namespace \
--set grafana.enabled=true \
--set prometheus.enabled=true \
--set loki.persistence.enabled=true \
--set loki.persistence.size=20Gi \
--set loki.persistence.storageClassName=fast-ssd
# 方式二:如果已有 Grafana,单独安装 Loki
helm install loki grafana/loki \
--namespace monitoring \
-f loki-values.yaml自定义 Loki 配置(loki-values.yaml):
# loki-values.yaml
loki:
auth_enabled: false # 单租户不需要认证
storage:
type: filesystem # 或者 s3(推荐生产用对象存储)
limits_config:
retention_period: 720h # 日志保留 30 天
ingestion_rate_mb: 16
ingestion_burst_size_mb: 32
max_query_length: 721h
# 分块存储配置
chunk_store_config:
max_look_back_period: 720h
table_manager:
retention_deletes_enabled: true
retention_period: 720h
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 1
memory: 1Gi
persistence:
enabled: true
size: 50Gi
storageClassName: fast-ssd配置 Promtail(日志采集 Agent)
Promtail 以 DaemonSet 形式运行在每台节点上,采集所有容器日志:
# promtail-config.yaml(通过 ConfigMap 配置)
apiVersion: v1
kind: ConfigMap
metadata:
name: promtail-config
namespace: monitoring
data:
promtail.yaml: |
server:
http_listen_port: 9080
grpc_listen_port: 0
positions:
filename: /tmp/positions.yaml
clients:
- url: http://loki:3100/loki/api/v1/push
scrape_configs:
# 采集 K8s Pod 日志
- job_name: kubernetes-pods
kubernetes_sd_configs:
- role: pod
relabel_configs:
# 只采集有 log 注解的 Pod(可选过滤)
# - source_labels: [__meta_kubernetes_pod_annotation_loki_scrape]
# action: keep
# regex: true
# 添加有用的 label
- source_labels: [__meta_kubernetes_namespace]
target_label: namespace
- source_labels: [__meta_kubernetes_pod_name]
target_label: pod
- source_labels: [__meta_kubernetes_pod_container_name]
target_label: container
- source_labels: [__meta_kubernetes_pod_label_app]
target_label: app
- source_labels: [__meta_kubernetes_pod_node_name]
target_label: node
# 日志文件路径
pipeline_stages:
# 解析 JSON 格式日志(如果应用输出 JSON)
- json:
expressions:
level: level
msg: message
timestamp: timestamp
- labels:
level:
# 过滤健康检查日志(减少无用日志量)
- drop:
expression: '.*GET /health.*'在 Grafana 中查询日志
LogQL 是 Loki 的查询语言,语法类似 PromQL:
# 查看特定应用的日志
{namespace="production", app="user-service"}
# 过滤关键词
{app="user-service"} |= "ERROR"
# 过滤 JSON 字段
{app="user-service"} | json | level="error"
# 统计每分钟错误数(用于告警)
sum(rate({app="user-service"} |= "ERROR" [1m])) by (pod)
# 查看特定请求 ID 的完整链路日志
{namespace="production"} |= "req-id-abc123"方案二:EFK(Elasticsearch + Fluent Bit + Kibana)
如果需要更强的全文检索和日志分析能力,用 EFK。
安装 Elasticsearch(使用 ECK Operator)
# 安装 ECK Operator
kubectl create -f https://download.elastic.co/downloads/eck/2.9.0/crds.yaml
kubectl apply -f https://download.elastic.co/downloads/eck/2.9.0/operator.yaml
# 创建 Elasticsearch 集群
kubectl apply -f - <<EOF
apiVersion: elasticsearch.k8s.elastic.co/v1
kind: Elasticsearch
metadata:
name: elasticsearch
namespace: monitoring
spec:
version: 8.10.2
nodeSets:
- name: default
count: 1 # 生产建议 3 节点
config:
node.store.allow_mmap: false
podTemplate:
spec:
containers:
- name: elasticsearch
resources:
requests:
memory: 2Gi
cpu: 0.5
limits:
memory: 4Gi
volumeClaimTemplates:
- metadata:
name: elasticsearch-data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: fast-ssd
resources:
requests:
storage: 50Gi
EOF安装 Fluent Bit(轻量级日志采集)
Fluent Bit 比 Fluentd 更轻量,推荐用 Fluent Bit:
# fluent-bit-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: fluent-bit-config
namespace: monitoring
data:
fluent-bit.conf: |
[SERVICE]
Flush 5
Log_Level info
Daemon off
Parsers_File parsers.conf
HTTP_Server On
HTTP_Listen 0.0.0.0
HTTP_Port 2020
[INPUT]
Name tail
Tag kube.*
Path /var/log/containers/*.log
Parser cri
DB /var/log/flb_kube.db
Mem_Buf_Limit 50MB
Skip_Long_Lines On
Refresh_Interval 10
[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
Kube_Tag_Prefix kube.var.log.containers.
Merge_Log On
Merge_Log_Key log_processed
K8S-Logging.Parser On
K8S-Logging.Exclude Off
[OUTPUT]
Name es
Match *
Host elasticsearch-es-http.monitoring.svc.cluster.local
Port 9200
HTTP_User elastic
HTTP_Passwd ${ELASTIC_PASSWORD}
TLS On
TLS.Verify Off
Index k8s-logs
Type _doc
Logstash_Format On
Logstash_Prefix k8s-logs
Retry_Limit False踩坑实录一:日志采集丢失 Pod 重启前的日志
现象:Pod 崩溃重启后,Loki/Elasticsearch 里没有崩溃前最后几秒的日志。
原因:容器日志文件在 Pod 删除后,如果 Promtail/Fluent Bit 的 positions 文件没有及时保存,会有丢失。
解法:配置更短的 flush 间隔,并确保 positions 文件持久化:
# Promtail 配置
positions:
filename: /var/log/positions.yaml
sync_period: 5s # 每 5s 同步一次 position(默认 10s)
# 将 positions 文件挂载到宿主机,跨 Pod 重启持久化
volumeMounts:
- name: positions
mountPath: /var/log/positions.yaml
subPath: positions.yaml
volumes:
- name: positions
hostPath:
path: /var/log/promtail-positions.yaml
type: FileOrCreate踩坑实录二:Elasticsearch 内存不够,JVM Heap 频繁 GC
现象:Elasticsearch Pod 日志里大量 [GC overhead limit exceeded],查询响应极慢,有时候直接 OOM 重启。
原因:Elasticsearch 的 JVM Heap 设置太小。ES 的经验规则是:heap 大小设为可用内存的 50%,且不超过 32GB。
解法:通过 ECK 配置 JVM 参数:
spec:
nodeSets:
- name: default
podTemplate:
spec:
containers:
- name: elasticsearch
env:
- name: ES_JAVA_OPTS
value: "-Xms2g -Xmx2g" # 2GB heap,限制 4GB memory 的 50%
resources:
requests:
memory: 4Gi
limits:
memory: 4Gi踩坑实录三:日志量暴涨,Loki 写入超出 rate limit
现象:某个服务出 bug 开始疯狂打日志(每秒几万行),Loki 开始拒绝写入,报 429 Too Many Requests,其他服务的日志也开始丢失。
原因:Loki 有 ingestion_rate_mb 限制,被一个失控的服务打满了。
解法:
- 临时处理:临时增大 limits 或者 kill 掉那个失控的服务
- 长期方案:在 Loki 配置里开启按租户(namespace)限流:
limits_config:
per_stream_rate_limit: 5MB # 单个 log stream 最多 5MB/s
per_stream_rate_limit_burst: 10MB
# 对于高日志量的服务,在 Promtail 里采样在 Promtail 里对高频日志做采样:
pipeline_stages:
- sampling:
rate: 0.1 # 只采集 10% 的 health check 日志
match:
selector: '{app="web-api"}'
pipeline_name: drop_health_checks
action: keep
stages:
- regex:
expression: "GET /health"日志系统是 K8s 运维体系里最基础的基础设施。没有集中日志,遇到问题两眼一抹黑,排查效率极低。
Loki 适合中小集群、已有 Grafana 的场景;EFK 适合日志量大、需要全文检索的场景。两套都能搭,关键是搭起来就不要再忽视它了。
