K8s 持久化 Pod 控制台日志到节点本地:使用 Fluent Bit

2577 字
13 分钟
K8s 持久化 Pod 控制台日志到节点本地:使用 Fluent Bit

K8s 持久化 Pod 控制台日志到节点本地:一个 Fluent Bit 实战指南#

动机#

在 Kubernetes 的日常使用中,我们通常会把日志接入云服务商或自建的中心化日志系统(如 ELK、Loki)。但在某些场景下,这并不是最佳选择,甚至不可行。

比如,你可能遇到了以下情况:

  • 环境限制:无法将日志上报到外部网络,或者压根就不想依赖外部服务。
  • 简单持久化需求:不需要复杂的搜索和分析,只希望 Pod 销毁后,日志能作为文件留在节点上,方便以后排查。
  • 快速调试需求:在开发测试时,只想快速、直接地看到某个应用的日志,不想经过层层转发和复杂的平台。

针对这些需求,我们可以采用一个更直接的方案:在 K8s 集群里部署一个日志代理,精确抓取特定应用的控制台日志,并把它写入每个节点主机的特定目录下。

本文将通过一个完整的实战案例,介绍如何使用 Fluent Bit(一个轻量级、高性能的日志处理工具)作为 DaemonSet 来实现这个目标。

解决方案架构#

我们的方案由三个核心的 K8s 资源组成,它们协同工作,构成了一条完整的日志采集流水线:

  1. RBAC 权限 (ServiceAccount, ClusterRole, ClusterRoleBinding):这是“通行证”。Fluent Bit 需要权限查询 K8s API,以获取 Pod 的标签等元数据,否则它就无法分辨日志的来源。
  2. ConfigMap (配置中心):这是“大脑”。它包含了 Fluent Bit 的所有配置,告诉它该做什么、怎么做。我们在这里定义日志的输入、解析、过滤和输出规则。
  3. DaemonSet (执行者):这是“工人”。通过 DaemonSet,我们确保每个节点上都运行一个 Fluent Bit Pod,负责采集该节点上所有容器的日志。

原理#

为了让我们的 Fluent Bit 配置更加清晰,我们有必要了解一下 K8s 节点上日志文件的命名规范。当你 SSH 登录到任何一个 K8s 工作节点并查看 /var/log/containers/ 目录时,你会看到一系列遵循严格格式的文件。

这个目录存放着节点上所有容器日志的实际物理文件

标准格式#

每个日志文件的名称都由几个部分拼接而成,格式如下:

<pod_name>_<namespace>_<container_name>-<container_id>.log

让我们来逐一拆解:

  • <pod_name>: 产生这条日志的 Pod 的完整名称。例如,my-app-7b5d8f9c6c-xyz
  • <namespace>: 该 Pod所在的 Kubernetes 命名空间。例如,my-app-ns
  • <container_name>: Pod 中产生日志的具体容器的名称。这对于一个 Pod 内有多个容器(例如,一个主应用容器和一个 sidecar 容器)的场景非常重要。
  • <container_id>: 由容器运行时(如 containerd 或 Docker)分配给该容器的一个唯一哈希值(ID)。这个 ID 确保了即使容器重启(Pod 不变,但容器实例是新的),日志文件也不会发生冲突。

举个例子#

假设我们有一个 Pod,信息如下:

  • Pod 名称: my-app-7b5d8f9c6c-xyz
  • 命名空间: my-app-ns
  • 容器名称: my-app-container

那么,它在节点上对应的日志文件名称就会是这样的: my-app-7b5d8f9c6c-xyz_my-app-ns_my-app-container-a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2.log

一个有用的快捷方式:/var/log/pods 目录#

除了 /var/log/containers/,你可能还会注意到一个名为 /var/log/pods/ 的目录。这个目录提供了一套更具可读性的符号链接(Symbolic Links),方便我们手动查找日志。

它的目录结构是这样的: /var/log/pods/<namespace>_<pod_name>_<pod_uid>/<container_name>/0.log

这里的 0.log 文件就是一个符号链接,它最终指向了 /var/log/containers/ 目录中对应的那个长长的、带有哈希值的实际日志文件。

