先说说我们的场景
我们有个电商系统跑在 K8s 上,几十个微服务,日志量不小。一开始搭日志系统的时候,就面临一个选择:日志到底落哪儿?
这个问题看起来简单,但真正落地的时候发现坑还挺多的。
日志收集的几种思路
在 K8s 环境下,日志收集主要就这么几种玩法:
graph TB
subgraph "方式一:DaemonSet 采集"
A1[Pod] -->|写日志| B1[节点本地磁盘]
C1[DaemonSet Filebeat] -->|读取| B1
C1 -->|推送| D1[日志中心]
end
subgraph "方式二:Sidecar 采集"
A2[业务容器] -->|共享Volume| B2[日志目录]
C2[Sidecar容器] -->|读取| B2
C2 -->|推送| D2[日志中心]
end
subgraph "方式三:直推"
A3[Pod] -->|直接发送| D3[Kafka/日志中心]
end
DaemonSet 方式是最常见的,每个节点上跑一个采集器,去读本地的日志文件。Sidecar 方式隔离性好,但每个 Pod 都得多跑一个容器,资源开销大。直推方式对应用有侵入,而且应用挂了日志就丢了。
我们选的是 DaemonSet 方式,用 Filebeat 采集。
我们的架构长这样
flowchart LR
subgraph Node1[K8s Node]
Pod1[业务 Pod] -->|写日志| Local1[本地磁盘 /data/logs]
FB1[Filebeat DaemonSet]
FB1 -->|读取| Local1
end
subgraph Node2[K8s Node]
Pod2[业务 Pod] -->|写日志| Local2[本地磁盘 /data/logs]
FB2[Filebeat DaemonSet]
FB2 -->|读取| Local2
end
FB1 --> Logstash
FB2 --> Logstash
Logstash -->|格式化/过滤| ES[Elasticsearch]
ES --> Kibana
Pod 日志落到本地磁盘,通过 hostPath 挂载出来。每个节点上有个 Filebeat 的 DaemonSet,负责采集本节点所有 Pod 的日志,统一打到 Logstash,最后落到 ES。
中间为什么要加一层 Logstash?主要是集中管理方便。改个日志格式、加个字段、做个过滤,直接改 Logstash 配置就行,不用挨个节点去发 Filebeat 配置。这点在运维层面省了不少事。
日志落本地还是落 PV?
这是个绕不开的选择题。
落本地(hostPath)的好处:
- 快,没有网络开销,日志直接写本地磁盘
- 简单,不用管 PVC 那套东西
- 稳定,不依赖外部存储
落 PV 的好处:
- 日志跟着 Pod 走,Pod 漂移后日志还能找到
- 管理相对规范
我们最后选了落本地,原因很现实:每条日志都走网络写 PV,这个开销在日志量大的时候扛不住。而且 PV 还得申请 PVC,管理起来也麻烦。
但落本地有个很烦的问题——Pod 重启或漂移后,日志就散落在各个节点上,找起来很头疼。
那个"找不到日志"的坑
有一次线上出问题,需要看某个服务昨天的日志。但这个服务的 Pod 昨晚重启过一次,还漂移到了另一个节点。
当时的情况是这样的:
sequenceDiagram
participant 我 as 运维
participant K8s
participant ES
我->>K8s: 查 Pod 当前在哪个节点
K8s-->>我: 在 worker-15
我->>ES: 搜索昨天的日志
ES-->>我: 没有(因为昨天 Pod 在 worker-08)
我->>我: 懵了,日志去哪了?
日志其实还在,但在昨天那个节点 worker-08 上。问题是我怎么知道昨天 Pod 在哪个节点?
翻 K8s 事件?早就被清理了。看 Deployment 的历史?也看不出具体调度到哪个节点。
用 Prometheus 解决定位问题
后来我想到一个办法:Prometheus 里有 Pod 的调度信息。
kube-state-metrics 这个组件会暴露一个指标叫 kube_pod_info,里面包含了 Pod 和 Node 的对应关系。关键是,Prometheus 会按时间序列存储这些数据,所以我们可以回溯历史。
查询语句很简单:
# 查某个服务的 Pod 历史调度信息
kube_pod_info{namespace="prod", pod=~"order-service.*"}
返回的结果里会包含 node 标签,告诉你这个 Pod 当时在哪个节点上。切换时间范围,就能看到不同时间点 Pod 的位置。
flowchart TB
subgraph Prometheus
KSM[kube-state-metrics] -->|暴露| Metrics[kube_pod_info 指标]
Metrics -->|存储| TSDB[时序数据库]
end
用户 -->|PromQL 查询| Prometheus
Prometheus -->|返回| 结果["Pod: order-service-xxx<br/>Node: worker-08<br/>Time: 昨天 14:00"]
用户 -->|SSH| Worker08[worker-08]
Worker08 -->|找到| 日志[/data/logs/order-service/]
这个方案的好处是不需要额外部署什么东西,kube-state-metrics 和 Prometheus 本来就有,只是以前没想到可以这么用。
实际操作流程
现在我们找历史日志的流程变成了这样:
- 先确定要找的是哪个服务、什么时间段的日志
- 去 Prometheus 查那个时间段 Pod 在哪个节点
- SSH 到对应节点,去
/data/logs/服务名/目录下找
虽然比直接在 ES 里搜麻烦一点,但至少能找到了。ES 里能搜到的就直接搜,搜不到的再走这个流程。
几个要注意的地方
Prometheus 的数据保留时间
默认可能只保留 15 天,如果你想回溯更久的调度历史,得调整 retention 配置。我们改成了 30 天,基本够用。
日志目录的规范
既然日志落本地,目录结构最好规范一点。我们的约定是 /data/logs/{namespace}/{pod-name}/,这样找起来方便。
# Pod 里的挂载配置示例
volumeMounts:
- name: log-volume
mountPath: /app/logs
subPathExpr: $(POD_NAMESPACE)/$(POD_NAME)
volumes:
- name: log-volume
hostPath:
path: /data/logs
type: DirectoryOrCreate
日志清理
落本地的日志不会自动清理,时间长了磁盘会爆。我们在每个节点上跑了个 CronJob,定期清理 7 天前的日志。
# 简单粗暴的清理脚本
find /data/logs -type f -mtime +7 -delete
find /data/logs -type d -empty -delete
Filebeat 的配置
Filebeat 要能自动发现新的日志文件,配置里用通配符:
filebeat.inputs:
- type: log
enabled: true
paths:
- /data/logs/*/*.log
- /data/logs/*/*/*.log
# 加上 K8s 相关的元数据
fields:
log_source: k8s-hostpath
fields_under_root: true
整体链路
最后画个完整的链路图:
flowchart TB
subgraph K8s集群
subgraph Node1
P1[Pod A] -->|写日志| L1[/data/logs/ns/pod-a/]
P2[Pod B] -->|写日志| L2[/data/logs/ns/pod-b/]
FB1[Filebeat] -->|采集| L1
FB1 -->|采集| L2
end
subgraph Node2
P3[Pod C] -->|写日志| L3[/data/logs/ns/pod-c/]
FB2[Filebeat] -->|采集| L3
end
KSM[kube-state-metrics] -->|Pod调度信息| Prom[Prometheus]
end
FB1 --> LS[Logstash]
FB2 --> LS
LS -->|格式化| ES[Elasticsearch]
ES --> Kibana[Kibana 查询]
Prom -->|历史调度查询| 定位[定位 Pod 曾在哪个 Node]
定位 -->|SSH 到节点| 原始日志[查看原始日志文件]
写在最后
这套方案不是最优雅的,但在我们的场景下够用了。日志落本地性能好,Prometheus 补上了定位问题,整体运维成本可控。
如果日志量再大,或者对日志查询要求更高,可能就得考虑换成 Loki 这种更轻量的方案了。ES 确实有点重,这块后面有机会再折腾。
对了,还有个小技巧:Filebeat 的 registry 文件记得持久化,不然 Filebeat 重启后会重新采集已经采过的日志,ES 里就会有重复数据。这个坑我们也踩过,后来给 Filebeat 的 DaemonSet 也加了 hostPath 挂载。
说到这儿,我挺好奇的:你们的 K8s 日志是怎么收集的?落本地还是落 PV?有没有遇到过类似的坑?
我们这个用 Prometheus 反查 Pod 调度历史的方案,说实话有点"野路子",不知道有没有更正规的做法。如果你有更好的思路,或者觉得我们这个方案哪里有问题,欢迎留言交流,一起探讨。