Skip to content

日志收集器对比 ​

最后更新: September 13, 2026

本指南对比 Fluent Bit、Grafana Alloy 和 OpenTelemetry Collector,并说明如何从已停止维护的 Promtail 安装迁移。请根据收集器实际的输入、输出插件、部署所需权限和故障行为来选型,而不是依据假想的内存占用或每秒事件数排名。

配置基线为 Fluent Bit 5.1.2、Alloy 1.19.2 和 OpenTelemetry Collector Contrib 0.160.0。发行版的版本、其包含的组件以及它支持的平台是彼此独立的检查项。

目录 ​

  1. 概述
  2. FluentBit
  3. Promtail
  4. Grafana Alloy
  5. OpenTelemetry Collector
  6. 对比与选型 (English)

概述 ​

日志收集器的角色 ​

日志来源经过收集与处理后进入所配置的日志目的地。

日志来源经过收集与处理后进入所配置的日志目的地。全屏打开 ↗

图中展示的是可能的目的地。这并不意味着每个收集器都原生支持每一种目的地,也不意味着启用多个输出就能对所有目的地提供事务性投递。

核心功能 ​

功能需要验证的内容
输入文件/API 权限、轮转、首次读取位置和来源归属
解析将容器运行时的分帧格式与应用 JSON 或堆栈跟踪分开处理
转换/过滤哪些记录和字段被修改或丢弃
元数据正确关联 Pod/namespace(命名空间)并控制标签基数
缓冲内存与持久化存储的差异、容量、重试和溢出策略
输出身份验证、TLS、租户映射、确认机制和目的地限制

每个来源只运行一条有意设计的收集路径。多个 agent 读取同一批文件,或文件读取器与 Kubernetes API 读取器同时针对相同的 Pod,都可能导致日志重复。offset、持久化队列和后端成功摄取是不同的状态。

平台收集注意事项
Linux Kubernetes 节点读取宿主机文件的 agent 需要挂载节点日志目录并具备被允许的 security context;journal 的位置各不相同
Windows 节点使用受支持的 Windows 构建/配置和实际的 Windows 路径;下文的 Linux manifest 并不适用
EKS Fargate不要安装读取宿主机文件的 DaemonSet;使用托管的 Fargate log router 或合适的基于 API/应用的路径
EKS Auto Mode确认可用的宿主机路径和 add-on 支持情况;托管组件输出的日志与应用 stdout 是不同的内容

示例将数据发送到一个已存在的、启用 mTLS 的私有日志网关。该网关必须信任每个 agent 的客户端证书、提供与其 DNS 名称匹配的证书、正确路由 Loki/OTLP 路径,并强制执行相应的租户策略。证书、DNS、网关配置和 NetworkPolicy 都是前置条件,不由这些代码片段创建。

FluentBit ​

概述 ​

Fluent Bit 是 Fluentd 生态(已毕业项目)中基于 C 的遥测 agent。当前的构建版本支持日志、指标、追踪和 OpenTelemetry 插件;早期那种“不支持追踪/不支持 OTLP”的对比说法并不准确。请确认所选镜像实际包含哪些插件。

Fluent Bit 5.1.2 和 AWS for Fluent Bit 是版本号各自独立的发行版。AWS 镜像的 tag 并不等于其内嵌的 Fluent Bit 版本。本示例固定使用官方上游镜像及其 manifest digest;下文链接的 AWS 专用指南使用它们各自经过审核的镜像基线。

架构 ​

Fluent Bit 的输入、parser、filter、缓冲和输出职责的概念图。

Fluent Bit 的输入、parser、filter、缓冲和输出职责的概念图。全屏打开 ↗

请将该图视为逻辑概览。缓冲并不等于恰好一次(exactly-once)保证,过滤也不会删除原始的容器日志文件。请保护节点存储,并在应用边界处控制敏感数据。

完整配置示例 ​

将以下内容保存为 fluent-bit.conf。它收集容器日志,并使用原生 C 实现的 loki 输出,其选项包括 line_format、tenant_id 和 auto_kubernetes_labels。另行开发的 Go 插件所使用的 LineFormat、TenantID、BatchWait 和 BatchSize 等名称不可互换使用。

ini
[SERVICE]
    Flush                     2
    Grace                     30
    Daemon                    Off
    Log_Level                 info
    HTTP_Server               On
    HTTP_Listen               0.0.0.0
    HTTP_Port                 2020
    Health_Check              On
    storage.path              /var/lib/fluent-bit/storage
    storage.sync              normal
    storage.checksum          On
    storage.backlog.mem_limit 32M

