跳过正文
Featured image for Kubernetes 日志采集方案选型:从技术对比到生产落地

Kubernetes 日志采集方案选型:从技术对比到生产落地

·668 字·4 分钟·
目录
K8s 完全指南 - 这篇文章属于一个选集。
§ : 本文

背景:一次被迫提速的日志系统建设
#

去年我们的微服务数量从十几个增长到将近五十个,分布在三套 EKS 集群上。那段时间有一次线上故障,某个服务在凌晨报 500,oncall 的同事需要翻查日志,结果发现每个服务只有 kubectl logs 可用,容器重启之后日志就丢了。那次故障定位花了将近三小时,其中两个小时是在找日志。

那之后日志系统建设被提上了高优先级。这篇文章记录我们整个选型和落地的过程,包括中间踩过的坑。


整体架构设计思路
#

在做技术选型之前,先把需求梳理清楚:

  1. 日志不丢失:容器重启或节点替换后,历史日志要能查到
  2. 延迟可接受:允许分钟级延迟,不需要实时
  3. 多集群统一入口:三套集群的日志要能在同一个地方查
  4. 资源占用可控:采集 Agent 不能抢占业务资源
  5. 运维成本低:团队只有 3 个 DevOps,不想维护太复杂的系统

基于这些约束,日志采集链路可以抽象为三层:

Pod/容器日志
    ↓
采集层(Agent)
    ↓
处理/聚合层(可选)
    ↓
存储层
    ↓
查询/展示层

每一层都有多个候选方案,下面逐层分析。


采集器选型
#

主要候选
#

目前 K8s 生态里比较成熟的采集器有四个:

Fluent Bit:C 语言编写,内存占用极低,官方给的数据是约 450KB 内存、不到 1% CPU。功能相对单一,主要做采集和基础过滤,复杂的数据处理能力不如 Fluentd。

Fluentd:Ruby 编写,生态丰富,插件系统完善。内存占用比 Fluent Bit 高一个数量级,通常在 40-100MB 左右,但数据处理和路由能力很强。

Filebeat:Elastic 家的产品,和 Elasticsearch 天然集成,配置直观。但它不支持太复杂的数据转换,灵活性不如 Fluentd。

Vector:Rust 编写,性能很好,近几年发展很快。但生产验证案例还不算多,我们当时评估后认为风险偏高。

我们最终决定用 Fluent Bit + Fluentd 的双层架构:Fluent Bit 以 DaemonSet 形式部署在每个节点做轻量采集,Fluentd 作为聚合层做数据处理和缓冲。

这个选择的核心理由是:把轻量和强处理能力分开,Fluent Bit 不占用业务资源,Fluentd 集中处理可以复用缓冲,减少对 ES 的直接压力。


部署模式:DaemonSet vs Sidecar
#

这是架构层面最重要的决策之一,两种模式有本质区别。

DaemonSet 模式
#

DaemonSet 部署一个 Agent 在每个 Node 上,读取宿主机的 /var/log/containers/ 目录,采集该节点所有 Pod 的日志。

优点很明显:资源复用效率高,一个 Agent 服务整个节点;运维管理简单,只需维护一套 DaemonSet。

缺点是所有 Pod 的日志都是混在一起的容器标准输出,如果应用把日志写到了容器内某个文件里而不是 stdout,DaemonSet 模式就采集不到。

Sidecar 模式
#

在每个 Pod 里注入一个 Sidecar 容器,专门采集主容器的日志,通过 emptyDir 共享卷读取日志文件。

优点是可以处理写文件的场景,也可以给不同 Pod 做完全独立的日志配置。

缺点是资源消耗翻倍,每个 Pod 多一个容器;而且如果 Pod 数量很多,维护成本会线性增长。

我们的选择
#

我们绝大多数服务都是云原生应用,日志输出遵循 12-Factor 规范,直接写 stdout。少数几个遗留服务写文件,这部分我们通过改造应用把日志导到 stdout 来解决,而不是引入 Sidecar 的复杂性。

结论:统一用 DaemonSet,要求所有服务日志必须输出到 stdout/stderr。

这个决策省了大量运维成本,事实证明是对的。


存储层选型对比
#

采集层确定之后,存储层是另一个重要决策。我们重点评估了三个方案:

维度 Elasticsearch Loki ClickHouse
查询能力 全文检索,非常强 标签过滤 + LogQL,中等 SQL 查询,需要定义 schema
写入性能 较高,需要倒排索引 低,只索引标签 极高,列存压缩率好
存储成本 高(倒排索引本身很大)
运维难度 高(JVM 调优、分片管理)
生态成熟度 非常成熟 中等,Grafana 强依赖 较成熟但日志场景偏少
非结构化日志 支持良好 一般 需要结构化
团队熟悉度

