Fluent Bit est un collecteur de logs ultra-léger, écrit en C, projet CNCF Graduated. Il consomme quelques mégaoctets de mémoire et s'est imposé comme l'agent de collecte de logs de référence sur chaque nœud Kubernetes, avant d'envoyer vers un agrégateur ou un backend comme Loki ou Elasticsearch.
Ce guide s'adresse aux profils intermédiaires et avancés qui exploitent des logs à grande échelle. Vous allez installer Fluent Bit, comprendre son pipeline (input, parser, filter, output), le configurer pour Kubernetes, écrire des parsers et router les logs vers Loki.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Comprendre le pipeline Fluent Bit : input, parser, filter, output.
- Installer Fluent Bit proprement (dépôt signé, image épinglée).
- Collecter les logs de conteneurs et les enrichir avec les métadonnées Kubernetes.
- Écrire des parsers pour structurer les logs bruts.
- Router les logs vers Loki, Elasticsearch ou un agrégateur.
Pourquoi Fluent Bit ?
Section intitulée « Pourquoi Fluent Bit ? »Fluent Bit est conçu pour les environnements contraints en ressources et les architectures cloud-native à grande échelle. Écrit en C, sans machine virtuelle ni interpréteur à charger au démarrage, il tourne sur chaque nœud d'un cluster sans amputer le budget mémoire des applications qui y sont déjà déployées. Le tableau ci-dessous rassemble les chiffres qui décident de son adoption : une empreinte de l'ordre du mégaoctet et une licence Apache 2.0 qui n'impose aucune contrepartie commerciale.
| Caractéristique | Description |
|---|---|
| Licence | Apache 2.0 |
| Maturité | CNCF Graduated |
| Langage | C |
| Mémoire | ~1 MB |
| Throughput | 100k+ events/sec |
Fluent Bit vs Fluentd
Section intitulée « Fluent Bit vs Fluentd »Les deux outils viennent du même projet mais ne jouent pas le même rôle. Fluent Bit se place en collecteur d'edge sur chaque nœud, là où la ressource est comptée ; Fluentd sert d'agrégateur central qui reçoit les flux, applique des transformations lourdes et s'appuie sur un écosystème de plugins bien plus vaste, au prix d'un runtime Ruby à héberger. Les deux se chaînent parfaitement : Fluent Bit collecte et préfiltre, Fluentd agrège et route.
| Critère | Fluent Bit | Fluentd |
|---|---|---|
| Mémoire | ~1 MB | ~40 MB |
| Langage | C | Ruby |
| Plugins | 100+ intégrés | 500+ externes |
| Rôle | Collecte (edge) | Agrégation (central) |
| Performances | Plus rapide | Plus flexible |
Architecture
Section intitulée « Architecture »Un événement traverse toujours les mêmes étages avant de quitter Fluent Bit.
L'input lit la source (fichier, journal systemd, socket réseau), le
parser découpe la ligne brute en champs exploitables, le filter
enrichit ou écarte des enregistrements, l'output écrit vers la
destination finale. Chaque événement porte un tag attribué par son
input, et c'est sur ce tag que les directives Match des filtres et des
sorties décident quoi traiter.
┌─────────────────────────────────────────────────────────────────┐│ Fluent Bit Pipeline ││ ││ ┌────────┐ ┌────────┐ ┌─────────────┐ ┌──────────────────┐ ││ │ Input │──│ Parser │──│ Filter │──│ Output │ ││ └────────┘ └────────┘ └─────────────┘ └──────────────────┘ ││ ││ - tail - JSON - kubernetes - loki ││ - systemd - regex - record_modifier - elasticsearch ││ - forward - logfmt - grep - forward ││ - syslog - docker - lua - prometheus_exporter│└─────────────────────────────────────────────────────────────────┘Installation
Section intitulée « Installation »Trois modes couvrent la quasi-totalité des besoins : le paquet système
pour un serveur classique, l'image de conteneur pour un essai rapide, le
chart Helm pour un cluster Kubernetes. La voie Debian passe par un
dépôt signé dont la clé GPG est déposée dans /usr/share/keyrings,
jamais par un script téléchargé et exécuté à la volée.
# Clé GPG officielle Fluent Bit (dépôt signé, pas de pipe-to-shell)sudo mkdir -p /usr/share/keyringscurl -fsSL https://packages.fluentbit.io/fluentbit.key \ | sudo gpg --dearmor -o /usr/share/keyrings/fluentbit-keyring.gpg
# Dépôt signé (adapter le nom de version : noble, jammy, bookworm...)echo "deb [signed-by=/usr/share/keyrings/fluentbit-keyring.gpg] https://packages.fluentbit.io/ubuntu/noble noble main" \ | sudo tee /etc/apt/sources.list.d/fluent-bit.list
sudo apt-get update && sudo apt-get install -y fluent-bitsudo systemctl enable --now fluent-bit
fluent-bit --versiondocker run -d \ --name fluent-bit \ -v /var/log:/var/log:ro \ -v $(pwd)/fluent-bit.conf:/fluent-bit/etc/fluent-bit.conf \ fluent/fluent-bit:4.0.5helm repo add fluent https://fluent.github.io/helm-chartshelm repo update
helm install fluent-bit fluent/fluent-bit \ --namespace logging \ --create-namespace \ -f values.yamlConfiguration
Section intitulée « Configuration »Fluent Bit supporte deux formats de configuration : classique et YAML. Le
format classique (.conf) est historique et reste majoritaire dans les
exemples trouvés en ligne ; le format YAML, arrivé plus tard, s'intègre
mieux dans une ConfigMap Kubernetes. Les deux décrivent les mêmes objets :
un bloc SERVICE global, puis des inputs, des filters et des
outputs. Un même fichier ne mélange pas les deux syntaxes, il faut
choisir.
Format classique (.conf)
Section intitulée « Format classique (.conf) »Chaque section est annoncée entre crochets et ses clés sont indentées en
dessous ; parsers_file charge le fichier de parsers auquel les inputs font
ensuite référence par leur nom.
[SERVICE] flush 1 log_level info parsers_file parsers.conf
[INPUT] name tail path /var/log/containers/*.log tag kube.* parser docker
[FILTER] name kubernetes match kube.*
[OUTPUT] name stdout match *Format YAML (recommandé Kubernetes)
Section intitulée « Format YAML (recommandé Kubernetes) »Tout est regroupé sous une clé pipeline, ce qui permet de coller la
configuration telle quelle dans un manifeste sans manipuler de bloc de texte
multiligne.
service: flush: 1 log_level: info
pipeline: inputs: - name: tail path: /var/log/containers/*.log tag: kube.* parser: cri
filters: - name: kubernetes match: kube.* merge_log: on k8s-logging.parser: on
outputs: - name: loki match: "*" host: loki-gateway port: 3100 labels: job=fluentbitCollecte Kubernetes
Section intitulée « Collecte Kubernetes »Sur un cluster, le kubelet écrit la sortie standard de chaque conteneur
dans /var/log/containers/, sous forme de liens symboliques vers
/var/log/pods/. Fluent Bit tourne donc en DaemonSet, avec ces
répertoires montés en lecture seule, puis interroge l'API Kubernetes
pour rattacher à chaque ligne le pod, le namespace et les labels qui lui
correspondent. Sans cet enrichissement, un log reste une chaîne de
caractères sans propriétaire identifiable une fois arrivée dans le backend.
Configuration complète
Section intitulée « Configuration complète »Ce fichier de valeurs est celui du chart officiel : chaque clé porte un bloc
de configuration brut qui sera injecté tel quel dans la ConfigMap montée
par les pods.
config: service: | [SERVICE] Daemon Off Flush 1 Log_Level info Parsers_File parsers.conf HTTP_Server On HTTP_Listen 0.0.0.0 HTTP_Port 2020 Health_Check On
inputs: | [INPUT] Name tail Path /var/log/containers/*.log multiline.parser docker, cri Tag kube.* Mem_Buf_Limit 50MB Skip_Long_Lines On Refresh_Interval 10
filters: | [FILTER] Name kubernetes Match kube.* Merge_Log On Keep_Log Off K8S-Logging.Parser On K8S-Logging.Exclude On Labels On Annotations Off
[FILTER] Name nest Match kube.* Operation lift Nested_under kubernetes Add_prefix kubernetes_
outputs: | [OUTPUT] Name loki Match kube.* Host loki-gateway.logging.svc Port 3100 Labels job=fluentbit, namespace=$kubernetes_namespace_name Remove_keys kubernetes_container_hash,kubernetes_docker_id
customParsers: | [PARSER] Name json Format json Time_Key time Time_Format %Y-%m-%dT%H:%M:%S.%L%zDaemonSet manifest
Section intitulée « DaemonSet manifest »Le DaemonSet garantit un pod de collecte par nœud, y compris sur les
nœuds ajoutés plus tard. La limits.memory déclarée ici doit rester
cohérente avec le Mem_Buf_Limit des inputs : si le buffer autorisé
dépasse la limite du conteneur, le kubelet tue le pod (OOMKilled) avant
que Fluent Bit ait eu l'occasion de freiner sa lecture.
apiVersion: apps/v1kind: DaemonSetmetadata: name: fluent-bit namespace: loggingspec: selector: matchLabels: app: fluent-bit template: metadata: labels: app: fluent-bit spec: serviceAccountName: fluent-bit containers: - name: fluent-bit image: fluent/fluent-bit:4.0.5@sha256:ef17f25f3a76c11a267f95467cda283dddd69bdd1079f5134b0926a7998ba356 ports: - containerPort: 2020 volumeMounts: - name: varlog mountPath: /var/log readOnly: true - name: varlibdockercontainers mountPath: /var/lib/docker/containers readOnly: true - name: config mountPath: /fluent-bit/etc/ resources: limits: memory: 100Mi cpu: 100m requests: memory: 50Mi cpu: 50m volumes: - name: varlog hostPath: path: /var/log - name: varlibdockercontainers hostPath: path: /var/lib/docker/containers - name: config configMap: name: fluent-bit-configLes parsers transforment les logs bruts en structure JSON. Sans parser, la
ligne reste un champ log unique et monolithique : le niveau de sévérité,
le code HTTP ou la durée de la requête restent noyés dans le texte, donc
impossibles à filtrer côté backend. Un parser se déclare une seule fois dans
parsers.conf, puis se référence par son nom depuis un input ou depuis
le filtre kubernetes. Le choix dépend du runtime de conteneurs :
cri pour containerd et CRI-O, docker pour l'ancien pilote de
journalisation JSON.
Parsers intégrés
Section intitulée « Parsers intégrés »Ces quatre définitions couvrent les sources les plus fréquentes.
Time_Key désigne le champ qui porte l'horodatage et Time_Format la
manière de le décoder ; sans eux, Fluent Bit date l'événement à l'heure de
sa lecture et non à celle de son émission.
[PARSER] Name docker Format json Time_Key time Time_Format %Y-%m-%dT%H:%M:%S.%L%z
[PARSER] Name cri Format regex Regex ^(?<time>[^ ]+) (?<stream>stdout|stderr) (?<logtag>[^ ]*) (?<log>.*)$ Time_Key time Time_Format %Y-%m-%dT%H:%M:%S.%L%z
[PARSER] Name syslog-rfc3164 Format regex Regex /^\<(?<pri>[0-9]+)\>(?<time>[^ ]* {1,2}[^ ]* [^ ]*) (?<host>[^ ]*) (?<ident>[a-zA-Z0-9_\/\.\-]*)(?:\[(?<pid>[0-9]+)\])?(?:[^\:]*\:)? *(?<message>.*)$/ Time_Key time Time_Format %b %d %H:%M:%S
[PARSER] Name json_logs Format json Time_Key timestamp Time_Format %Y-%m-%dT%H:%M:%S.%LZParser personnalisé
Section intitulée « Parser personnalisé »Pour un format maison, un parser de type regex avec des groupes nommés
(?<champ>...) produit directement les clés du JSON de sortie : chaque nom
de groupe devient un nom de champ.
[PARSER] Name nginx_access Format regex Regex ^(?<remote>[^ ]*) (?<host>[^ ]*) (?<user>[^ ]*) \[(?<time>[^\]]*)\] "(?<method>\S+)(?: +(?<path>[^\"]*?)(?: +\S*)?)?" (?<code>[^ ]*) (?<size>[^ ]*)(?: "(?<referer>[^\"]*)" "(?<agent>[^\"]*)")?$ Time_Key time Time_Format %d/%b/%Y:%H:%M:%S %zLes filtres s'appliquent entre le parsing et la sortie, dans l'ordre de
déclaration du fichier de configuration. Ils servent à trois choses :
enrichir un enregistrement avec des champs supplémentaires, écarter
ce qui ne mérite pas d'être stocké, et remodeler la structure avant
l'envoi. Comme les sorties, chaque filtre ne traite que les événements dont
le tag correspond à sa directive Match.
Kubernetes metadata
Section intitulée « Kubernetes metadata »Enrichit chaque log avec les métadonnées du pod. Le filtre interroge l'API server avec le token du ServiceAccount monté dans le pod, ce qui suppose un rôle RBAC autorisé à lire les pods du cluster ; sans cette autorisation, les champs d'enrichissement restent vides sans que la collecte s'arrête.
[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 Merge_Log On K8S-Logging.Parser On K8S-Logging.Exclude On Labels On Annotations OffRecord modifier
Section intitulée « Record modifier »Ce filtre ajoute des champs constants à tous les enregistrements sélectionnés. C'est ce qui permet de distinguer la provenance des logs une fois plusieurs clusters regroupés dans le même backend.
[FILTER] Name record_modifier Match * Record cluster production-us-east-1 Record environment prodGrep (inclusion/exclusion)
Section intitulée « Grep (inclusion/exclusion) »La directive Regex ne conserve que les enregistrements dont le champ
visé correspond au motif ; Exclude fait l'inverse et les rejette.
C'est le levier le plus direct pour réduire le volume facturé par le
backend.
# Exclure les logs de health checks[FILTER] Name grep Match kube.* Exclude log /health|/ready|/metrics/
# Garder seulement les erreurs[FILTER] Name grep Match kube.* Regex level (error|fatal|critical)Lua (transformation avancée)
Section intitulée « Lua (transformation avancée) »Quand aucun filtre intégré ne suffit, un script Lua reçoit chaque
enregistrement et renvoie un code qui décide de son sort : 1 pour un
enregistrement modifié, 0 pour un enregistrement laissé intact, -1 pour
le supprimer du flux.
[FILTER] Name lua Match * script /fluent-bit/scripts/transform.lua call transform
# transform.luafunction transform(tag, timestamp, record) if record["level"] == nil then record["level"] = "info" end return 1, timestamp, recordendUne sortie écrit les événements vers leur destination finale et gère seule
les échecs : si le backend ne répond pas, le scheduler de Fluent Bit
réessaie avec un délai croissant pendant que les données en attente
s'accumulent dans le buffer. Plusieurs sorties cohabitent sans problème dans
la même configuration ; il suffit que leurs directives Match sélectionnent
les mêmes tags pour dupliquer un flux vers deux destinations.
L'option Auto_Kubernetes_Labels on promeut les labels du pod en labels
Loki. À manier avec prudence : chaque combinaison de labels crée un flux
distinct côté Loki, et une cardinalité trop élevée dégrade fortement les
performances d'indexation.
[OUTPUT] Name loki Match * Host loki-gateway Port 3100 Labels job=fluentbit, app=$kubernetes_labels['app'] Line_Format json Auto_Kubernetes_Labels onElasticsearch
Section intitulée « Elasticsearch »Suppress_Type_Name On est indispensable à partir d'Elasticsearch 8,
qui a supprimé la notion de type de document : sans cette option, le
serveur rejette les requêtes d'indexation. Logstash_Format On fait générer
un nom d'index daté, compatible avec les politiques de rétention par index.
[OUTPUT] Name es Match * Host elasticsearch Port 9200 Index logs-%Y.%m.%d Type _doc Suppress_Type_Name On Logstash_Format On Retry_Limit 5Forward (vers Fluentd)
Section intitulée « Forward (vers Fluentd) »Le protocole forward est le format d'échange natif de l'écosystème
Fluentd. Require_ack_response True fait attendre l'accusé de
réception de l'agrégateur avant de considérer un lot comme livré, ce qui
évite de perdre silencieusement des logs quand la liaison se coupe.
[OUTPUT] Name forward Match * Host fluentd-aggregator Port 24224 Require_ack_response TrueMulti-output
Section intitulée « Multi-output »Deux sorties déclarées sur le même Match reçoivent chacune une copie du
flux. Le cas typique associe un backend de recherche à chaud et un bucket
objet pour l'archive longue durée, beaucoup moins coûteuse au gigaoctet.
# Dupliquer vers plusieurs destinations[OUTPUT] Name loki Match * Host loki
[OUTPUT] Name s3 Match * bucket my-logs-bucket region eu-west-1 total_file_size 50MMultiline logging
Section intitulée « Multiline logging »Une trace d'exception Java ou Python s'étale sur plusieurs lignes, et chacune
arrive comme un événement distinct : l'erreur se retrouve éparpillée en
dix entrées illisibles dans le backend. Un multiline parser répare cela
en définissant une règle de démarrage (start_state) et une règle de
continuation (cont) pour recoller ces lignes en un seul enregistrement.
Fluent Bit embarque déjà des règles prêtes à l'emploi (docker, cri,
java, python, go) utilisables directement dans multiline.parser.
[MULTILINE_PARSER] name java_multiline type regex flush_timeout 1000 rule "start_state" "^(\d{4}-\d{2}-\d{2})" "cont" rule "cont" "^(?!\d{4}-\d{2}-\d{2})" "cont"
[INPUT] name tail path /var/log/app/*.log multiline.parser java_multilineMonitoring
Section intitulée « Monitoring »Fluent Bit publie son propre état par une API HTTP interne, désactivée par défaut. Une fois activée, elle sert à la fois de sonde de vivacité pour Kubernetes et de source de métriques pour Prometheus : enregistrements lus par input, octets envoyés par output et, surtout, compteurs de tentatives et d'échecs qui trahissent un backend en difficulté bien avant que les utilisateurs ne signalent des logs manquants.
Métriques HTTP
Section intitulée « Métriques HTTP »Les trois endpoints répondent sur le port 2020 déclaré ci-dessous :
/api/v1/health pour les probes Kubernetes, les deux autres pour les
métriques, respectivement au format JSON et au format Prometheus.
[SERVICE] HTTP_Server On HTTP_Listen 0.0.0.0 HTTP_Port 2020
# Endpoints disponibles :# GET /api/v1/health# GET /api/v1/metrics# GET /api/v1/metrics/prometheusPrometheus ServiceMonitor
Section intitulée « Prometheus ServiceMonitor »Cette ressource suppose l'opérateur Prometheus installé sur le cluster : c'est lui qui la lit pour générer la configuration de scraping. Chaque pod Fluent Bit est alors interrogé toutes les 30 secondes sur son endpoint Prometheus.
apiVersion: monitoring.coreos.com/v1kind: ServiceMonitormetadata: name: fluent-bit namespace: loggingspec: selector: matchLabels: app: fluent-bit endpoints: - port: http path: /api/v1/metrics/prometheus interval: 30sBonnes pratiques
Section intitulée « Bonnes pratiques »Ces quatre réflexes évitent les deux incidents les plus courants en production : le collecteur qui consomme plus de mémoire que l'application qu'il observe, et le flux de logs qui fait exploser la facture de stockage. Tous se configurent au moment de l'installation, avant que le volume ne devienne un problème à traiter dans l'urgence.
-
Limitez la mémoire
Configurez
Mem_Buf_Limitpour éviter les OOM.[INPUT]Name tailMem_Buf_Limit 50MB -
Activez les health checks
[SERVICE]Health_Check On -
Utilisez des labels Kubernetes
Filtrez par namespace pour ne pas tout envoyer.
[FILTER]Name grepMatch kube.*Regex kubernetes_namespace_name ^(prod-|staging-) -
Gérez le backpressure
En cas de destination lente, évitez d'accumuler en mémoire.
Dépannage
Section intitulée « Dépannage »Les pannes de collecte se ressemblent toutes : les logs n'arrivent plus et rien n'indique où la chaîne s'est rompue. Le tableau relie les symptômes observés à leur cause la plus probable ; les commandes qui suivent permettent de valider une configuration et de relire le pipeline en mode verbeux sans redémarrer le service de production.
| Symptôme | Cause probable | Solution |
|---|---|---|
| Logs manquants | Path incorrect | Vérifier le pattern glob |
| Parser échoue | Format non reconnu | Tester avec fluent-bit -c ... -i dummy |
| Mémoire élevée | Buffer plein | Réduire Mem_Buf_Limit |
| Connection refused | Output down | Vérifier la destination |
# Test de configurationfluent-bit -c /etc/fluent-bit/fluent-bit.conf --dry-run
# Mode debugfluent-bit -c /etc/fluent-bit/fluent-bit.conf -vvv
# Parser testecho '{"level":"info","msg":"test"}' | fluent-bit -i stdin -o stdout -p format=jsonFAQ : questions fréquentes
Section intitulée « FAQ : questions fréquentes »Les questions ci-dessous reviennent le plus souvent lors d'une première mise en production : positionnement face à Fluentd, choix du parser et envoi des entrées vers Loki.
parsers.conf. Un parser transforme une ligne brute en JSON structuré : les parsers json, cri ou docker pour les conteneurs, ou un parser regex personnalisé pour un format applicatif (logs nginx, par exemple). On l'associe à l'input tail ou au filtre Kubernetes.loki : on indique le host et le port de Loki (3100) et les labels à appliquer. Fluent Bit tail les fichiers de logs, les enrichit éventuellement avec les métadonnées Kubernetes, puis pousse chaque entrée vers l'endpoint de Loki. C'est un pipeline validé, lisible ensuite dans Grafana via LogQL.À retenir
Section intitulée « À retenir »- Ultra-léger : ~1 MB de mémoire
- CNCF Graduated : standard Kubernetes
- Parser CRI/Docker : natif pour les conteneurs
- Kubernetes filter : enrichissement auto des métadonnées
- Multi-output : dupliquer vers plusieurs destinations
Prochaines étapes
Section intitulée « Prochaines étapes »Ressources
Section intitulée « Ressources »La documentation officielle répertorie l'intégralité des plugins avec toutes leurs options ; ce guide n'en couvre que les plus employés.