[INPUT]
    Name                      tail
    Tag                       kube.*
    Path                      /var/log/containers/*.log
    Exclude_Path              /var/log/containers/fluent-bit-*_logging_*.log
    multiline.parser          docker, cri
    DB                        /var/lib/fluent-bit/tail.db
    DB.locking                true
    Mem_Buf_Limit             32M
    Skip_Long_Lines           On
    Refresh_Interval          10
    Rotate_Wait               30
    Read_From_Head            Off
    storage.type              filesystem

[FILTER]
    Name                      kubernetes
    Match                     kube.*
    Kube_URL                  https://kubernetes.default.svc:443
    Kube_Tag_Prefix           kube.var.log.containers.
    Merge_Log                 On
    Merge_Log_Key             log_processed
    Keep_Log                  On
    K8S-Logging.Parser         Off
    K8S-Logging.Exclude        Off
    Use_Kubelet               Off
    Labels                    On
    Annotations               Off

[FILTER]
    Name                      lua
    Match                     kube.*
    script                    /fluent-bit/scripts/process.lua
    call                      process_log
    protected_mode            On

[OUTPUT]
    Name                      loki
    Match                     kube.*
    Host                      logs-gateway.logging.svc.cluster.local
    Port                      443
    tls                       On
    tls.verify                On
    tls.verify_hostname       On
    tls.ca_file               /fluent-bit/tls/ca.crt
    tls.crt_file              /fluent-bit/tls/tls.crt
    tls.key_file              /fluent-bit/tls/tls.key
    Labels                    job=fluent-bit,namespace=$kubernetes['namespace_name']
    line_format               json
    auto_kubernetes_labels    Off
    Retry_Limit               5
    storage.total_limit_size  1G

tail 数据库和文件系统 chunk 使用可写的状态挂载卷,而不是只读的日志挂载卷。已有的 offset 会从数据库恢复;Read_From_Head Off 会在首次发现文件时跳过其中已存在的内容。修改该设置前请先确定一个明确的回填策略。Skip_Long_Lines On、有限的重试次数和有限的存储都可能丢弃数据;请对这些情况进行监控。

排除规则匹配的是本示例自身的收集器 Pod。如果 namespace 或工作负载名称发生变化,请调整该规则,而不要不知不觉地排除某个 namespace 中的所有应用。在该配置中,Kubernetes annotation 无法覆盖 parser/排除策略。

示例使用 API server 获取元数据,不需要 kubelet 的 nodes/proxy 访问权限。如果启用 Use_Kubelet,请另行确认 kubelet 地址、授权、证书和网络访问。开放一个 HTTP 指标监听端口并不等于可以把收集器的 UI/健康检查端点公开暴露。

对于其他目的地,请有意识地选择对应的输出和工作负载身份:

  • CloudWatch Logs:使用原生的 cloudwatch_logs、预先创建好的 log group 和 agent ServiceAccount 的实际身份。不要添加不受支持的 compress 选项,也不要假设已具备创建/设置保留期的权限。
  • OpenSearch:为所选后端使用原生的 opensearch 输出,并配置正确的 SigV4 service/Region、TLS 和 typeless API 设置。
  • S3:为 s3 输出配置其自身的可写 store_dir、唯一的 object key 策略以及 bucket 前缀权限。它的缓冲/上传行为与通用的文件系统队列不同。在把它当作备份之前,请测试部分上传、重启恢复和数据取回。

如果添加 systemd 输入,请挂载该节点操作系统上实际存在的 journal,保持其 cursor 数据库可写,并为其 tag 添加匹配的输出。一个 host.systemd 输入在只有 Match kube.* 的输出时没有任何投递路径。宿主机的 journal/audit 文件并不是 EKS 控制平面的 API 审计日志。

Parser 配置 ​

tail 输入内置的 docker, cri 多行 parser 用于重组容器运行时的日志片段。这与拼接应用的 Java/Python/Go 堆栈跟踪是不同的事情。

格式处理方式
Docker JSON 外层封装先解析运行时封装,再解析应用 JSON
CRI/containerd/CRI-O解析时间戳、stream 和 partial/full 标记;重组 partial 记录
JSON 应用日志只解析应用负载;保留或显式处理格式错误/纯文本记录
Nginx/logfmt/自定义文本为该应用格式选择对应的 parser,而不是无条件串联所有 parser
应用堆栈跟踪使用经过测试的多行 parser,并设置 stream 边界、大小和超时限制

使用多行 filter 时,请遵循其关于重新发送/顺序的指引:把它放在那些否则会重复处理已重新发送记录的 filter 之前。请测试容器日志交错以及没有时间戳的异常场景;单一的“行以日期开头”这类通用表达式并不是万能的堆栈跟踪 parser。

Lua 脚本示例 ​

将以下内容保存为 process.lua。它会对已解析的应用对象中选定的 key 进行脱敏,包括嵌套的对象/数组,并移除未脱敏的原始重复内容。它不能检测任意文本中的所有 secret 或个人标识信息。

lua
-- Redacts selected structured keys; it is not a general PII detector.
local sensitive = {
    password = true, passwd = true, token = true, secret = true,
    api_key = true, ["api-key"] = true, authorization = true
}

local function redact(value, depth)
    if type(value) ~= "table" then
        return value
    end
    if depth > 8 then
        return "[DEPTH_LIMIT]"
    end
    for key, child in pairs(value) do
        if type(key) == "string" and sensitive[string.lower(key)] then
            value[key] = "***"
        elseif type(child) == "table" then
            value[key] = redact(child, depth + 1)
        end
    end
    return value
end

function process_log(tag, timestamp, record)
    local app = record["log_processed"]
    if type(app) == "table" then
        record["log_processed"] = redact(app, 0)
        -- Do not retain an unredacted duplicate of the parsed application JSON.
        record["log"] = nil
        if type(app["level"]) == "string" then
            record["level"] = string.upper(app["level"])
        else
            record["level"] = "UNKNOWN"
        end
    else
        if type(record["log"]) ~= "string" then
            record["log"] = "[NON_STRING_LOG]"
        end
        record["level"] = "UNKNOWN"
    end
    -- 2 changes the record while retaining the original Fluent Bit timestamp.
    return 2, timestamp, record
end

例如,password 字段会被脱敏,但诸如 "message": "password=..." 这样的文本不会被自动识别为凭据。纯文本日志仍然是纯文本。该转换不是一个故障即关闭(fail-closed)的安全边界;请保护源文件、本地存储和目的地,并在需要更强保证时使用应用侧的日志字段允许列表。

return 2 会在修改记录的同时保留 Fluent Bit 的原始时间戳。类型检查可避免格式错误的布尔型日志级别导致回调崩溃。这些转换是用原生 Lua 解释器测试的;并未运行完整的 Fluent Bit 容器。

DaemonSet 部署 ​

将以下内容保存为 fluent-bit-workload.yaml。它要求 logging 中存在 agent-gateway-client Secret,并包含 ca.crt、tls.crt 和 tls.key。这些是与部署环境相关的凭据;不要把示例私钥粘贴到 Git 中。

yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: fluent-bit
  namespace: logging
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: log-collector-fluent-bit
rules:
- apiGroups:
  - ''
  resources:
  - pods
  - namespaces
  verbs:
  - get
  - list
  - watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: log-collector-fluent-bit
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: log-collector-fluent-bit
subjects:
- kind: ServiceAccount
  name: fluent-bit
  namespace: logging
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluent-bit
  namespace: logging
spec:
  selector:
    matchLabels: &id001
      app.kubernetes.io/name: fluent-bit
  template:
    metadata:
      labels: *id001
    spec:
      serviceAccountName: fluent-bit
      nodeSelector:
        kubernetes.io/os: linux
      terminationGracePeriodSeconds: 45
      containers:
      - name: fluent-bit
        image: fluent/fluent-bit:5.1.2@sha256:d792375ca8e53be72fc25716c28f291f32c6fc6f4f31d12d0d14bc78cefe9226
        command:
        - /fluent-bit/bin/fluent-bit
        args:
        - -c
        - /fluent-bit/etc/fluent-bit.conf
        ports:
        - name: metrics
          containerPort: 2020
        securityContext:
          runAsUser: 0
          allowPrivilegeEscalation: false
          readOnlyRootFilesystem: true
          capabilities:
            drop:
            - ALL
        resources:
          requests:
            cpu: 100m
            memory: 128Mi
          limits:
            cpu: 500m
            memory: 512Mi
        livenessProbe:
          httpGet:
            path: /
            port: metrics
          initialDelaySeconds: 10
        readinessProbe:
          httpGet:
            path: /api/v1/health
            port: metrics
          initialDelaySeconds: 10
        volumeMounts:
        - name: logs
          mountPath: /var/log
          readOnly: true
        - name: state
          mountPath: /var/lib/fluent-bit
        - name: config
          mountPath: /fluent-bit/etc
          readOnly: true
        - name: scripts
          mountPath: /fluent-bit/scripts
          readOnly: true
        - name: tls
          mountPath: /fluent-bit/tls
          readOnly: true
        - name: tmp
          mountPath: /tmp
      volumes:
      - name: logs
        hostPath:
          path: /var/log
          type: Directory
      - name: state
        hostPath:
          path: /var/lib/fluent-bit
          type: DirectoryOrCreate
      - name: config
        configMap:
          name: fluent-bit-config
          items:
          - key: fluent-bit.conf
            path: fluent-bit.conf
      - name: scripts
        configMap:
          name: fluent-bit-config
          items:
          - key: process.lua
            path: process.lua
      - name: tls
        secret:
          secretName: agent-gateway-client
      - name: tmp
        emptyDir:
          sizeLimit: 32Mi

先保存前面的配置/脚本文件,然后在启动工作负载之前创建它们的 ConfigMap:

bash
kubectl create namespace logging --dry-run=client -o yaml |
  kubectl apply -f -
kubectl -n logging create configmap fluent-bit-config \
  --from-file=fluent-bit.conf --from-file=process.lua \
  --dry-run=client -o yaml | kubectl apply -f -
kubectl apply -f fluent-bit-workload.yaml
kubectl -n logging rollout status daemonset/fluent-bit

该 agent 以 root 运行,以便读取示例中的节点日志路径并写入其专用状态目录,同时不添加任何 capability、不允许特权提升、也不使用可写的根文件系统。使用 hostPath 仍然需要集群具备相应的 admission 策略。请根据实际的节点操作系统调整文件权限、SELinux/AppArmor、taint 和存储。不要仅仅为了让应用日志 agent 保持运行就赋予它保留的系统关键 PriorityClass。

45 秒的 Pod 终止宽限期长于 Fluent Bit 的 30 秒宽限期,但这并不能保证在长时间故障期间成功投递。资源限制仅作示例。请在后端验证真实记录,并检查 tail offset、健康状态和缓冲/重试指标;仅凭 Pod 处于 Ready 状态并不能证明数据已被摄取。

Promtail ​

概述 ​

Promtail 已于 2026 年 3 月 2 日结束生命周期(EOL)。 商业支持和后续更新均已终止。请将现有安装迁移到 Alloy 或其他受支持的客户端;不要为新的 Loki 部署选择 Promtail。此次宣布的停止维护不包括独立的 lambda-promtail 客户端。

架构 ​

这是一条历史数据路径,而不是安装建议。Loki 仓库的许可例外说明将 clients/(包括 Promtail 源码)列为 Apache-2.0;不要把 Loki 服务端的 AGPL 许可套用到每个客户端上。请检查实际 artifact 及其依赖的许可证。position 文件记录的是读取进度,而不是已确认的后端投递。

完整配置示例 ​

使用已有的 promtail.yaml 作为迁移输入,而不要去部署已过时的 2.9.4 镜像:

bash
alloy convert --source-format=promtail \
  --report=conversion-report.txt \
  --output=config.alloy promtail.yaml
alloy validate config.alloy

请检查生成的配置和诊断报告。不要把绕过转换错误当成正常的部署步骤。转换器支持几乎所有旧功能,但这并不是行为完全一致的无条件保证。

被审核的旧配置转换成功,但工具给出了警告:其全局读取速率限制会变成按管道的 stage.limit 限制、Promtail 自身的 tracing 配置可能需要手动迁移、以及 Alloy 输出的自监控指标不同。请更新告警/仪表板,并用真实数据验证这些变化。

管道 stage 细节 ​

以下是各自独立的概念,不是把所有 parser 依次运行的配方:

Promtail YAMLAlloy 对应项重要区别
cri / dockerstage.cri / stage.docker选择实际的运行时分帧格式
json、regex、logfmt对应的 stage.*选择正确的源字段和格式错误数据的处理策略
先 template,再 labels先 stage.template,再 stage.labels在把值复制到标签之前先做归一化
dropstage.dropPromtail 的 YAML key 是 drop,不是 stage.drop
matchstage.match分支只应作用于其目标 stream
metricsstage.metrics检查标签集合、空闲时间序列和指标名称
timestamp、multiline对应的 stage测试时间格式、stream 隔离和有界等待
outputstage.output替换日志行可能丢弃关联分析所需的字段
packstage.packJSON 行打包不同于 Loki 独立的 structured metadata 功能

不要默认把客户端 IP、订单 ID、trace ID 或所有任意的应用标签建入索引。Pod 和 filename 标签同样有基数成本。请决定哪些元数据必须以标签、structured metadata 还是日志正文的形式保持可用。

DaemonSet 部署 ​

用选定的、仍在维护的收集器替换已停止维护的工作负载,并有意识地保留来源/状态的过渡。旧的只读根文件系统示例中,位于 /tmp 下的 positions 文件既不持久也不可写;在 /run/promtail 上挂载卷并不能修复一个把文件写到别处的配置。

请协调好旧读取器的停止位置、新读取器的起始位置和后端检查。不要让两个读取器长期同时读取相同的日志。转换成功并不能验证 Secret 挂载、Kubernetes RBAC、journal 路径、状态迁移或后端投递。

Grafana Alloy ​

概述 ​

Alloy 是 Grafana 的 OpenTelemetry Collector 发行版,附带 Prometheus 和 Loki 组件。它的配置语言称为 Alloy 配置语法(以前叫 River);它类似 HCL,但并不是可以互换使用的 Terraform 文件。

River 配置 ​

将以下内容保存为 config.alloy。该示例针对 Linux CRI 日志使用单一的基于文件的路径。通过 Pod Downward API 从 spec.nodeName 设置非敏感的 NODE_NAME,挂载节点日志文件,并提供可写的持久化 --storage.path。

alloy
logging {
  level = "info"
}

discovery.kubernetes "pods" {
  role = "pod"
  selectors {
    role = "pod"
    field = "spec.nodeName=" + sys.env("NODE_NAME")
  }
}

discovery.relabel "pods" {
  targets = discovery.kubernetes.pods.targets
  rule {
    source_labels = ["__meta_kubernetes_namespace", "__meta_kubernetes_pod_label_app_kubernetes_io_name"]
    regex = "logging;alloy"
    action = "drop"
  }
  rule {
    source_labels = ["__meta_kubernetes_namespace"]
    target_label = "namespace"
  }
  rule {
    source_labels = ["__meta_kubernetes_pod_name"]
    target_label = "pod"
  }
  rule {
    source_labels = ["__meta_kubernetes_pod_container_name"]
    target_label = "container"
  }
  rule {
    source_labels = ["__meta_kubernetes_pod_label_app_kubernetes_io_name"]
    target_label = "service_name"
    regex = "(.+)"
  }
  rule {
    source_labels = ["__meta_kubernetes_pod_uid", "__meta_kubernetes_pod_container_name"]
    separator = "/"
    target_label = "__path__"
    replacement = "/var/log/pods/*$1/*.log"
  }
}

local.file_match "pods" {
  path_targets = discovery.relabel.pods.output
}

loki.source.file "pods" {
  targets = local.file_match.pods.targets
  forward_to = [loki.process.pods.receiver]
  tail_from_end = true
}

loki.process "pods" {
  forward_to = [loki.write.logs.receiver]
  stage.cri {}
  stage.json {
    expressions = {
      level = "level",
    }
    drop_malformed = false
  }
  stage.template {
    source = "level"
    template = "{{ if .Value }}{{ $v := ToUpper .Value }}{{ if or (eq $v \"TRACE\") (eq $v \"DEBUG\") (eq $v \"INFO\") (eq $v \"WARN\") (eq $v \"WARNING\") (eq $v \"ERROR\") (eq $v \"FATAL\") (eq $v \"CRITICAL\") }}{{ $v }}{{ else }}UNKNOWN{{ end }}{{ else }}UNKNOWN{{ end }}"
  }
  stage.labels {
    values = {
      level = "",
    }
  }
  stage.label_drop {
    values = ["filename"]
  }
  // Retain the application line, including its trace ID; do not assume it is safe.
}

loki.write "logs" {
  endpoint {
    url = "https://logs-gateway.logging.svc.cluster.local/loki/api/v1/push"
    batch_wait = "1s"
    batch_size = "1MiB"
    tls_config {
      ca_file = "/etc/alloy/tls/ca.crt"
      cert_file = "/etc/alloy/tls/tls.crt"
      key_file = "/etc/alloy/tls/tls.key"
      insecure_skip_verify = false
    }
  }
  external_labels = {
    cluster = "lab-cluster",
  }
}

配置中指定的 mTLS 证书文件必须存在。Kubernetes 服务发现权限和相应的网关策略需要另行配置。使用所选的 Alloy 二进制文件校验该文件:

bash
alloy validate config.alloy

严重级别标签被限制为已知级别和 UNKNOWN,同时应用日志行仍然保留,可用于按 trace ID 查询。这不是一条脱敏管道。filename 标签被移除;Pod/container 标签被保留,仍应结合保留期/基数限制进行评估。

若采用基于 API 的收集,请使用 loki.source.kubernetes 替代文件读取器,并移除 CRI/Docker 封装解析 stage:Kubernetes 日志 API 提供的就是应用日志行。一个 API 收集器即可收集整个集群,无需宿主机挂载。多个实例需要有意识的目标分区,或配置 Alloy clustering 并让相关组件参与其中;单纯增加副本数可能导致重复收集。

在本次审核的版本中,env() 仍是已弃用的函数;非敏感配置请使用 sys.env()。相比通过环境变量导出令牌,优先使用挂载的凭据文件或支持 secret 的组件。

Alloy 的自监控指标可以被抓取。若要将它们发送到 Prometheus 的 /api/v1/write,还需要启用 remote write 接收端,或使用为 remote write 设计的后端;仅有 URL 并不能启用该能力。请将指标/UI 访问保持在私有范围内。请为该部署显式配置遥测/上报行为。

从 Promtail 迁移 ​

以下内容需要各自独立地保留:运行时解析、服务发现标签、应用字段、offset、记录丢弃策略、客户端身份验证和自监控指标告警。一个使用未定义服务发现组件的 API 源示例,或把 stage.docker parser 应用到已解码的 API 日志上,都不是完整的迁移。

Alloy 1.19.2 提供可选的 Loki WAL,但该功能是实验性的且默认关闭。主示例没有启用它。持久化的来源读取位置不等于持久的确认队列;请分别评估重试限制、来源轮转和任何 WAL 保留策略。

OpenTelemetry Collector ​

概述 ​

OpenTelemetry 提供厂商中立的遥测管道,并已于 2026 年 5 月 11 日成为 CNCF 毕业项目。请使用包含所需 receiver/processor/exporter 的发行版;core 发行版并不包含所有 Contrib 组件。

OTLP 可以使用 Protobuf 或 JSON。传输体积取决于实际字段、资源分组、压缩和传输方式。Filebeat/Fluentd 也可以批量发送。用 Protobuf tag 替换字段名并不会消除任意 JSON 正文或属性键中的字符串。

举一个有条件成立的算术例子:如果一条管道每个 Kafka 记录存放一个事件,而另一条每个记录打包 150 个事件,那么在后者中 1,000 个事件大约需要七条记录。这并不能说明网络请求量会等比例减少,或吞吐量会提升 18 倍。请在硬件、数据、目的地和持久化设置都匹配的条件下对完整管道做基准测试。

架构 ​

Loki 通过其 OTLP 端点接入。Contrib 0.160.0 中已不存在停止维护的 Collector loki exporter。集群范围的 Kubernetes event 以及集中式 Syslog/OTLP receiver 需要各自的归属和部署模型;不要在每个节点上都重复部署一个 event watcher。

完整配置示例 ​

将以下内容保存为 otel.yaml。它使用 Contrib 的 container operator 进行运行时解析/重组,并生成文件路径相关的资源元数据。要使用所指定的 Kubernetes 关联方式,Pod UID 必须是一个资源属性;仅提取普通的 attributes.uid 是不够的。

yaml
extensions:
  file_storage/offsets:
    directory: /var/lib/otelcol/offsets
    create_directory: true
  file_storage/queue:
    directory: /var/lib/otelcol/queue
    create_directory: true
  health_check:
    endpoint: 0.0.0.0:13133

receivers:
  filelog:
    include: [/var/log/pods/*/*/*.log]
    exclude: [/var/log/pods/logging_otel-collector-*/*/*.log]
    start_at: end
    include_file_path: true
    storage: file_storage/offsets
    retry_on_failure:
      enabled: true
      max_elapsed_time: 5m
    operators:
      - type: container
        id: container-parser

processors:
  memory_limiter:
    check_interval: 1s
    limit_mib: 400
    spike_limit_mib: 100
  k8sattributes:
    auth_type: serviceAccount
    filter:
      node_from_env_var: NODE_NAME
    pod_association:
      - sources:
          - from: resource_attribute
            name: k8s.pod.uid
    extract:
      metadata:
        - k8s.namespace.name
        - k8s.pod.name
        - k8s.pod.uid
        - k8s.node.name
        - k8s.container.name
  resource/cluster:
    attributes:
      - key: k8s.cluster.name
        value: lab-cluster
        action: upsert
  transform/application:
    error_mode: ignore
    log_statements:
      - context: log
        statements:
          - 'set(cache["app"], ParseJSON(body)) where IsString(body) and IsMatch(body, "^\\s*\\{")'
          - 'set(severity_text, ConvertCase(cache["app"]["level"], "upper")) where IsMap(cache["app"]) and IsString(cache["app"]["level"])'
          - 'set(severity_number, SEVERITY_NUMBER_ERROR) where severity_text == "ERROR"'
          - 'set(severity_number, SEVERITY_NUMBER_WARN) where severity_text == "WARN" or severity_text == "WARNING"'
          - 'set(severity_number, SEVERITY_NUMBER_INFO) where severity_text == "INFO"'
          - 'set(severity_number, SEVERITY_NUMBER_DEBUG) where severity_text == "DEBUG"'
          - 'set(severity_number, SEVERITY_NUMBER_TRACE) where severity_text == "TRACE"'
          - 'set(severity_number, SEVERITY_NUMBER_FATAL) where severity_text == "FATAL" or severity_text == "CRITICAL"'
  batch:
    send_batch_size: 1024
    send_batch_max_size: 2048
    timeout: 2s

exporters:
  otlp_http/loki:
    endpoint: https://logs-gateway.logging.svc.cluster.local/otlp
    encoding: proto
    compression: gzip
    tls:
      ca_file: /etc/otelcol/tls/ca.crt
      cert_file: /etc/otelcol/tls/tls.crt
      key_file: /etc/otelcol/tls/tls.key
    sending_queue:
      enabled: true
      num_consumers: 2
      queue_size: 128
      storage: file_storage/queue
    retry_on_failure:
      enabled: true
      max_elapsed_time: 5m

service:
  extensions: [file_storage/offsets, file_storage/queue, health_check]
  telemetry:
    logs:
      level: info
    metrics:
      readers:
        - pull:
            exporter:
              prometheus:
                host: 0.0.0.0
                port: 8888
  pipelines:
    logs:
      receivers: [filelog]
      processors: [memory_limiter, k8sattributes, resource/cluster, transform/application, batch]
      exporters: [otlp_http/loki]

请提供 NODE_NAME 的 Downward API 取值、只读的节点日志挂载、可写的 /var/lib/otelcol、获取 Kubernetes 元数据所需的 RBAC 以及客户端证书挂载。存储 extension 会在该可写卷内部创建自己的目录。使用实际部署环境的取值进行校验:

bash
otelcol-contrib validate --config=otel.yaml

该 exporter 会在 /otlp 后面追加 /v1/logs;请据此配置网关以及 Loki 的 OTLP/structured metadata 支持。在该基线中,service.telemetry.metrics.readers 取代了旧的无效 address key。

memory_limiter 可以用可重试的错误拒收数据并请求垃圾回收。它不是进程内存上限,也不是绝对的 OOM 保证。上游的重试行为很重要;本文的文件 receiver 只会在有限的五分钟内重试,超时之后失败的批次可能被丢弃。队列容量、磁盘容量、关闭流程和后端故障仍需测试。

应用日志正文会被保留,包括纯文本和格式错误的 JSON。解析严重级别并不等于移除敏感数据。不要额外添加一个详细 debug exporter,否则可能无意间把未脱敏的生产负载复制到收集器日志中。

Routing Connector ​

这个独立的本地路由演示定义了所有被引用的组件,并包含一条兜底路由。由 OTLP 生产者提供 resource.attributes["logtype"];应用 JSON 正文中的某个字段不会自动变成资源属性。

yaml
receivers:
  otlp:
    protocols:
      http:
        endpoint: 127.0.0.1:4318

connectors:
  routing:
    default_pipelines: [logs/other]
    table:
      - condition: resource.attributes["logtype"] == "mysql"
        pipelines: [logs/mysql]
      - condition: resource.attributes["logtype"] == "nginx"
        pipelines: [logs/nginx]
      - condition: resource.attributes["logtype"] == "app"
        pipelines: [logs/app]

exporters:
  file/mysql:
    path: /var/lib/otelcol/routed/mysql.json
  file/nginx:
    path: /var/lib/otelcol/routed/nginx.json
  file/app:
    path: /var/lib/otelcol/routed/app.json
  file/other:
    path: /var/lib/otelcol/routed/other.json

service:
  pipelines:
    logs/ingestion:
      receivers: [otlp]
      exporters: [routing]
    logs/mysql:
      receivers: [routing]
      exporters: [file/mysql]
    logs/nginx:
      receivers: [routing]
      exporters: [file/nginx]
    logs/app:
      receivers: [routing]
      exporters: [file/app]
    logs/other:
      receivers: [routing]
      exporters: [file/other]

运行该示例前请先创建可写的输出目录。它的 receiver 绑定在回环地址,目的地是本地文件;它既不是生产网关,也不是 ClickHouse 部署。只有在配置好每个真实后端、身份验证和存储策略之后,才可替换这些本地输出。

当前的 connector 接受 statement: route() where ...;这一点已经过验证,不应被错误地当成已移除。condition 是这里使用的更清晰的写法。默认的 move 动作会把匹配到的数据从后续路由中移除;copy 的分发行为不同。未匹配的记录需要一条有意设计的兜底路径。

只有在 ACL、保留期、分区、消费者归属和故障隔离仍然合适的情况下,合并 Kafka topic 才可能简化管理。在 Collector 内部做分类并不能替代 broker 层面的隔离,也不会让分发变成原子操作。由生产者控制的路由属性不是租户授权边界。

按日志级别分池(大规模环境) ​

池示例目标所需控制手段
快速:ERROR/FATAL两分钟内送达预留容量、适当的队列/分区隔离和可度量的积压
常规:INFO/WARN15 分钟内送达经过度量的自动扩缩和有界的保留期/队列
调试:DEBUG/TRACE尽力而为明确的丢弃/限流策略,以及对被丢弃数据的可见性

这些是示意性目标,不是实测的 SLA。仅仅命名三个 Deployment 或分配副本数并不会路由数据,也不会隔离一个共享的、已拥塞的输入队列。请根据观测到的输入体积、处理、批处理和重试情况来设置资源 request 和 limit。

请使用适合该集群、由运维方自行定义的 PriorityClass,而不要把保留的 system-cluster-critical/system-node-critical 类别当作通用的日志方案建议。专用节点、优先级和预留容量并不能保证在所有故障期间都可用。

对比与选型指南 ​

功能对比表 ​

项目Fluent BitPromtailAlloyOTel Collector Contrib
生命周期维护中已 EOL;需迁移维护中维护中
配置经典配置 / YAML旧版 YAMLAlloy 语法YAML
信号取决于插件的日志/指标/追踪主要是 Loki 日志日志/指标/追踪取决于组件的日志/指标/追踪
Loki 路径原生输出旧版 push 客户端Loki 组件OTLP HTTP exporter
AWS 输出有原生插件可用不是它的用途检查包含的组件/转发路径检查包含的 AWS exporter
可扩展性C/插件、Lua filter,以及其他取决于构建的选项旧版管道 stage组件和管道receiver/processor/connector/exporter
持久化输入 chunk/状态和特定输出的存储来源 position;有界的客户端缓冲position;可选的实验性 Loki WAL用于 offset 的 file storage 和受支持的 exporter 队列
资源占用请测量所选配置仅有历史测量数据请测量所选配置请测量所选配置

原生插件支持与把 OTLP 转发到第二个收集器不是一回事。在断定某个目的地受支持或不受支持之前,请确认所安装发行版实际包含的组件列表以及后端协议。

按使用场景的建议 ​

  • 如果需要现有的原生输出集成,且节点 agent 的资源占用适合你实测的工作负载,可评估 Fluent Bit。
  • 面向 Grafana/Loki/Prometheus 工作流和 Promtail 迁移时,可评估 Alloy,并采用一种有意设计的来源归属模型。
  • 需要标准 OTLP 和多厂商管道并依赖其 processor/connector 时,可评估 OTel Collector。
  • 迁移 Promtail;仅仅因为它已经在运行就继续保留它,不是受支持的长期选择。

决策流程 ​

参考资料与验证范围 ​

验证范围包括已发布的 Alloy/Collector 配置校验、本地合成日志处理、Lua 转换测试、官方插件/源契约以及 Kubernetes manifest 结构。未执行真实的 Kubernetes 元数据查询、节点 agent 部署、网关 mTLS、AWS 投递、HA、生产负载或吞吐量基准测试。

测验 ​

请通过日志收集器测验检验你的理解。

向文档提问

按 Enter 发送,按 Shift+Enter 换行。