K8s 持久化 Pod 控制台日志到节点本地:使用 Fluent Bit
K8s 持久化 Pod 控制台日志到节点本地:一个 Fluent Bit 实战指南
动机
在 Kubernetes 的日常使用中,我们通常会把日志接入云服务商或自建的中心化日志系统(如 ELK、Loki)。但在某些场景下,这并不是最佳选择,甚至不可行。
比如,你可能遇到了以下情况:
- 环境限制:无法将日志上报到外部网络,或者压根就不想依赖外部服务。
- 简单持久化需求:不需要复杂的搜索和分析,只希望 Pod 销毁后,日志能作为文件留在节点上,方便以后排查。
- 快速调试需求:在开发测试时,只想快速、直接地看到某个应用的日志,不想经过层层转发和复杂的平台。
针对这些需求,我们可以采用一个更直接的方案:在 K8s 集群里部署一个日志代理,精确抓取特定应用的控制台日志,并把它写入每个节点主机的特定目录下。
本文将通过一个完整的实战案例,介绍如何使用 Fluent Bit(一个轻量级、高性能的日志处理工具)作为 DaemonSet 来实现这个目标。
解决方案架构
我们的方案由三个核心的 K8s 资源组成,它们协同工作,构成了一条完整的日志采集流水线:
- RBAC 权限 (ServiceAccount, ClusterRole, ClusterRoleBinding):这是“通行证”。Fluent Bit 需要权限查询 K8s API,以获取 Pod 的标签等元数据,否则它就无法分辨日志的来源。
- ConfigMap (配置中心):这是“大脑”。它包含了 Fluent Bit 的所有配置,告诉它该做什么、怎么做。我们在这里定义日志的输入、解析、过滤和输出规则。
- 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/部署与验证
应用配置: 将上面的配置保存为
fluent-bit-daemonset.yaml,然后执行:kubectl apply -f fluent-bit-daemonset.yaml检查 DaemonSet 状态: 确认 Fluent Bit 的 Pod 已经在你的节点上成功运行。
kubectl get ds -n my-app-ns # 期望输出中 DESIRED, CURRENT, READY 的数量应该一致验证日志文件: SSH 登录到运行你目标应用 (
my-app) 的任一节点,然后查看目标文件:tail -f /var/log/my-app/app.log此时,当你与
my-app应用交互时,应该能在这里看到实时输出的原始日志行。
常见问题排查 (Troubleshooting)
如果你发现日志文件为空,别慌,这很常见。我们可以按以下步骤排查:
确认源头有日志:你的应用真的在向控制台输出日志吗?我们可以绕过 Fluent Bit,直接用
kubectl检查。# 确保你的应用正在运行并处理流量 kubectl logs deployment/my-app -n my-app-ns -f如果这里没输出,那问题就在你的应用本身,而不是日志采集。
确认标签完全匹配:我们的过滤规则依赖精确的标签。检查一下 Pod 是否真的拥有我们指定的标签。
kubectl get pods -n my-app-ns --show-labels仔细核对
LABELS列,确保目标 Pod 拥有app: my-app。任何拼写错误都会导致过滤失败。检查 Fluent Bit 自身日志:Fluent Bit Pod 在启动或运行时报错了吗?
kubectl logs -n my-app-ns -l k8s-app=fluent-bit -f常见的错误包括权限不足(RBAC 问题)、配置文件语法错误或无法访问挂载目录。
总结与扩展
通过上面的步骤,我们就成功构建了一个轻量级、高针对性的 K8s 日志持久化系统。它不依赖任何外部服务,将指定应用的控制台日志以最纯粹的形式保存在了节点本地,完美解决了特定场景下的日志采集需求。
这个方案的真正强大之处在于它的扩展性。通过简单修改 ConfigMap,你可以:
- 更改过滤规则:采集不同应用或整个命名空间的日志。
- 更改输出目标:将日志发送到 Elasticsearch、S3、Loki 或其他任何 Fluent Bit 支持的后端,轻松从本地持久化方案升级为中心化日志系统。