Loki 的架构很轻量,资源占用确实低,但它的查询模型是基于标签的,对于我们需要做大量关键字检索(比如搜 traceId、error message)的场景,表现不够好。Loki 更适合"日志量巨大但查询需求简单"的场景。

ClickHouse 在分析场景下性能很好,但需要日志是结构化的,而我们有大量 Java 服务输出的是半结构化日志,改造成本高。

最终选择 Elasticsearch。虽然运维成本高,但我们团队有 ES 经验,查询能力是我们最核心的需求,这个取舍值得。


生产配置详解
#

Fluent Bit DaemonSet 核心配置
#

apiVersion: v1
kind: ConfigMap
metadata:
  name: fluent-bit-config
  namespace: logging
data:
  fluent-bit.conf: |
    [SERVICE]
        Flush         5
        Daemon        Off
        Log_Level     info
        Parsers_File  parsers.conf
        HTTP_Server   On
        HTTP_Listen   0.0.0.0
        HTTP_Port     2020

    [INPUT]
        Name              tail
        Tag               kube.*
        Path              /var/log/containers/*.log
        Parser            docker
        DB                /var/log/flb_kube.db
        Mem_Buf_Limit     50MB
        Skip_Long_Lines   On
        Refresh_Interval  10

    [FILTER]
        Name                kubernetes
        Match               kube.*
        Kube_URL            https://kubernetes.default.svc:443
        Kube_CA_File        /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
        Kube_Token_File     /var/run/secrets/kubernetes.io/serviceaccount/token
        Kube_Tag_Prefix     kube.var.log.containers.
        Merge_Log           On
        Merge_Log_Key       log_processed
        Keep_Log            Off
        K8S-Logging.Parser  On
        K8S-Logging.Exclude On
        Labels              On
        Annotations         Off

    [FILTER]
        Name    grep
        Match   kube.*
        Exclude $kubernetes['namespace_name'] logging

    [OUTPUT]
        Name          forward
        Match         kube.*
        Host          fluentd-aggregator.logging.svc.cluster.local
        Port          24224
        Retry_Limit   False

  parsers.conf: |
    [PARSER]
        Name        docker
        Format      json
        Time_Key    time
        Time_Format %Y-%m-%dT%H:%M:%S.%L
        Time_Keep   On

几个关键配置说明:

  • Mem_Buf_Limit 50MB:限制每个 tail 插件的内存用量,防止日志突增把节点内存吃满
  • DB /var/log/flb_kube.db:使用 SQLite 记录读取位点,容器重启后从上次位置续读
  • Exclude $kubernetes['namespace_name'] logging:过滤掉 logging 命名空间自身的日志,避免循环采集
  • K8S-Logging.Exclude On:支持 Pod 通过 annotation 主动排除自身日志采集

Fluentd 聚合层配置
#

# fluentd.conf 核心片段
<source>
  @type forward
  port 24224
  bind 0.0.0.0
</source>

<filter kube.**>
  @type record_transformer
  enable_ruby true
  <record>
    cluster_name "#{ENV['CLUSTER_NAME']}"
    @timestamp ${time.strftime('%Y-%m-%dT%H:%M:%S.%3NZ')}
  </record>
</filter>

<match kube.**>
  @type elasticsearch
  host "#{ENV['ES_HOST']}"
  port 9200
  scheme https
  ssl_verify true
  user "#{ENV['ES_USER']}"
  password "#{ENV['ES_PASSWORD']}"
  
  index_name fluentd-${record['kubernetes']['namespace_name']}-%Y.%m.%d
  
  <buffer tag,time>
    @type file
    path /var/log/fluentd-buffers/kubernetes.system.buffer
    flush_mode interval
    retry_type exponential_backoff
    flush_thread_count 2
    flush_interval 5s
    retry_forever true
    retry_max_interval 30
    chunk_limit_size 8M
    total_limit_size 512M
    overflow_action block
  </buffer>
</match>

overflow_action block 这个配置很关键,后面踩坑部分会详细说。


生产踩坑记录
#

坑一:日志量突增导致 ES 写入背压
#

有一次我们做了一个大促,流量翻了五倍,日志量随之暴增。ES 集群的写入队列满了,开始拒绝请求,报 429 Too Many Requests

当时 Fluentd 的 buffer 配置是默认的 overflow_action drop_oldest,结果丢失了大量日志,事后排查时完全看不到那个时间段的数据。

解决方案:

首先把 overflow_action 改成 block,这样 buffer 满的时候 Fluentd 会停止接收新数据而不是丢弃,背压会传递到 Fluent Bit,Fluent Bit 的 Mem_Buf_Limit 会触发,最终的代价是采集延迟增加,但日志不丢失。

其次把 total_limit_size 从 256M 调大到 512M,给更多的缓冲空间应对突发。

最后针对 ES 集群做了写入限流的自动扩容策略,在写入队列 utilization 超过 80% 时触发 data node 扩容。

# ES 集群告警规则
- alert: ElasticsearchHighIndexingLatency
  expr: |
    elasticsearch_indices_indexing_index_time_seconds_total / 
    elasticsearch_indices_indexing_index_total > 0.1
  for: 5m
  annotations:
    summary: "ES 写入延迟过高,检查 bulk queue"

坑二:Fluentd buffer 磁盘写满
#

某个节点的 /var/log/fluentd-buffers/ 目录把磁盘写满了,Fluentd Pod 直接 OOMKilled(其实是 buffer 写磁盘失败,但表现像是 OOM)。

原因是那个节点上恰好有一个异常的服务在死循环输出日志,Fluent Bit 采集速度远超 Fluentd 转发速度,buffer 文件持续增长。

解决方案:

把 buffer 目录挂载到独立的 PVC 上,和节点系统盘隔离:

volumeMounts:
  - name: buffer
    mountPath: /var/log/fluentd-buffers
volumes:
  - name: buffer
    persistentVolumeClaim:
      claimName: fluentd-buffer-pvc

同时对 buffer 大小加了硬性上限,超过后直接丢弃最老的 chunk,接受少量数据丢失换取系统稳定性。

另外加了 Pod 日志速率限制,通过 Fluent Bit 的 throttle filter 对单个 Pod 的日志输出做限流:

[FILTER]
    Name          throttle
    Match         kube.*
    Rate          1000
    Window        5
    Print_Status  true
    Interval      30s

坑三:Kubernetes filter 权限问题
#

刚部署完发现 Fluent Bit 的 Kubernetes filter 不工作,日志里没有 Pod 元数据。查日志发现是 API Server 返回 403。

原因是忘记给 Fluent Bit 的 ServiceAccount 绑定相应的 RBAC 权限:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: fluent-bit
rules:
  - apiGroups: [""]
    resources:
      - namespaces
      - pods
      - pods/logs
    verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: fluent-bit
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: fluent-bit
subjects:
  - kind: ServiceAccount
    name: fluent-bit
    namespace: logging

不同规模的选型建议
#

经过这段时间的实践,总结一下针对不同规模的建议:

小规模(< 10 个服务,单集群)

直接用 Loki + Promtail + Grafana。资源占用极低,部署简单,Grafana 通吃监控和日志。如果已经有 Prometheus + Grafana,接入成本几乎为零。不需要复杂的全文检索,LogQL 足够用了。

中规模(10-100 个服务,1-3 个集群)

Fluent Bit(DaemonSet)+ Elasticsearch + Kibana。Fluentd 聚合层可以根据日志量决定是否需要,日志量不大的话 Fluent Bit 直接输出到 ES 也可以。ES 用托管服务(AWS OpenSearch 或 Elastic Cloud),避免自己管 JVM 调优。

大规模(> 100 个服务,多集群)

Fluent Bit + Fluentd 双层架构 + Elasticsearch。这时候 Fluentd 的缓冲和路由能力就非常重要了。ES 要做好分片规划,按 namespace 或服务名分 index,避免单一超大 index。可以考虑引入 Kafka 在 Fluentd 和 ES 之间做流量削峰。

日志采集系统看起来简单,实际上生产环境里细节很多。最重要的两点:日志不丢失(buffer 策略)和资源隔离(避免日志系统影响业务)。其他功能都可以迭代,这两点要在设计阶段就定好。

Wenzhuo Huang
作者
Wenzhuo Huang
搞运维的工程师,写代码的运维人。专注 Kubernetes、AWS、GitOps 与基础设施可靠性。这个博客既是我的技术笔记本,也是我踩过的坑的受害者档案。
K8s 完全指南 - 这篇文章属于一个选集。
§ : 本文

相关文章

Kubernetes 资源管理实战——QoS、ResourceQuota、VPA 体系化实践

·739 字·4 分钟
我在生产中见过太多因为资源配置不当导致的事故:不设 limits 的服务把节点内存吃光导致 OOM 驱逐、requests 设得过高导致 Pod 调度不上去、HPA 配置错误导致扩缩失灵。这篇文章把 K8s 资源管理体系从头到尾捋一遍,让你建立完整的资源治理思路。