K8s 微服务日志收集实践:日志落本地还是落 PV?以及那个"找不到日志"的坑

59 阅读5分钟

先说说我们的场景

我们有个电商系统跑在 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 本来就有,只是以前没想到可以这么用。

实际操作流程

现在我们找历史日志的流程变成了这样:

  1. 先确定要找的是哪个服务、什么时间段的日志
  2. 去 Prometheus 查那个时间段 Pod 在哪个节点
  3. 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 调度历史的方案,说实话有点"野路子",不知道有没有更正规的做法。如果你有更好的思路,或者觉得我们这个方案哪里有问题,欢迎留言交流,一起探讨。