Translate

понеділок, 12 жовтня 2026 р.

Fluent Bit: An End to End Observability Pipeline

У динамічній інфраструктурі важливо мати віддалене зберігання логів додатків, адже їх життєвий цикл може бути коротким. Середовище додатку (наприклад, под, в якому додаток працює) банально вже може не існувати допоки інженер знайде час для дослідження проблеми. Тому цього разу поглянемо на роботу Fluent Bit в Kubernetes і як він здатен допомогти у вирішенні цієї задачі.

У 2014 році, розуміючи потребу в легкому процесорі логів для вбудованих систем на базі Linux, команда Fluentd в межах компанії Treasure Data створила Fluent Bit. Наразі він продовжує розвиватись під парасолею команди Fluentd як стабільний CNCF проєкт. Fluent Bit є дещо менш функціональним за материнський проєкт Fluentd, проте все ж пропонується як продукт наступної генерації.

Fluent Bit звісно не єдиний в своєму роді і ту ж роботу може виконати Grafana Alloy, Vector від Datadog, FileBeat від Elastic тощо.


1. KEY CONCEPTS

Для того, щоб рухатись далі, познайомимось із базовими концепціями Fluent Bit.

Кожен фрагмент логу, метрики, трейсу чи профайлу, що отримав Fluent Bit, називається Подією чи Записом (Event or a Record).

Для прикладу розглянемо вміст файлу Syslog:

Jan 18 12:52:16 flb systemd[2222]: Starting GNOME Terminal Server
Jan 18 12:52:16 flb dbus-daemon[2243]: [session uid=1000 pid=2243] Successfully activated service 'org.gnome.Terminal'
Jan 18 12:52:16 flb systemd[2222]: Started GNOME Terminal Server.
Jan 18 12:52:16 flb gsd-media-keys[2640]: # watch_fast: "/org/gnome/terminal/legacy/" (establishing: 0, active: 0)


Тож тут наведено 4 різні події, кожна із яких складається із наступних частин:

  • позначка часу (timestamp)
  • метадані ключ/значення (metadata)
  • корисні дані (payload, message)

[[TIMESTAMP, METADATA], MESSAGE]

Основне завдання Fluent Bit - зробити запис структурованим, за потреби збагативши його корисними даними, і готовим до запису у віддалене сховище. Цей функціонал забезпечується плагінами, що можуть відповідати за збір даних (Input), фільтрацію (Filter), передачу (Output) тощо.

  • Input - це плагіни, що збирають дані задля подальшої обробки. Вони можуть читати дані із файлів, зі сторонньої бази, операційної системи (метрики CPU, GPU) тощо.
  • Processors - механізми, що працюють в межах Input чи Output плагінів та не є глобальними на відміну від Filters. Дозволяють маніпуляції із контентом, метаданими логів чи трейсів, можуть відсіювати необхідні метрики та інше.
  • Parser - логіка, що перетворює неструктуровані дані (syslog дані), в межах Input плагіна, в структуровані (json). Парсери відпрацьовують до Filters та Processors та є єдиними в межах одного Input плагіна. Є можливість описувати власні парсери.
  • Filters також імплементовані як плагіни. Забезпечують зміну зібраних даних (івентів) перед відправкою їх до остаточного пункту призначення. Гарний приклад, із яким ми ознайомимось пізніше, це Kubernetes фільтр.
  • Buffers - після отримання даних Fluent Bit тимчасово зберігає їх в системній пам'яті (на диску чи в RAM). На основі буферів також побудований Backpressure механізм, коли віддалений сторедж для постійного зберігання логів не справляється із вчасним отриманням всіх даних. Загалом використовується для оптимізації віддалених записів.
  • Routing - механізм в межах існуючих плагінів, що дозволяє коректно обробляти та направляти івенти в правильний remote storage/backend. Зазвичай це tag-based routing (tags та matching правила), коли протеганий chunk (блок) івентів йде по всьому пайплайну.
  • Output - кінцеве місце призначення даних. Це можуть бути віддалені сервіси/бази, локальні файлові системи, інтерфейси тощо. Це окремі плагіни, яких існує безліч. Ми в подальшому будемо використовувати Elasticsearch та AWS S3 і однойменні плагіни для цих сервісів.


Опції до цих вищезгаданих плагінів та механізмів трансформуються в конфігураційні файли, основний із яких має наступний вигляд:

[SERVICE]
    flush     1
    log_level info

