3.6. 集群日志系统:EFK 与 Fluent Bit
kubectl logs只能看当下,Pod 一删日志就散在节点磁盘里。要把全集群日志变成可检索、可聚合、可保留的数据,需要一条「采集 → 传输 → 存储 → 查询」的链路。本章用 Elasticsearch + Fluent Bit + Kibana 搭出这条链路,讲清每个 values 键在做什么,再与 Loki 做一次选型对照。
- 层级:
L3 · 平台
- 难度:
进阶
- 预计:
90 分钟
- 前置:
Helm 基础、PV/PVC 与日志采集模型
3.6.1. 你将学到
Kubernetes 日志从 CRI 文件到后端索引的完整路径,以及每一跳的边界。
EFK 各组件的角色划分:Fluent Bit 采集、Elasticsearch 存储与检索、Kibana 查询。
book-repo 中 elasticsearch、kibana、fluent-bit 三份 values 的关键键位与坑。
索引命名、Kibana 检索与 EFK/Loki 的选型依据。
3.6.2. 为什么需要它
容器日志有三个绕不开的问题:Pod 是临时的,重建后旧日志随节点文件清理;日志分散在每个节点,不可能逐台 SSH;排障要按 namespace、Pod、关键字、状态码组合检索,grep 撑不住。指标能告诉你「错误率涨了」,只有日志能告诉你「哪个请求、什么堆栈」。EFK 是最经典的组合——Fluent Bit 采集、Elasticsearch 建倒排索引、Kibana 提供检索界面,代价是资源与运维成本都高于 Loki。
3.6.3. 核心概念
3.6.3.1. 日志采集模型
容器运行时把 stdout/stderr 写成 CRI 日志文件,路径形如 /var/log/pods/<namespace>_<pod>_<uid>/<container>/0.log;/var/log/containers/ 下还有带 Pod、命名空间、容器名的符号链接,采集器用通配符就能发现全部容器。kubectl logs 由 kubelet 直接读这些文件,不经过 API Server 存储,所以 Pod 删除后日志通常就没了。
生产做法是在每个节点跑一个 DaemonSet:用 hostPath 挂载 /var/log/containers 与 /var/log/pods,通过 API 补充 Pod 元数据,给每行打上 namespace、pod、container、node 标签后发往后端。kubelet 默认按 containerLogMaxSize=10Mi、containerLogMaxFiles=5 轮转,采集器必须能处理文件被截断或重命名。
3.6.3.2. EFK 架构
三段职责:采集端是 Fluent Bit DaemonSet,负责 tail、解析、补元数据、输出;存储端是 Elasticsearch,按角色分为 master、coordinating、data、ingest;查询端是 Kibana,连 coordinating 节点的 9200 端口。
图:Pod 日志与 kubelet 日志在节点侧汇入 Fluent Bit,coordinating 只做路由与聚合,数据落在 data 节点,Kibana 通过 9200 检索。
原书用 bitnami 的 elasticsearch Chart,release 名为 es,因此 Service 是 es-elasticsearch-coordinating-only(fullname 规则为 <release>-<chart>)。Fluent Bit 与 Kibana 都指向这个名字,换 release 名要同步改两处 values。
3.6.3.3. values 里的关键配置
Elasticsearch 的 values 决定拓扑与容量,最该看懂的是这几项:
键 |
原书值 |
含义与判断 |
|---|---|---|
|
2 |
准主节点。偶数不满足多数派,一台故障就选不出 master,生产用 3 |
|
2 |
客户端入口,Service 端口 9200,不存数据 |
|
2 / 10Gi |
存分片与副本, |
|
512m / 128m / 2048m |
JVM 堆,建议不超过容器 limit 的 50%,留出堆外内存 |
|
false |
需要 ingest pipeline 时才开 |
|
true |
用 Pod 注解暴露指标;有 Operator 时改开 |
Kibana 的 values 只有三件事值得记:persistence.storageClass: longhorn、ingress.hosts[0].name: kibana.ilinux.io(class 为 nginx),以及 elasticsearch.hosts 指向 es-elasticsearch-coordinating-only.logging.svc.cluster.local.、端口 9200。末尾那个点是绝对 FQDN 写法,能解析,但与其他清单风格不一致,排错时容易被误认为域名写错。
Fluent Bit 的 values 全部是「一段段配置文本」,拼进 ConfigMap 后成为 fluent-bit.conf:
kind: DaemonSet
config:
inputs: |
[INPUT]
Name tail
Path /var/log/containers/*.log
Parser cri
Tag kube.*
Mem_Buf_Limit 5MB
Skip_Long_Lines On
filters: |
[FILTER]
Name kubernetes
Match kube.*
Merge_Log On
Keep_Log Off
K8S-Logging.Exclude On
outputs: |
[OUTPUT]
Name es
Match kube.*
Host es-elasticsearch-coordinating-only.logging.svc.cluster.local
Logstash_Format On
Logstash_Prefix k8s-cluster
Replace_Dots On
几个键的实际作用:Parser cri 剥掉 CRI 头(时间戳 stdout F)并取到正确时间;原书用的是 Parser docker,在 containerd 集群上时间解析会失败,同目录的 fluent-fluent-bit-with-parsers-values.yaml 才是改好的版本。Merge_Log On 把容器输出的 JSON 展开成字段;Mem_Buf_Limit 5MB 与 Skip_Long_Lines On 用丢超长行换内存安全;Logstash_Format On + Logstash_Prefix 让索引按天滚成 k8s-cluster-2026.11.08;Replace_Dots On 把字段名里的点换成下划线,否则 ES 会把 kubernetes.pod_name 当成嵌套对象路径。
备注
Type 在 ES 7 之后无效
原书 outputs 里写了 Type flb_type,那是 ES 6 的 mapping type。ES 7 起类型被移除,这个键不再生效,字段映射要交给 index template 或 component template 管理。
3.6.3.4. 索引与检索
日志按天分索引,Kibana 里建 k8s-cluster-* 的 index pattern 才能检索,时间字段选 @timestamp。分索引的好处是生命周期管理简单:ILM 按「热 → 温 → 删」滚动,删除即删整个索引,比在大索引里 delete by query 便宜。代价是索引数随天数线性增长,分片要提前规划:单分片 10–50GB 是常见区间,日增量小就该用 rollover 而不是一天一个索引。
kubectl -n logging port-forward svc/es-elasticsearch-coordinating-only 9200:9200
curl -s 'http://127.0.0.1:9200/_cat/indices/k8s-cluster-*?v&h=index,docs.count,store.size'
3.6.3.5. EFK 与 Loki 的选型
维度 |
EFK |
Loki |
|---|---|---|
索引方式 |
倒排索引,全文可查 |
只索引标签,正文压缩 |
存储成本 |
高,通常 3–10 倍 |
低 |
查询能力 |
全文检索、聚合、KQL/Lucene |
LogQL 标签定位 + 行过滤 |
资源占用 |
JVM 堆 + 分片,节点要求高 |
轻量,后端可用对象存储 |
运维复杂度 |
高,分片、副本、ILM 都要管 |
低 |
判断标准很简单:需要「任意字段全文搜索、聚合分析、给非运维人员用」选 EFK;需要「按 namespace/Pod 定位、低成本长期保留、与 Prometheus 共用 Grafana」选 Loki。两者也可以并存,热数据进 Loki,审计与合规数据进 EFK。
3.6.3.6. 资源与生产注意事项
master 必须奇数:2 个 master 是原书 values 的妥协,生产至少 3 个,且与 data 节点分离。
堆与 limit 配套:
heapSize只是 JVM 堆,容器 limit 还要加上 Lucene 的堆外缓存;只设 requests 会拖垮节点。磁盘水位:默认 85% 触发只读、90% 开始迁分片、95% 写不进去,必须提前扩容或调 ILM。
采集端要有背压:ES 不可用时内存缓冲会涨到
Mem_Buf_Limit上限然后丢日志,生产应开 filesystem 缓冲并设storage.total_limit_size。
3.6.4. 动手实验
实验:Helm 部署 Elasticsearch + Kibana + Fluent Bit
前置:Kubernetes v1.31;有 longhorn 或 nfs-csi StorageClass;Helm 3;节点内存 ≥ 8GiB。
3.6.4.1. 步骤 1 · 准备命名空间与仓库
kubectl create namespace logging
helm repo add bitnami https://charts.bitnami.com/bitnami && helm repo update
kubectl get sc # 确认 values 里的 storageClass 真实存在
3.6.4.2. 步骤 2 · 部署 Elasticsearch
master:
replicas: 3
heapSize: 512m
persistence: {enabled: true, storageClass: longhorn, size: 8Gi}
coordinating:
replicas: 2
service: {type: ClusterIP, port: 9200}
data:
replicas: 2
heapSize: 1024m
persistence: {enabled: true, storageClass: longhorn, size: 10Gi}
helm install es bitnami/elasticsearch -n logging -f es-values.yaml
kubectl -n logging get sts,pvc
kubectl -n logging port-forward svc/es-elasticsearch-coordinating-only 9200:9200 &
curl -s 'http://127.0.0.1:9200/_cluster/health?pretty' | jq '{status,number_of_nodes}'
status 为 green 再继续;yellow 表示副本没分够,单节点实验环境正常,但说明副本数大于可用节点数。
3.6.4.3. 步骤 3 · 部署 Kibana
helm install kibana bitnami/kibana -n logging \
--set persistence.storageClass=longhorn \
--set 'elasticsearch.hosts[0]=es-elasticsearch-coordinating-only.logging.svc.cluster.local'
kubectl -n logging port-forward svc/kibana 5601:5601
打开 http://127.0.0.1:5601,进 Stack Management → Index Patterns 建 k8s-cluster-*,时间字段选 @timestamp;此时还没有数据,下一步补上采集端。
3.6.4.4. 步骤 4 · 部署 Fluent Bit
helm install fluent-bit ./fluent-bit-0.6.2.tgz -n logging -f fluent-bit-values.yaml
kubectl -n logging get ds fluent-bit
kubectl -n logging logs ds/fluent-bit --tail=20
仓库自带的 Chart 是 fluent-bit-0.6.2.tgz(对应 Fluent Bit 1.5.2)。values 里要确认三处:Parser cri(不是 docker)、outputs 的 Host 指向 coordinating Service、tolerations 让 DaemonSet 能落到控制平面节点。日志里出现 [output:es] 的连接失败会重试,先解决 ES 再排查采集。
3.6.4.5. 步骤 5 · 造日志并检索
kubectl run loggen --image=busybox:1.36 --restart=Never -- \
sh -c 'for i in $(seq 1 50); do echo "{\"level\":\"info\",\"msg\":\"hello-efk\",\"i\":$i}"; sleep 1; done'
sleep 20 && curl -s 'http://127.0.0.1:9200/_cat/indices/k8s-cluster-*?v'
curl -s 'http://127.0.0.1:9200/k8s-cluster-*/_search?q=hello-efk&size=1' | jq '.hits.hits[0]._source'
输出应形如 {"level": "info", "msg": "hello-efk", "i": 50}。能查到 msg 与 level 两个字段,说明 Merge_Log 生效、JSON 被展开;只能查到整行字符串就回去检查 Merge_Log On 与 Merge_Parser。Kibana 的 Discover 里按 kubernetes.namespace_name: default 过滤,应能看到同一个 Pod 的日志。
3.6.4.6. 验证清单
es-elasticsearch-coordinating-only可访问,_cluster/health返回green或预期的yellow。Kibana 能打开,
k8s-cluster-*index pattern 有文档数。Fluent Bit DaemonSet 的 DESIRED 等于节点数,Pod 全部 Ready。
3.6.5. 常见问题与排错
现象 |
可能原因 |
处理方式 |
|---|---|---|
Fluent Bit 报 |
ES Service 名或端口写错,release 名不是 |
用 |
索引里没有日志 |
|
改 |
字段是一整行字符串 |
没开 |
开 |
索引变成只读 |
磁盘使用率超过 85%/95% 水位 |
扩容 PVC,或用 |
Pod Pending,PVC 不绑定 |
|
|
3.6.6. 进阶挑战
给 Fluent Bit 加
[FILTER] Name grep丢弃kube-system的噪声,并加一个[OUTPUT] Name loki双写,比较两套后端的结果。用 ILM 把
k8s-cluster-*的保留期设为 7 天,热节点 2 天、温节点 5 天,验证 rollover 与删除。把 ES 堆调到 2GiB 并配 limit 4GiB,压测 5000 行/秒,观察 GC 与写入延迟的变化。
3.6.7. 小结
容器日志落在节点文件里,
kubectl logs只是 kubelet 的读取接口,生产必须用 DaemonSet 采集。EFK 的分工是「Fluent Bit 采集、ES 存储检索、Kibana 查询」,coordinating 是入口,data 存分片。
values 里最关键的四个点:master 用奇数、heap 与 limit 配套、
Parser cri、索引前缀与 index pattern 对齐。EFK 强在全文检索与聚合,Loki 强在成本与运维简单;按查询模式选,不按流行度选。