理解了这个文件结构,我们 Fluent Bit 配置中的 Path /var/log/containers/*.log 就变得非常清晰了:它告诉 Fluent Bit 去监控这个目录下所有符合命名规范的日志文件,从而捕获节点上所有容器的输出。

完整配置清单#

要实现我们的目标,你需要将以下所有 K8s 资源清单整合到一个 YAML 文件中(例如 fluent-bit-daemonset.yaml)。

这个配置文件会创建一个 my-app-ns 命名空间,并部署所有需要的资源。它经过精心设计,能够:

  • 监控节点上所有容器的控制台输出。
  • 为每条日志附加其来源 Pod 的 K8s 标签。
  • 只保留那些带有 app: my-app 标签的 Pod 的日志。
  • 将这些日志的纯净、原始内容(不含任何 JSON 包装或时间戳前缀)写入到每个节点主机的 /var/log/my-app/app.log 文件中。
# --- 第 1 部分:RBAC 权限 ---
# Fluent Bit 需要从 K8s API 读取 Pod 和 Namespace 元数据的权限。
apiVersion: v1
kind: ServiceAccount
metadata:
name: fluent-bit
namespace: my-app-ns # 替换为你的目标命名空间
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: fluent-bit-read
rules:
- apiGroups: [""]
resources:
- namespaces
- pods
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: fluent-bit-read
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: fluent-bit-read
subjects:
- kind: ServiceAccount
name: fluent-bit
namespace: my-app-ns # 替换为你的目标命名空间
---
# --- 第 2 部分:FLUENT BIT 配置 ---
apiVersion: v1
kind: ConfigMap
metadata:
name: fluent-bit-config
namespace: my-app-ns # 替换为你的目标命名空间
labels:
k8s-app: fluent-bit
data:
# 主配置文件
fluent-bit.conf: |
[SERVICE]
Flush 5
Log_Level info
Parsers_File parsers.conf
[INPUT]
# 'tail' 插件是我们的输入源,用于监控文件内容
Name tail
Tag kube.*
# 这是 K8s 节点上所有容器日志的标准路径
Path /var/log/containers/*.log
# 使用下面定义的 'cri' 解析器来提取原始的日志消息
Parser cri
# 一个数据库文件,用于记录已读取的日志位置,防止重启后重复采集
DB /var/fluent-bit/db/flb_kube.db
Mem_Buf_Limit 5MB
[FILTER]
# 'kubernetes' 过滤器会用 K8s API 中的元数据来丰富日志记录
Name kubernetes
Match kube.*
# 将解析出的日志消息合并回主记录中
Merge_Log On
# 指定合并后日志消息所使用的键名
Merge_Log_Key log_processed
[FILTER]
# 'grep' 过滤器用于筛选来自特定应用的日志
Name grep
Match kube.*
# 这是核心过滤逻辑:只保留 'app' 标签为 'my-app' 的日志
Regex $kubernetes['labels']['app'] ^my-app$ # 将 'my-app' 替换为你的应用标签值
[OUTPUT]
# 'file' 输出插件将日志写入文件
Name file
Match kube.*
# 日志将被写入到宿主机的这个目录下
Path /host-logs/
# 指定输出日志文件的名称
File app.log
# 使用自定义模板来格式化输出
Format template
Template {log}
# 解析器配置文件
parsers.conf: |
[PARSER]
Name cri
Format regex
# 这个正则表达式用于剥离容器运行时添加的时间戳和流信息(stdout/stderr),
# 只捕获原始的日志内容并存入 'log' 字段。
Regex ^(?<time>[^ ]+) (?<stream>stdout|stderr) (?<logtag>[^ ]*) (?<log>.*)$
Time_Key time
Time_Format %Y-%m-%dT%H:%M:%S.%L%z
---
# --- 第 3 部分:DAEMONSET 部署 ---
# 这个 DaemonSet 确保集群中的每个节点上都运行一个 Fluent Bit Pod,
# 从而能够采集该节点上所有应用的日志。
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluent-bit
namespace: my-app-ns # 替换为你的目标命名空间
labels:
k8s-app: fluent-bit
spec:
selector:
matchLabels:
k8s-app: fluent-bit
template:
metadata:
labels:
k8s-app: fluent-bit
spec:
# 使用我们在第 1 部分中创建的 Service Account
serviceAccountName: fluent-bit
terminationGracePeriodSeconds: 10
# 定义卷,用于将宿主机路径和 ConfigMap 挂载到 Pod 中
volumes:
# 用于从宿主机读取容器日志的卷
- name: varlog
hostPath:
path: /var/log
# 用于容器运行时状态的卷(某些运行时需要)
- name: varlibdockercontainers
hostPath:
path: /var/lib/docker/containers
# 用于将最终的日志文件写入宿主机的卷
- name: host-log-output
hostPath:
path: /var/log/my-app # 替换为你期望的宿主机输出目录
type: DirectoryOrCreate
# 用于从 ConfigMap 挂载配置文件的卷
- name: config
configMap:
name: fluent-bit-config
# 用于在宿主机上存放 Fluent Bit 数据库文件的可写卷
- name: fluent-bit-db
hostPath:
path: /var/lib/fluent-bit/
type: DirectoryOrCreate
# 定义 Fluent Bit 容器
containers:
- name: fluent-bit
image: fluent/fluent-bit:latest
# 将上面定义的卷挂载到容器的文件系统中
volumeMounts:
- name: varlog
mountPath: /var/log
readOnly: true
- name: varlibdockercontainers
mountPath: /var/lib/docker/containers
readOnly: true
- name: host-log-output
mountPath: /host-logs
- name: config
mountPath: /fluent-bit/etc/
- name: fluent-bit-db
mountPath: /var/fluent-bit/db/

部署与验证#

  1. 应用配置: 将上面的配置保存为 fluent-bit-daemonset.yaml,然后执行:

    Terminal window
    kubectl apply -f fluent-bit-daemonset.yaml
  2. 检查 DaemonSet 状态: 确认 Fluent Bit 的 Pod 已经在你的节点上成功运行。

    Terminal window
    kubectl get ds -n my-app-ns
    # 期望输出中 DESIRED, CURRENT, READY 的数量应该一致
  3. 验证日志文件: SSH 登录到运行你目标应用 (my-app) 的任一节点,然后查看目标文件:

    Terminal window
    tail -f /var/log/my-app/app.log

    此时,当你与 my-app 应用交互时,应该能在这里看到实时输出的原始日志行。

常见问题排查 (Troubleshooting)#

如果你发现日志文件为空,别慌,这很常见。我们可以按以下步骤排查:

  1. 确认源头有日志:你的应用真的在向控制台输出日志吗?我们可以绕过 Fluent Bit,直接用 kubectl 检查。

    Terminal window
    # 确保你的应用正在运行并处理流量
    kubectl logs deployment/my-app -n my-app-ns -f

    如果这里没输出,那问题就在你的应用本身,而不是日志采集。

  2. 确认标签完全匹配:我们的过滤规则依赖精确的标签。检查一下 Pod 是否真的拥有我们指定的标签。

    Terminal window
    kubectl get pods -n my-app-ns --show-labels

    仔细核对 LABELS 列,确保目标 Pod 拥有 app: my-app。任何拼写错误都会导致过滤失败。

  3. 检查 Fluent Bit 自身日志:Fluent Bit Pod 在启动或运行时报错了吗?

    Terminal window
    kubectl logs -n my-app-ns -l k8s-app=fluent-bit -f

    常见的错误包括权限不足(RBAC 问题)、配置文件语法错误或无法访问挂载目录。

总结与扩展#

通过上面的步骤,我们就成功构建了一个轻量级、高针对性的 K8s 日志持久化系统。它不依赖任何外部服务,将指定应用的控制台日志以最纯粹的形式保存在了节点本地,完美解决了特定场景下的日志采集需求。

这个方案的真正强大之处在于它的扩展性。通过简单修改 ConfigMap,你可以:

  • 更改过滤规则:采集不同应用或整个命名空间的日志。
  • 更改输出目标:将日志发送到 Elasticsearch、S3、Loki 或其他任何 Fluent Bit 支持的后端,轻松从本地持久化方案升级为中心化日志系统。
K8s 持久化 Pod 控制台日志到节点本地:使用 Fluent Bit
https://axi.moe/posts/s7z75nvl/
作者
NolanHo
发布于
2025-08-30
许可协议
CC BY-NC-SA 4.0

目录