[INPUT]
    name      tail
    path      /var/log/containers/*.log
    parser    docker

[INPUT]
    name      forward
    listen    0.0.0.0

[OUTPUT]
    name      stdout
    match     *


Це історичний .conf формат. Його нова YAML версія буде виглядати так:

service:
    flush:       1
    log_level:   info

pipeline:
    inputs:
        tail:
            path: "/var/log/containers/*.log"
            parser: docker

        forward:
            listen: 0.0.0.0

    outputs:
        stdout:
            match: "*"


Що цікаво, класичний .conf формат буде оголошено застарілим до кінця 2026 року, проте він досі використовується за замовчуванням в офіційному Kubernetes сетапі Fluent Bit. Більш того, деякі опції можуть бути активованими лише в YAML варіанті конфігураційного файлу.


2. SETUP FLUENT BIT OPERATOR

Як вже було згадано, роботу Fluent Bit ми будемо демонструвати на Kubernetes та за традицією візьмемо AWS EKS. Тож інсталюємо його як і раніше, але для установки останньої на даний момент 1.36 версії варто скористатись тегом та Тераформом версії 1.16.2.

Fluent Bit будемо встановлювати як оператор хоч це і варіант, що підтримується community. Сам проєкт Fluent Bit пропонує інший варіант - офіційний helm чарт, що виливається як деймонсет. Коли ви волієте простоти і вищої стабільності - краще скористатись офіційним способом, проте оператор має і свої переваги:

  • Дозволяє оновлення конфігурації без ребуту подів.
  • Завдяки новим CRD дозволяє оголошувати не лише глобальний конфіг для всіх подів (завдяки ClusterFluentBitConfig, ClusterParser, ClusterFilter, ClusterOutput та іншим CRD), а й у межах неймспейсів (аналогічні FluentBitConfig, Parser, Filter, Output тощо). Це може бути зручно для окремих команд, що бажають мати свою логіку логування і керувати нею самостійно.
  • Підтримує установку Fluentd, якщо це необхідно для додаткової логіки обробки логів

Отже опишемо установку Fluent Operator в Terraform.

$ git clone git@github.com:ipeacocks/terraform-aws-example.git
$ git checkout v1.16.2

$ cd terraform-aws-example/eks-infra/vpc
$ terraform init && terraform apply --auto-approve

$ cd ../eks
$ terraform init && terraform apply --auto-approve


На установці Fluent Bit зупинимось детальніше.

$ cd ../addons/fluentbit
$ cat main.tf


data "kubectl_path_documents" "this" {
  pattern = "${path.module}/templates/manifests/*.yaml.tpl"
  vars = {
    es_host = var.es_host
  }
}

data "terraform_remote_state" "eks" {
  backend = "s3"

  config = {
    bucket = "my-tf-state-2023-06-01"
    key    = "my-eks.tfstate"
    region = "us-east-1"
  }
}

resource "helm_release" "this" {
  name             = "fluent-operator"
  repository       = "https://fluent.github.io/helm-charts"
  chart            = "fluent-operator"
  version          = var.helm_package_version
  create_namespace = true
  namespace        = var.namespace
  values = [
    templatefile("${path.module}/templates/helm/values.yaml.tpl", {
      limits = {
        cpu    = var.resources.limits.cpu
        memory = var.resources.limits.memory
      }
      requests = {
        cpu    = var.resources.requests.cpu
        memory = var.resources.requests.memory
      }
    })
  ]
}

resource "kubectl_manifest" "this" {
  count = length(data.kubectl_path_documents.this.documents)

  yaml_body  = element(data.kubectl_path_documents.this.documents, count.index)
  depends_on = [helm_release.this]
}


Тут було вичитано стейт EKS кластера для того, щоб використати його дані для установки helm-чарту Fluent Bit оператора. Маніфести із директорії templates/manifests будуть застосовані в кінці, адже вони потребують кастомних CRD, котрі сам helm-chart і надає (це ClusterFilter та ClusterOutput). Це відбудеться за допомогою стороннього провайдера kubectl, котрий надає можливість роботи зі вже звичними Kubernetes YAML описами. У довгостроковій перспективі, можливо, було б не зайвим мігрувати на офіційний kubernetes провайдер від Hashicorp, але це вже зовсім інша історія.

Поглянемо на змінні, де описується версія хелм-чарту, адреса elasticsearch-а (саме його ми будемо використовувати як фінальну точку призначення наших логів) та інші дані для доступу, регіон тощо:

$ cat variables.tf

variable "region" {
  description = "AWS region"
  type        = string
  default     = "us-east-1"
}

variable "credentials" {
  default     = ["~/.aws/credentials"]
  description = "Where your access and secret_key are stored, you create the file when you run the aws config"
}

variable "helm_package_version" {
  type        = string
  description = "Helm package version for fluent operator."
  default     = "4.3.0"
}

variable "namespace" {
  description = "Namespace to which service will be deployed."
  type        = string
  default     = "fluent"
}

variable "es_host" {
  type        = string
  description = "Elasticsearch host to send logs to (IP address or hostname)."
  default     = "elasticsearch.elastic.svc"
}

variable "resources" {
  description = "FluentBit daemonset resources."
  type = object({
    limits = optional(object({
      cpu    = optional(string, "1000m")
      memory = optional(string, "1000Mi")
    }), {})
    requests = optional(object({
      cpu    = optional(string, "100m")
      memory = optional(string, "250Mi")
    }), {})
  })
  default = {}
}


Зупинимось на вмісті файлу параметрів для helm-чарту templates/helm/values.yaml.tpl:

$ cat templates/helm/values.yaml.tpl

containerRuntime: containerd
Kubernetes: true

operator:
  resources:
    limits:
      cpu: 200m
      memory: 300Mi
    requests:
      cpu: 200m
      memory: 150Mi

fluentbit:
  resources:
    limits:
      cpu: ${limits.cpu}
      memory: ${limits.memory}
    requests:
      cpu: ${requests.cpu}
      memory: ${requests.memory}

  filter:
    kubernetes:
      enable: false


Тут варто звернути увагу, що опція "Kubernetes: true" дозволила активувати пайплайн для збору логів всіх подів, інакше потрібно було би описувати їх явно. Далі ми описали кількість ресурсів, котрі передали оператору та деймонсетам та відключили фільтри, котрі надає helm оператор, адже вони нас не влаштовують. Про це поговоримо далі.

Проведемо установку всього написаного вище:

$ cd terraform-aws-example/eks-infra/addons/fluentbit
$ terraform init && terraform apply --auto-approve


Та перевіримо результати:

$ aws eks update-kubeconfig --region us-east-1 --name my-eks

$ kubectl get pods -n fluent

NAME                               READY   STATUS    RESTARTS   AGE
fluent-bit-4vbhh                   1/1     Running   0          2m55s
fluent-bit-fld9c                   1/1     Running   0          2m55s
fluent-operator-5f748f7457-z92dd   1/1     Running   0          3m1s


Конфіги вище лише описують адресу Elasticsearch, без його установки. Тому встановимо його окремо, адже тут будуть зберігатись логи:

$ kubectl apply -f - <<EOF
apiVersion: v1
kind: Namespace
metadata:
  name: elastic
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: elasticsearch
  namespace: elastic
spec:
  replicas: 1
  selector:
    matchLabels:
      app: elasticsearch
  template:
    metadata:
      labels:
        app: elasticsearch
    spec:
      containers:
        - name: elasticsearch
          image: docker.elastic.co/elasticsearch/elasticsearch:9.5.3
          ports:
            - containerPort: 9200
            - containerPort: 9300

          env:
            - name: discovery.type
              value: single-node

            - name: xpack.security.enabled
              value: "false"

            - name: ES_JAVA_OPTS
              value: "-Xms1g -Xmx1g"

          resources:
            requests:
              memory: 2Gi
              cpu: 500m
            limits:
              memory: 2Gi
              cpu: "1"

          volumeMounts:
            - name: elasticsearch-data
              mountPath: /usr/share/elasticsearch/data

      volumes:
        - name: elasticsearch-data
          emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
  name: elasticsearch
  namespace: elastic
spec:
  selector:
    app: elasticsearch
  ports:
    - name: http
      port: 9200
      targetPort: 9200
EOF


Ця інсталяція Elasticsearch наведена виключно для експериментів і не є persistent. Скоріш за все, при впровадженні Fluent Bit, інфраструктура вже матиме свої Kibana/Elasticsearch, котрі і потрібно використовувати.

Перевіримо, чи почали писатись індекси:

$ kubectl exec -n elastic \                                        
  deploy/elasticsearch -- \
  curl -s "http://localhost:9200/_cat/indices?v&h=health,status,index,docs.count,store.size"

health status index                      docs.count store.size
yellow open   fluent-operator-2026.09.24          1     15.7kb
yellow open   unknown-2026.09.24                122    141.6kb
yellow open   fluent-bit-2026.09.24             229    205.3kb

$ curl "http://localhost:9200/fluent-operator-2026.09.24/_search?pretty"
{
  "took" : 2,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "fluent-operator-2026.09.24",
        "_id" : "acc14daf-1ce2-c5ce-1294-1193e236680b",
        "_score" : 1.0,
        "_ignored" : [
          "log.keyword"
        ],
        "_source" : {
          "@timestamp" : "2026-09-24T20:05:12.131Z",
          "logtag" : "F",
          "log" : "2026-09-24T20:05:12Z\tINFO\tcontrollers.FluentBitConfig\tFluent Bit main configuration has updated\t{\"logging-control-plane\": \"fluent\", \"fluentbitconfig\": \"fluent-bit-config\", \"secret\": \"fluent-bit-config\", \"config-hash\": \"e90d117dfa5edf7b76252315a306e4d36ee854c94eecd17efeb3c8581ecaf648\"}",
          "kubernetes" : {
            "pod_name" : "fluent-operator-5f748f7457-z92dd",
            "namespace_name" : "fluent",
            "labels" : {
              "app.kubernetes.io/component" : "operator",
              "app.kubernetes.io/name" : "fluent-operator",
              "pod-template-hash" : "5f748f7457",
              "topology.kubernetes.io/region" : "us-east-1",
              "topology.kubernetes.io/zone" : "us-east-1a"
            },
            "pod_ip" : "10.0.12.240",
            "container_name" : "fluent-operator",
            "docker_id" : "848026c4f76ac34957a36ec4d24febe36f6c8dddea1d5f058e8300744d66f526",
            "container_image" : "ghcr.io/fluent/fluent-operator/fluent-operator:3.10.0"
          }
        }
      }
    ]
  }
}


Поле "log", тобто рядок логу, що видав сам контейнер fluent-operator, описаний як звичайна стрінга в плейнтекст форматі, і це недобре. Якщо є можливість, то варто у всіх додатках активувати логування в JSON і тоді логи будуть кориснішими.


3. HOW FLUENT OPERATOR WORKS

Fluent оператор імплементує нові кастомні ресурси для управління сетапами Fluent Bit та Fluentd. Нас більше цікавить перший, тож це наступні CRD:

  • FluentBit - описує створення деймонсету та його конфігів. Для цього використовується кастомний образ ghcr.io/fluent/fluent-operator/fluent-bit
  • ClusterFluentBitConfig/FluentBitConfig - ресурс, що будує фінальний конфіг із input/filter/output плагінів та зберігає його в об'єкті секрету. Відповідно кластерна та namespace-level версії
  • ClusterInput - тип ресурсу, що будує Input секцію конфігу. Є лише кластерна версія
  • ClusterParser/Parser - відповідає за секцію парсерів.
  • ClusterMultilineParser/MultilineParser - за секцію багаторядкових парсерів.
  • ClusterFilter/Filter - кластерна та namespace-level секції фільтрів
  • ClusterOutput/Output - кластерна та namespace-level секції output

Взаємодію цих CRD гарно демонструє наступна схема, що представлена в офіційному репозиторії оператора:



Kind FluentBit описує, який саме ClusterFluentBitConfig вичитується, а вже останній описує селектори, за якими він підключає ClusterInput/Output/Filter об'єкти. Fluent оператор спостерігає за цими об'єктами та формує Secret, що і є репрезентацією конфігураційного файлу, парсерів та ін. Secret монтується у кожний деймонсет та вичитується додатком Fluent Bit перед запуском.

Як було вже згадано, зміни конфігурації (тобто зміни до кастомних ресурсів) не призводять до рестарту подів деймонсету, завдяки додатковому вотчеру всередині кастомного імеджу:



Саме з цієї причини версія Fluent Bit, що використовує оператор, може бути дещо старішою за версію офіційного helm-чарту.

Перевіримо на практиці, як це виглядає і почнемо із FluentBit:

$ kubectl get FluentBit -n fluent
NAME AGE
fluent-bit 4h31m

$ kubectl get FluentBit fluent-bit -n fluent -o yaml

apiVersion: fluentbit.fluent.io/v1alpha2
kind: FluentBit
metadata:
annotations:
meta.helm.sh/release-name: fluent-operator
meta.helm.sh/release-namespace: fluent

  creationTimestamp: "2026-09-24T14:16:34Z"
  finalizers:
  - fluentbit.fluent.io
  generation: 2
  labels:
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/name: fluent-bit
  name: fluent-bit
  namespace: fluent
  resourceVersion: "2151"
  uid: 181dbc78-9616-4465-985f-5db3592f938e
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: node-role.kubernetes.io/edge
            operator: DoesNotExist
  fluentBitConfigName: fluent-bit-config
  image: ghcr.io/fluent/fluent-operator/fluent-bit:5.1.0
  livenessProbe:
    failureThreshold: 8
    httpGet:
      path: /
      port: 2020
    initialDelaySeconds: 10
    periodSeconds: 10
    successThreshold: 1
    timeoutSeconds: 15
  namespaceFluentBitCfgSelector: {}
  positionDB:
    hostPath:
      path: /var/lib/fluent-bit/
  rbacRules:
  - apiGroups:
    - ""
    resources:
    - events
    verbs:
    - list
  resources:
    limits:
      cpu: "1"
      memory: 1000Mi
    requests:
      cpu: 100m
      memory: 250Mi
  service: {}
  tolerations:
  - operator: Exists
status: {}


ClusterFluentBitConfig описує, які саме об'єкти filter/input/тощо, через selector логіку, він буде збирати в єдиний конфігураційний файл (classic формату) та секцію [SERVICE], що стосується загальної конфігурації демону:

$ kubectl get ClusterFluentBitConfig fluent-bit-config -n fluent -o yaml

apiVersion: fluentbit.fluent.io/v1alpha2
kind: ClusterFluentBitConfig
metadata:
  ...
  labels:
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/name: fluent-bit
  name: fluent-bit-config
  ...
spec:
  configFileFormat: classic
  filterSelector:
    matchLabels:
      fluentbit.fluent.io/enabled: "true"
  inputSelector:
    matchLabels:
      fluentbit.fluent.io/enabled: "true"
  multilineParserSelector:
    matchLabels:
      fluentbit.fluent.io/enabled: "true"
  outputSelector:
    matchLabels:
      fluentbit.fluent.io/enabled: "true"
  parserSelector:
    matchLabels:
      fluentbit.fluent.io/enabled: "true"
  service:
    httpServer: true
    parsersFile: /fluent-bit/etc/parsers.conf
    LogLevel: info
    LogFormat: json


Хелм чарт також створив 2 ClusterInput-а (завдяки опції "Kubernetes: true"):

$ kubectl get ClusterInput -n fluent
NAME         AGE
containerd   4h24m
tail         4h24m


Перший containerd, котрий вичитує systemd логи демонів containerd та kubelet і вішає тег service. А другий - tail, котрий читає всі логи /var/log/containers/*.log і ставить мітку kube. Далі буде більш якісно пояснено логіку роботи конфігураційного файлу, цього разу просто поглянемо на його загальний вигляд:

$ kubectl get ClusterInput -n fluent -o yaml


apiVersion: v1
items:
- apiVersion: fluentbit.fluent.io/v1alpha2
  kind: ClusterInput
  metadata:
    ...
    labels:
      ...
      fluentbit.fluent.io/enabled: "true"
    name: containerd
    ...
  spec:
    systemd:
      db: /fluent-bit/tail/systemd.db
      dbSync: Normal
      path: /var/log/journal
      storageType: memory
      stripUnderscores: "off"
      systemdFilter:
      - _SYSTEMD_UNIT=containerd.service
      - _SYSTEMD_UNIT=kubelet.service
      tag: service.*
- apiVersion: fluentbit.fluent.io/v1alpha2
  kind: ClusterInput
  metadata:
    ...
    labels:
      ...
      fluentbit.fluent.io/enabled: "true"
    name: tail
    ...
  spec:
    tail:
      db: /fluent-bit/tail/pos.db
      dbSync: Normal
      memBufLimit: 100MB
      parser: cri
      path: /var/log/containers/*.log
      readFromHead: false
      refreshIntervalSeconds: 10
      skipEmptyLines: true
      skipLongLines: true
      storageType: memory
      tag: kube.*
kind: List
...

Об'єкти ClusterFilter відповідають за 3 секції фільтрів (їх може бути декілька в загальному конфігураційному файлі):

$ kubectl get ClusterFilter -n fluent   
    
NAME         AGE
containerd   4h26m
kubernetes   4h26m
systemd      4h26m

$ kubectl get ClusterFilter -n fluent -o yaml

apiVersion: v1
items:
- apiVersion: fluentbit.fluent.io/v1alpha2
  kind: ClusterFilter
  metadata:
    ...
    labels:
      ...
      fluentbit.fluent.io/enabled: "true"
    name: containerd
    ...
  spec:
    filters:
    - lua:
        call: containerd
        script:
          key: containerd.lua
          name: fluent-bit-containerd-config
        timeAsTable: true
    match: kube.*
- apiVersion: fluentbit.fluent.io/v1alpha2
  kind: ClusterFilter
  metadata:
    generation: 1
    labels:
      ...
      fluentbit.fluent.io/enabled: "true"
    name: kubernetes
    ...
  spec:
    filters:
    - kubernetes:
        annotations: false
        k8sLoggingExclude: true
        keepLog: false
        mergeLog: true
    - nest:
        addPrefix: kubernetes_
        nestedUnder: kubernetes
        operation: lift
    - modify:
        rules:
        - remove: stream
        - remove: time
        - remove: kubernetes_pod_id
        - remove: kubernetes_host
        - remove: kubernetes_container_hash
    - nest:
        nestUnder: kubernetes
        operation: nest
        removePrefix: kubernetes_
        wildcard:
        - kubernetes_*
    match: kube.*
- apiVersion: fluentbit.fluent.io/v1alpha2
  kind: ClusterFilter
  metadata:
    ...
    labels:
      ...
      fluentbit.fluent.io/enabled: "true"
    name: systemd
    ...
  spec:
    filters:
    - lua:
        call: add_time
        script:
          key: systemd.lua
          name: fluent-bit-lua
        timeAsTable: true
    match: service.*
kind: List
...


ClusterFilter із іменем kubernetes ми описали як окремий темплейт в Terraform, тож він його і вилив. Тепер настала черга ClusterOutput:

$ kubectl get ClusterOutput -n fluent
NAME   AGE
es     4h29m

$ kubectl get ClusterOutput -n fluent -o yaml

apiVersion: v1
items:
- apiVersion: fluentbit.fluent.io/v1alpha2
  kind: ClusterOutput
  metadata:
    ...
    labels:
      ...
      fluentbit.fluent.io/enabled: "true"
    name: es
    ...
  spec:
    es:
      bufferSize: 1MB
      compress: gzip
      generateID: true
      host: elasticsearch.elastic.svc
      logstashFormat: true
      logstashPrefix: unknown
      logstashPrefixKey: kubernetes['labels']['app.kubernetes.io/name']
      port: 9200
      suppressTypeName: "On"
      timeKey: '@timestamp'
    matchRegex: (?:kube|service)\.(.*)
kind: List
...


Наразі ми використовуємо єдиний output в ElasticSearch, а індекси пишемо в logstash-форматі.

Перевіряємо фінальний конфігураційний файл, котрий зібрав для нас Fluent оператор:

$ kubectl get secrets -n fluent
NAME                                    TYPE                 DATA   AGE
fluent-bit-config                       Opaque               5      5h3m


Та перетворимо base64 в формат зручний для читання:

$ for key in $(kubectl get secret fluent-bit-config -n fluent -o json | jq -r '.data | keys[]'); do
  echo "### $key ###"
  kubectl get secret fluent-bit-config -n fluent -o json | jq -r ".data[\"$key\"]" | base64 --decode
  echo -e "\n"
done


### containerd.lua ###
function containerd( tag, timestamp, record)
       if(record["logtag"]~=nil)
       then
       timeStr = os.date("!*t",  timestamp["sec"])
        t = string.format("%4d-%02d-%02dT%02d:%02d:%02d.%sZ",
        timeStr["year"], timeStr["month"], timeStr["day"],
        timeStr["hour"], timeStr["min"], timeStr["sec"],
        timestamp["nsec"]);
        record["time"] = t;
        record["log"] = record["message"];
        record["message"] =  nil;
        return 1, timestamp, record
        else
        return 0,timestamp,record
       end
end

### fluent-bit.conf ###
[Service]
    Http_Server    true
    Parsers_File    /fluent-bit/etc/parsers.conf
[Input]
    Name    systemd
    Path    /var/log/journal
    DB    /fluent-bit/tail/systemd.db
    DB.Sync    Normal
    Tag    service.*
    Strip_Underscores    off
    storage.type    memory
    Systemd_Filter    _SYSTEMD_UNIT=containerd.service
    Systemd_Filter    _SYSTEMD_UNIT=kubelet.service
[Input]
    Name    tail
    Path    /var/log/containers/*.log
    Refresh_Interval    10
    Skip_Long_Lines    true
    DB    /fluent-bit/tail/pos.db
    DB.Sync    Normal
    Mem_Buf_Limit    100MB
    Parser    cri
    Tag    kube.*
    storage.type    memory
    Read_from_Head    false
    Skip_Empty_Lines    true
[Filter]
    Name    lua
    Match    kube.*
    script    /fluent-bit/config/containerd.lua
    call    containerd
    time_as_table    true
[Filter]
    Name    kubernetes
    Match    kube.*
    Merge_Log    true
    Keep_Log    false
    K8S-Logging.Exclude    true
    Annotations    false
[Filter]
    Name    nest
    Match    kube.*
    Operation    lift
    Nested_under    kubernetes
    Add_prefix    kubernetes_
[Filter]
    Name    modify
    Match    kube.*
    Remove    stream
    Remove    time
    Remove    kubernetes_pod_id
    Remove    kubernetes_host
    Remove    kubernetes_container_hash
[Filter]
    Name    nest
    Match    kube.*
    Operation    nest
    Wildcard    kubernetes_*
    Nest_under    kubernetes
    Remove_prefix    kubernetes_
[Filter]
    Name    lua
    Match    service.*
    script    /fluent-bit/config/systemd.lua
    call    add_time
    time_as_table    true
[Output]
    Name    es
    Match_Regex    (?:kube|service)\.(.*)
    Host    elasticsearch.elastic.svc
    Port    9200
    Compress    gzip
    Buffer_Size    1MB
    Logstash_Prefix    unknown
    Time_Key    @timestamp
    Logstash_Prefix_Key    kubernetes['labels']['app.kubernetes.io/name']
    Suppress_Type_Name    On
    Logstash_Format    true
    Generate_ID    true

### parsers.conf ###

### parsers_multiline.conf ###

### systemd.lua ###
function add_time(tag, timestamp, record)
  new_record = {}

  timeStr = os.date("!*t", timestamp["sec"])
  t = string.format("%4d-%02d-%02dT%02d:%02d:%02d.%sZ",
  timeStr["year"], timeStr["month"], timeStr["day"],
  timeStr["hour"], timeStr["min"], timeStr["sec"],
  timestamp["nsec"])

  kubernetes = {}
  kubernetes["pod_name"] = record["_HOSTNAME"]
  kubernetes["container_name"] = record["SYSLOG_IDENTIFIER"]
  kubernetes["namespace_name"] = "kube-system"

  new_record["time"] = t
  new_record["log"] = record["MESSAGE"]
  new_record["kubernetes"] = kubernetes

  return 1, timestamp, new_record
end


Фінальний DaemonSet, власником якого і є FluentBit об'єкт/оператор:

$ kubectl get ds fluent-bit -n fluent -o yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
  annotations:
    meta.helm.sh/release-name: fluent-operator
    meta.helm.sh/release-namespace: fluent
  creationTimestamp: "2026-09-24T14:16:39Z"
  generation: 1
  labels:
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/name: fluent-bit
  name: fluent-bit
  namespace: fluent
  ownerReferences:
  - apiVersion: fluentbit.fluent.io/v1alpha2
    blockOwnerDeletion: true
    controller: true
    kind: FluentBit
    name: fluent-bit
    uid: 181dbc78-9616-4465-985f-5db3592f938e
  resourceVersion: "2209"
  uid: 03183a5e-ae93-4cc0-a12e-6a6956b86b1e
spec:
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app.kubernetes.io/managed-by: Helm
      app.kubernetes.io/name: fluent-bit
  template:
    metadata:
      labels:
        app.kubernetes.io/managed-by: Helm
        app.kubernetes.io/name: fluent-bit
      name: fluent-bit
      namespace: fluent
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: node-role.kubernetes.io/edge
                operator: DoesNotExist
      containers:
      - env:
        - name: NODE_NAME
          valueFrom:
            fieldRef:
              apiVersion: v1
              fieldPath: spec.nodeName
        - name: HOST_IP
          valueFrom:
            fieldRef:
              apiVersion: v1
              fieldPath: status.hostIP
        image: ghcr.io/fluent/fluent-operator/fluent-bit:5.1.0
        imagePullPolicy: IfNotPresent
        livenessProbe:
          failureThreshold: 8
          httpGet:
            path: /
            port: 2020
            scheme: HTTP
          initialDelaySeconds: 10
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 15
        name: fluent-bit
        ports:
        - containerPort: 2020
          name: metrics
          protocol: TCP
        resources:
          limits:
            cpu: "1"
            memory: 1000Mi
          requests:
            cpu: 100m
            memory: 250Mi
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
        volumeMounts:
        - mountPath: /fluent-bit/config
          name: config
          readOnly: true
        - mountPath: /var/log/containers
          mountPropagation: None
          name: varlibcontainers
          readOnly: true
        - mountPath: /var/log/
          mountPropagation: None
          name: varlogs
          readOnly: true
        - mountPath: /var/log/journal
          mountPropagation: None
          name: systemd
          readOnly: true
        - mountPath: /fluent-bit/tail
          name: positions
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      serviceAccount: fluent-bit
      serviceAccountName: fluent-bit
      terminationGracePeriodSeconds: 30
      tolerations:
      - operator: Exists
      volumes:
      - name: config
        secret:
          defaultMode: 420
          secretName: fluent-bit-config
      - hostPath:
          path: /var/log/containers
          type: ""
        name: varlibcontainers
      - hostPath:
          path: /var/log
          type: ""
        name: varlogs
      - hostPath:
          path: /var/log/journal
          type: ""
        name: systemd
      - hostPath:
          path: /var/lib/fluent-bit/
          type: ""
        name: positions
  updateStrategy:
    rollingUpdate:
      maxSurge: 0
      maxUnavailable: 1
    type: RollingUpdate


Тут можна пересвідчитись, що fluent-bit-config справді монтується в кожен под за адресою /fluent-bit/config, також прокидаються всередину всі логи /var/log/containers, journal (systemd), файли позицій тощо.


4. HOW FLUENT BIT CONFIG WORKS

Отже зупинимось на фінальному конфігураційному файлі fluent-bit.conf. Дублювати його не буду, адже завжди за його повним лістингом можна звернутись "нагору".



Отже перша секція [Service]:

[Service]
    Http_Server    true
    Parsers_File    /fluent-bit/etc/parsers.conf


Http_Server активує prometheus-like метрики, Parsers_File вказує адресу розміщення конфігу із парсерами.

Секцій Input може бути багато, у нас лише дві. Перша стосується Systemd:

[Input]
    Name    systemd
    Path    /var/log/journal
    DB    /fluent-bit/tail/systemd.db
    DB.Sync    Normal
    Tag    service.*
    Strip_Underscores    off
    storage.type    memory
    Systemd_Filter    _SYSTEMD_UNIT=containerd.service
    Systemd_Filter    _SYSTEMD_UNIT=kubelet.service


SQLite база /fluent-bit/tail/systemd.db потрібна для збереження позиції логів, що вже були записані (тобто зміщення/offset). Це необхідно для зменшення ймовірності втрати логів у разі перевантаження демона чи його аварійного падіння.

Цей input тегається міткою service.*, котру використовує Fluent Bit для роутингу логів. Зірочка в кінці має спеціальне значення для плагіну: до тегів буде додано значення _SYSTEMD_UNIT.

Systemd_Filter
вказує, що нас цікавлять лише логи containerd та kubelet сервісів. А storage.type активує буферизацію в оперативній пам'яті.

Основний tail input:

[Input]
    Name    tail
    Path    /var/log/containers/*.log
    Refresh_Interval    10
    Skip_Long_Lines    true
    DB    /fluent-bit/tail/pos.db
    DB.Sync    Normal
    Mem_Buf_Limit    100MB
    Parser    cri
    Tag    kube.*
    storage.type    memory
    Read_from_Head    false
    Skip_Empty_Lines    true


Вичитуємо всі логи контейнерів /var/log/containers/*.log та із інтервалом в 10 секунд ведемо пошук нових доступних файлів. Для парсингу цих логів буде використаний парсер cri. Як і у попередньому разі виставляється обов'язковий тег, по якому і буде відбуватись подальший роутинг повідомлень. Зірочка в кінці вказує на те, що вона буде замінена на повну адресу файла (наприклад /var/log/containers/apache-logs-annotated_default_apache-aeeccc7a9f00f6e4e066aeff0434cf80621215071f1b20a51e8340aa7c35eac6.log), а слеші будуть замінені на крапки (тобто результат буде наступним -  kube.var.log.containers.apache-logs-annotated_default_apache-aeeccc7a9f00f6e4e066aeff0434cf80621215071f1b20a51e8340aa7c35eac6.log). Це робиться задля того, щоб додати більше інформації до таких логів, адже наприклад файли логів в Kubernetes створюються із додаванням назви пода, неймспейсу та контейнера. Тобто це вже корисні дані, котрі не хотілось би втрачати.

Cri парсер вже описаний в дефолтних конфігураційних файлах і він допомагає розкласти рядок логу на поля.

Наступний фільтр - lua (він також описаний в секреті fluent-bit-config). Цей фільтр дозволяє робити постобробку логів за допомогою Lua скриптів (додавати нові поля, прибирати їх, додавати вкладеність тощо):

[Filter]
    Name    lua
    Match    kube.*
    script    /fluent-bit/config/containerd.lua
    call    containerd
    time_as_table    true


У нашому випадку він (вміст containerd.lua наведений вище, як результат вичитки k8s секрету) стандартизує мітку часу та перейменовує (насправді копіє значення в новий ключ, а старий видаляє) поле log в message. Це за умови, що в івенті присутній ключ "logtag".

Наступний фільтр - kubernetes. Завдяки "Match kube.*" він візьме логи від tail input плагіна та збагатить їх додатковими ключами, такими як лейбли подів, неймспейси тощо.

[Filter]
    Name    kubernetes
    Match    kube.*
    Merge_Log    true
    Keep_Log    false
    K8S-Logging.Exclude    true
    Annotations    false


Отримавши значення routing тегу від tail, наприклад kube.var.log.containers.apache-logs-annotated_default_apache-aeeccc7a9f00f6e4e066aeff0434cf80621215071f1b20a51e8340aa7c35eac6.log, його буде вичитано і перетворено в  apache-logs-annotated_default_apache-aeeccc7a9f00f6e4e066aeff0434cf80621215071f1b20a51e8340aa7c35eac6.log, завдяки дефолтній опції kube_tag_prefix, що має значення "kube.var.log.containers". Після цього, використовуючи регулярний вираз "(?<pod_name>[a-z0-9](?:[-a-z0-9]*[a-z0-9])?(?:\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*)_(?<namespace_name>[^_]+)_(?<container_name>.+)-(?<docker_id>[a-z0-9]{64})\.log$" (цей regex прописаний в коді, проте його можна перевизначити в кастомному парсері) https://rubular.com/r/HZz3tYAahj6JCd, назва файлу буде перетворена в корисні ключі до event-у Fluent Bit: а саме назву пода, неймспейсу, назву контейнера та id контейнера.

Якщо в основному пейлоаді, тобто ключі log, знаходиться JSON (додаток одразу пише логи в JSON), то опція "Merge_Log true" дозволить додати ці поля в структуру самого івенту/логу.

"Keep_Log false" видалить оригінальне поле log, якщо опція Merge_Log активована і log справді був JSON-ом.

"K8S-Logging.Exclude true"
дозволяє вимикати логування для окремих подів на рівні їх анотацій. Для деяких не-prod середовищ це може бути корисним. https://docs.fluentbit.io/manual/data-pipeline/filters/kubernetes#kubernetes-pod-annotations

"Annotations false" заборонить додавати значення анотацій подів як додаткові поля. Це зменшить обсяг даних для відправки та збереження.

Три наступні фільтри розглянемо як одне ціле, адже вони покликані вирішити єдину задачу - видалити непотрібні поля:

[Filter]
    Name    nest
    Match    kube.*
    Operation    lift
    Nested_under    kubernetes
    Add_prefix    kubernetes_
[Filter]
    Name    modify
    Match    kube.*
    Remove    stream
    Remove    time
    Remove    kubernetes_pod_id
    Remove    kubernetes_host
    Remove    kubernetes_container_hash
[Filter]
    Name    nest
    Match    kube.*
    Operation    nest
    Wildcard    kubernetes_*
    Nest_under    kubernetes
    Remove_prefix    kubernetes_


Все виглядає доволі складно, адже фільтр modify не вміє видаляти nested поля. Тому фільтр nest спочатку "піднімає" нагору все, що знаходиться у вкладеній структурі kubernetes (опція Nested_under):
...
"kubernetes" : {
  "pod_name" : "fluent-operator-5f748f7457-z92dd",
  "namespace_name" : "fluent",
  "labels" : {
    "app.kubernetes.io/component" : "operator",
    "app.kubernetes.io/name" : "fluent-operator",
    "pod-template-hash" : "5f748f7457",
    "topology.kubernetes.io/region" : "us-east-1",
    "topology.kubernetes.io/zone" : "us-east-1a"
  },
  "pod_ip" : "10.0.12.240",
  "pod_id" : "848026c4f76ac34957a36ec4d24febe36f6c8dddea1d5f058e8300744d66f526",
  "host" : "host.example.com",
  "container_hash": "a36ec4d24febe36f6c8dddea1d5f058",
  "container_name" : "fluent-operator",
  "docker_id" : "848026c4f76ac34957a36ec4d24febe36f6c8dddea1d5f058e8300744d66f526",
  "container_image" : "ghcr.io/fluent/fluent-operator/fluent-operator:3.10.0"
}
...


І результат виходить наступний (додається kubernetes_ до ключів, адже це значення опції Add_prefix):
...
"kubernetes_pod_name" : "fluent-operator-5f748f7457-z92dd",
"kubernetes_namespace_name" : "fluent",
"kubernetes_labels" : {
    "app.kubernetes.io/component" : "operator",
    "app.kubernetes.io/name" : "fluent-operator",
    "pod-template-hash" : "5f748f7457",
    "topology.kubernetes.io/region" : "us-east-1",
    "topology.kubernetes.io/zone" : "us-east-1a"
  },
"kubernetes_pod_ip" : "10.0.12.240",
"kubernetes_pod_id" : "848026c4f76ac34957a36ec4d24febe36f6c8dddea1d5f058e8300744d66f526",
"kubernetes_host" : "host.example.com",
"kubernetes_container_hash": "a36ec4d24febe36f6c8dddea1d5f058",
"kubernetes_container_name" : "fluent-operator",
"kubernetes_docker_id" :  "848026c4f76ac34957a36ec4d24febe36f6c8dddea1d5f058e8300744d66f526",
"kubernetes_container_image" : "ghcr.io/fluent/fluent-operator/fluent-operator:3.10.0"
...


Потім modify видаляє непотрібні поля (див. оригінальний конфіг, які саме поля), а після цього nest повертає оригінальну мапу без зайвих полів:

...
"kubernetes" : {
  "pod_name" : "fluent-operator-5f748f7457-z92dd",
  "namespace_name" : "fluent",
  "labels" : {
    "app.kubernetes.io/component" : "operator",
    "app.kubernetes.io/name" : "fluent-operator",
    "pod-template-hash" : "5f748f7457",
    "topology.kubernetes.io/region" : "us-east-1",
    "topology.kubernetes.io/zone" : "us-east-1a"
  },
  "pod_ip" : "10.0.12.240",
  "container_name" : "fluent-operator",
  "docker_id" : "848026c4f76ac34957a36ec4d24febe36f6c8dddea1d5f058e8300744d66f526",
  "container_image" : "ghcr.io/fluent/fluent-operator/fluent-operator:3.10.0"
}
...


Далі йде знову lua фільтр, але вже для systemd логів:

[Filter]
    Name    lua
    Match    service.*
    script    /fluent-bit/config/systemd.lua
    call    add_time
    time_as_table    true


Він знову ж стандартизує мітку часу та збагачує логи додатковими полями, що стосуються Кубернетіса.

І остання секція - Output, де ми відправляємо результат у віддалену базу Elasticsearch:

[Output]
    Name    es
    Match_Regex    (?:kube|service)\.(.*)
    Host    elasticsearch.elastic.svc
    Port    9200
    Compress    gzip
    Buffer_Size    1MB
    Logstash_Prefix    unknown
    Time_Key    @timestamp
    Logstash_Prefix_Key    kubernetes['labels']['app.kubernetes.io/name']
    Suppress_Type_Name    On
    Logstash_Format    true
    Generate_ID    true


Завдяки Match_Regex ми зібрали всі логи під тегами kube та service та відправили їх до elasticsearch.elastic.svc. Назва індексу буде визначатись динамічно за лейблом kubernetes['labels']['app.kubernetes.io/name'], проте якщо його немає - то буде проставлятись unknown. Всі індекси будуть закінчуватись міткою часу, тож загальна практика роботи із ними - це періодично видаляти старі. Десь так будуть виглядати індекси:

$ kubectl exec -n elastic \                                        
  deploy/elasticsearch -- \
  curl -s "http://localhost:9200/_cat/indices?v&h=health,status,index,docs.count,store.size"

health status index                      docs.count store.size
yellow open   fluent-operator-2026.09.24          1     15.7kb
yellow open   unknown-2026.09.24                122    141.6kb
yellow open   fluent-bit-2026.09.24             229    205.3kb


Ця модель запису може бути незручною, адже генерує велику кількість індексів, що часто є проблемою, якщо є бажання зберігати індекси впродовж довгого часу. Тому є варіант, хоч і дещо не елегантний, зберігати логи кожного додатку в окремому індексі, проте без мітки часу. А потім вже накрутити окрему полісі Elasticsearch, котра буде підчищати цей індекс (або datastream).

...
logstashDateFormat: eks
logstashFormat: true
logstashPrefix: unknown
logstashPrefixKey: kubernetes['labels']['app.kubernetes.io/name']
...


Тобто logstashDateFormat буде фіксований постійно. Або ж ставити лише місяць чи рік цією опцією.


5. FLUENT BIT ADVANCED TIPS

Скажімо, з'явилась необхідність відправляти деякі логи в інше середовище, не порушуючи роботу загальної схеми. Наприклад, ми хочемо, щоб логи Fluent Bit подів відправлялись до AWS S3. Для цього необхідні інший Fluent Bit output plugin та rewrite_tag filter.

Щоб авторизуватись в AWS S3, скористаємось додатковою IAM роллю та прив'яжемо її до Fluent Bit із pod identity. Для цього підправимо тераформ код:

$ cat main.tf
...
module "s3_bucket" {
  source  = "terraform-aws-modules/s3-bucket/aws"
  version = "5.16.1"

  bucket = "fluentbit-logs-2026"

  versioning = {
    enabled = false
  }

  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

data "aws_iam_policy_document" "this" {

  statement {
    sid    = "S3Write"
    effect = "Allow"

    actions = [
      "s3:PutObject",
      "s3:GetObject",
      "s3:DeleteObject"
    ]

    resources = [
      "${module.s3_bucket.s3_bucket_arn}/*"
    ]
  }
}

module "custom_pod_identity" {

  source  = "terraform-aws-modules/eks-pod-identity/aws"
  version = "2.9.0"

  name            = "eks-fluentbit-${data.terraform_remote_state.eks.outputs.cluster_name}-${var.region}"
  use_name_prefix = false

  attach_custom_policy = true
  source_policy_documents = [
    data.aws_iam_policy_document.this.json
  ]

  associations = {
    one = {
      cluster_name    = data.terraform_remote_state.eks.outputs.cluster_name
      namespace       = var.namespace
      service_account = "fluent-bit"
    }
  }
}


Тут ми створили новий бакет fluentbit-logs-2026 (не забуваймо, що ім'я бакета унікальне), створили роль на запис в нього та асоціювали цю роль із сервіс акаунтом та іменем неймспейсу. Увесь новий код буде доступний за новим тегом.

Окрім цього було створено новий фільтр із rewriteTag плагіном:

$ cat templates/manifests/cluster-filter.yaml.tpl
...
  - rewriteTag:
      rules:
      - $kubernetes['labels']['app.kubernetes.io/name'] ^fluent-bit$ fluentbit.$TAG true
      emitterName: fluentbit
      emitterMemBufLimit: 64M


Цей фільтр дозволяє зробити новий тег на деякі рекорди, що задовольняють певні умови. Про них трошки пізніше.

Правила (секція rules) мають наступний формат:

$KEY  REGEX  NEW_TAG  KEEP

$KEY - ключ, значення якого перевіряють
REGEX - саме значення. Якщо бути точнішим, регулярка, що задовольняє всі потрібні значення
NEW_TAG - відповідно новий routing тег, під яким будуть опубліковані записи, що задовольняють попередній вираз
KEEP - зберігати чи ні оригінальне значення із попереднім тегом. Наприклад, у нас раніше на цих записах стояв тег kube.*

Тож у нас буде перевірка значення лейбла поду $kubernetes['labels']['app.kubernetes.io/name'] і якщо воно має значення fluent-bit (потраплятиме під регулярний вираз ^fluent-bit$), то воно буде затегано під новим тегом fluentbit.$TAG. Проте старий тег kube.* ми не будемо видаляти, тож ті ж значення будуть присутні і в ElasticSearch, і в AWS S3.

І, нарешті, S3 output:

$ cat templates/manifests/cluster-output-s3.yaml.tpl
---
apiVersion: fluentbit.fluent.io/v1alpha2
kind: ClusterOutput
metadata:
  name: s3-fluent-bit
  labels:
    app.kubernetes.io/managed-by: Terraform
    fluentbit.fluent.io/component: logging
    fluentbit.fluent.io/enabled: 'true'
spec:
  match: fluentbit.*
  s3:
    Region: us-east-1
    Bucket: fluentbit-logs-2026
    S3KeyFormat: /%Y-%m-%d-%H_$UUID.log
    ContentType: text/plain
    TotalFileSize: 45M
    UploadTimeout: 1m
    UsePutObject: true
    StoreDir: /fluentbit/buffer/s3
    StoreDirLimitSize: 2G
    JsonDateKey: '@timestamp'
    JsonDateFormat: iso8601
    PreserveDataOrdering: true
    RetryLimit: 5


Застосуємо всі зміни та зачекаємо декілька хвилин:

$ cd terraform-aws-example
$ git pull && git checkout v1.16.5 && git pull
$ cd eks-infra/addons/fluentbit
$ terraform init && terraform apply --auto-approve


Переглянемо новостворені записи:

$ aws s3 ls s3://fluentbit-logs-2026                             
2026-10-01 22:21:34       5778 2026-10-01-19_0HsQr56b.log
2026-10-01 22:18:14      52278 2026-10-01-19_HHblKQhA.log
2026-10-01 22:20:26       6518 2026-10-01-19_M2sTlPOk.log
2026-10-01 22:18:12      51561 2026-10-01-19_S2syAYdp.log
2026-10-01 22:19:16      25037 2026-10-01-19_Wr5U9Hfb.log
2026-10-01 22:21:36       5786 2026-10-01-19_Xq6muBEW.log
2026-10-01 22:19:16      21086 2026-10-01-19_a6r9lLvT.log
2026-10-01 22:20:24       5778 2026-10-01-19_hFAEdtHl.log
2026-10-01 22:19:14      23433 2026-10-01-19_kC6X1egH.log

$ aws s3 cp s3://fluentbit-logs-2026/2026-10-01-19_HHblKQhA.log -

{"@timestamp":"2026-10-01T19:17:16.770212Z","logtag":"F","log":"[2026/10/01 19:17:16.770] [ info] [reload] stop everything of the old context","kubernetes":{"pod_name":"fluent-bit-gkzvb","namespace_name":"fluent","labels":{"app.kubernetes.io/managed-by":"Helm","app.kubernetes.io/name":"fluent-bit","controller-revision-hash":"7f6dbb8d7c","eks.amazonaws.com/pod-identity":"enabled","pod-template-generation":"1","topology.kubernetes.io/region":"us-east-1","topology.kubernetes.io/zone":"us-east-1b"},"pod_ip":"10.0.21.21","container_name":"fluent-bit","docker_id":"ee09f657553234f4478b216093324ca8eaef23dac76b0cd9fa785c3b69a1918d","container_image":"ghcr.io/fluent/fluent-operator/fluent-bit:5.1.0"}}
...


Все виглядає правильно.

Посилання:
https://github.com/ipeacocks/terraform-aws-example
https://docs.fluentbit.io/manual
https://github.com/fluent/fluent-operator

Немає коментарів:

Дописати коментар