Inhoudsopgave
- Wat je gaat leren
- Vereisten
- Hoe Kubernetes logging werkt (en waar het misgaat)
- Waarom Fluent Bit en niet Fluentd
- Elasticsearch en Kibana deployen met ECK
- Fluent Bit deployen als DaemonSet
- Controleren of logs in Kibana aankomen
- Productie-hardening
- Veelvoorkomende problemen
- Wat je hebt geleerd
- Verder lezen
Wat je gaat leren
Aan het einde van deze tutorial heb je een werkende gecentraliseerde logging-pipeline: Fluent Bit die container-logs van elke node verzamelt, ze verrijkt met Kubernetes-metadata, via TLS naar Elasticsearch 8.x stuurt, en een Kibana data view klaar voor queries. Je begrijpt waarom elke configuratiekeuze er is en wat je moet aanpassen als je cluster groeit.
Vereisten
Zorg dat je het volgende hebt:
- Een draaiend Kubernetes-cluster, versie 1.25 of nieuwer, met containerd als container-runtime. Managed clusters (GKE, EKS, AKS) en lokale clusters (kind, minikube) werken allebei. Deze tutorial richt zich op Fluent Bit 5.0 en Elasticsearch 8.17.
- Helm 3 geinstalleerd en geconfigureerd voor je cluster.
kubectlmet cluster-admin rechten. De ECK-operator installeert CRDs en cluster-brede resources.- Voldoende clustercapaciteit voor het Elasticsearch StatefulSet: minimaal 3 nodes met 8 Gi geheugen beschikbaar per ES-pod. Voor een snelle test op kind/minikube werkt een single-node ES-setup, maar dat is niet productiewaardig.
- Als je al Prometheus draait, Fluent Bit stelt een
/api/v1/metrics/prometheusendpoint beschikbaar dat je kunt scrapen. Niet vereist voor deze tutorial, maar goed om te weten.
Hoe Kubernetes logging werkt (en waar het misgaat)
Containers schrijven naar stdout en stderr. De container-runtime (containerd) vangt die output op en schrijft het naar logbestanden op de node: /var/log/pods/<namespace>_<pod>_<uid>/<container>/0.log. Symlinks in /var/log/containers/ bieden een platte directory van alle containers op de node.
De kubelet roteert deze bestanden. De defaults zijn 10 MiB per bestand (containerLogMaxSize) en 5 geroteerde bestanden (containerLogMaxFiles). kubectl logs leest alleen het nieuwste bestand, dus je krijgt maximaal 10 MiB van de meest recente output van een enkele pod.
Voor het debuggen van een enkele pod in development is dat prima. In productie gaat het snel mis:
- Pod-verwijdering verwijdert logs. Zodra een pod wordt verwijderd of ge-evict, ruimt de kubelet de logbestanden op. Post-mortem analyse is dan onmogelijk.
- Geen aggregatie. Een Deployment met 10 replica's betekent 10 losse
kubectl logscommando's. Correlatie tussen services vereist handwerk. - API-server belasting. Elke
kubectl logscall gaat via de API-server naar de kubelet. Bij schaal (honderden pods, meerdere teams) wordt dit een bottleneck. - Een 50-node cluster met 500 pods kan 10-50 GB logs per dag genereren. Handmatig ophalen is niet realistisch.
De Kubernetes-documentatie beschrijft drie cluster-level logging-architecturen. De meest gebruikte is de node logging agent: een DaemonSet die container-logs van het nodebestandssysteem leest en doorstuurt naar een centrale opslag. Dat is het EFK-patroon.
Waarom Fluent Bit en niet Fluentd
Fluent Bit en Fluentd zijn allebei CNCF graduated projecten onder de Fluent-paraplu, wat voor verwarring zorgt. Het zijn verschillende tools, gebouwd voor verschillende rollen.
| Fluentd | Fluent Bit | |
|---|---|---|
| Taal | Ruby + C | Puur C |
| Geheugengebruik | ~40 MB basis | Minder dan 30 MB werkgeheugen |
| Runtime-afhankelijkheden | Ruby-runtime, gem management | Geen externe afhankelijkheden |
| Plugin-ecosysteem | 1.000+ Ruby gems | ~100 ingebouwde plugins |
| CNCF-status | Graduated | Graduated |
| Ontwerprol | Centrale aggregatie, complexe transformaties | Node-level collection agent |
Voor een Kubernetes DaemonSet (een pod per node) is de sub-30 MB footprint en de dependency-loze binary gewoon een betere keuze. Fluent Bit handelt logcollectie, metadata-verrijking en Elasticsearch-forwarding af in een enkel binary, zonder Fluentd als tussenlaag.
Elasticsearch en Kibana deployen met ECK
De Elastic Cloud on Kubernetes (ECK) operator automatiseert TLS-certificaten, rolling upgrades en wachtwoordbeheer. Handmatige manifests werken ook, maar ECK neemt een groot oppervlak aan certificaat- en lifecycle-beheer van je over.
Stap 1: Installeer de ECK-operator
helm repo add elastic https://helm.elastic.co
helm repo update
helm install elastic-operator elastic/eck-operator \
-n elastic-system --create-namespace
Controleer of de operator draait:
kubectl get pods -n elastic-system
Verwachte output:
NAME READY STATUS RESTARTS AGE
elastic-operator-<hash> 1/1 Running 0 45s
Stap 2: Maak de logging-namespace en deploy Elasticsearch
# elasticsearch.yaml
apiVersion: v1
kind: Namespace
metadata:
name: logging
---
apiVersion: elasticsearch.k8s.elastic.co/v1
kind: Elasticsearch
metadata:
name: elasticsearch
namespace: logging
spec:
version: 8.17.0
nodeSets:
- name: default
count: 3 # minimum voor productie-HA
config:
node.store.allow_mmap: false # vereist voor de meeste container-runtimes
podTemplate:
spec:
containers:
- name: elasticsearch
resources:
requests:
memory: 4Gi
cpu: 500m
limits:
memory: 8Gi
cpu: "2"
env:
- name: ES_JAVA_OPTS
value: "-Xms4g -Xmx4g" # heap = 50% van geheugenlimiet, min/max gelijk
kubectl apply -f elasticsearch.yaml
De JVM-heapregel is belangrijk: stel -Xms en -Xmx in op dezelfde waarde, precies 50% van de pod-geheugenlimiet. Ga nooit boven 31 GB, want daarboven kan de JVM geen compressed object pointers gebruiken en verspil je geheugen.
Wacht tot het cluster groen is:
kubectl get elasticsearch -n logging
Verwachte output (kan 2-3 minuten duren):
NAME HEALTH NODES VERSION PHASE AGE
elasticsearch green 3 8.17.0 Ready 3m
Stap 3: Deploy Kibana
# kibana.yaml
apiVersion: kibana.k8s.elastic.co/v1
kind: Kibana
metadata:
name: kibana
namespace: logging
spec:
version: 8.17.0
count: 1
elasticsearchRef:
name: elasticsearch
kubectl apply -f kibana.yaml
Stap 4: Haal het Elasticsearch-wachtwoord op
ECK genereert een wachtwoord voor de elastic superuser en slaat het op in een Secret:
kubectl get secret elasticsearch-es-elastic-user -n logging \
-o go-template='{{.data.elastic | base64decode}}'
Bewaar dit wachtwoord. Je hebt het nodig voor zowel de Fluent Bit-configuratie als de Kibana-login.
Checkpoint: Elasticsearch is groen met 3 nodes, Kibana-pod is Running, en je hebt het elastic wachtwoord.
Fluent Bit deployen als DaemonSet
Stap 5: Maak de RBAC-resources aan
Fluent Bit heeft leestoegang nodig tot pod- en namespace-metadata voor de Kubernetes filterplugin:
# fluent-bit-rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: fluent-bit
namespace: logging
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: fluent-bit-read
rules:
- apiGroups: [""]
resources:
- namespaces
- pods
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: fluent-bit-read
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: fluent-bit-read
subjects:
- kind: ServiceAccount
name: fluent-bit
namespace: logging
kubectl apply -f fluent-bit-rbac.yaml
Stap 6: Maak de Elasticsearch credentials Secret
Sla de credentials op in een Secret (nooit in een ConfigMap, die is standaard leesbaar voor elke pod in de namespace):
kubectl create secret generic elasticsearch-credentials \
-n logging \
--from-literal=ES_USERNAME=elastic \
--from-literal=ES_PASSWORD=<plak-wachtwoord-uit-stap-4>
Stap 7: Kopieer het ECK CA-certificaat
ECK slaat het CA-certificaat op in elasticsearch-es-http-certs-public. Omdat Kubernetes Secrets namespace-scoped zijn, kan Fluent Bit er direct bij als beide in de logging namespace draaien. Als je Fluent Bit-pods in een andere namespace draaien, kopieer dan de Secret:
kubectl get secret elasticsearch-es-http-certs-public -n logging \
-o yaml | kubectl apply -n <fluent-bit-namespace> -f -
Stap 8: Maak de Fluent Bit-configuratie
Deze ConfigMap gebruikt het YAML-configuratieformaat dat beschikbaar is sinds Fluent Bit 3.2. YAML vereenvoudigt Kubernetes-integratie omdat parserdefinities in hetzelfde bestand kunnen staan, wat aparte parser-ConfigMaps overbodig maakt.
# fluent-bit-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: fluent-bit-config
namespace: logging
data:
fluent-bit.yaml: |
service:
flush: 5 # flush-interval in seconden
log_level: info
http_server: true # activeert health- en metrics-endpoints
http_listen: 0.0.0.0
http_port: 2020
health_check: true
storage.metrics: true
pipeline:
inputs:
- name: tail
path: /var/log/containers/*.log
multiline.parser: docker, cri # ondersteunt beide formaten; containerd gebruikt CRI
tag: kube.*
db: /var/log/flb_kube.db # onthoudt leespositie na herstart
mem_buf_limit: 5MB
skip_long_lines: true
refresh_interval: 10
filters:
- name: kubernetes
match: kube.*
kube_url: https://kubernetes.default.svc.cluster.local:443
kube_ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
kube_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
merge_log: true # parse JSON-logvelden en flatten in het record
keep_log: false # verwijder raw log-veld na merge (bespaart opslag)
labels: true
annotations: false # annotations zijn vaak groot; activeer selectief
outputs:
- name: es
match: kube.*
host: elasticsearch-es-http.logging.svc.cluster.local
port: 9200
http_user: ${ES_USERNAME}
http_passwd: ${ES_PASSWORD}
tls: true
tls.verify: true
tls.ca_file: /etc/ssl/elasticsearch/ca.crt
logstash_format: true # date-stamped indices voor ILM-compatibiliteit
logstash_prefix: kubernetes # indexnaam wordt kubernetes-2026.04.09
logstash_dateformat: "%Y.%m.%d"
replace_dots: true # ES weigert dots in veldnamen
suppress_type_name: true # vereist voor ES 8.x (mapping types verwijderd)
retry_limit: 5 # eindige retries voorkomen onbegrensde geheugengroei
time_key: "@timestamp"
trace_error: true # logt ES-foutresponses voor debugging
kubectl apply -f fluent-bit-config.yaml
De db-parameter op de Tail-input is belangrijk. Zonder dit verliest Fluent Bit zijn leespositie bij een herstart en leest het ofwel alle logs opnieuw (duplicaten) of mist logs die tijdens de herstart zijn geschreven.
De multiline.parser: docker, cri instelling laat Fluent Bit het logformaat automatisch detecteren. Moderne clusters met containerd produceren CRI-format logs met een P/F vlag die aangeeft of een regel gedeeltelijk of volledig is. Oudere Docker-gebaseerde clusters gebruiken JSON-wrapped logregels.
Stap 9: Deploy het DaemonSet
# fluent-bit-daemonset.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluent-bit
namespace: logging
spec:
selector:
matchLabels:
app: fluent-bit
template:
metadata:
labels:
app: fluent-bit
spec:
serviceAccountName: fluent-bit
tolerations:
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule # verzamel ook control-plane logs
containers:
- name: fluent-bit
image: cr.fluentbit.io/fluent/fluent-bit:5.0
ports:
- containerPort: 2020
name: http
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 256Mi
livenessProbe:
httpGet:
path: /api/v1/health
port: 2020
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /api/v1/health
port: 2020
initialDelaySeconds: 10
periodSeconds: 5
envFrom:
- secretRef:
name: elasticsearch-credentials
volumeMounts:
- name: config
mountPath: /fluent-bit/etc/fluent-bit.yaml
subPath: fluent-bit.yaml
- name: varlog
mountPath: /var/log
readOnly: true
- name: varlibdockercontainers
mountPath: /var/lib/docker/containers
readOnly: true
- name: elasticsearch-ca
mountPath: /etc/ssl/elasticsearch
readOnly: true
volumes:
- name: config
configMap:
name: fluent-bit-config
- name: varlog
hostPath:
path: /var/log
- name: varlibdockercontainers
hostPath:
path: /var/lib/docker/containers
- name: elasticsearch-ca
secret:
secretName: elasticsearch-es-http-certs-public
kubectl apply -f fluent-bit-daemonset.yaml
Controleer dat er een pod per node draait:
kubectl get daemonset fluent-bit -n logging
Verwachte output:
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
fluent-bit 3 3 3 3 3 <none> 30s
Bekijk de Fluent Bit-logs op fouten:
kubectl logs -n logging daemonset/fluent-bit --tail=20
Als je [output:es:es.0] ...connected ziet en geen [error] regels, dan is de pipeline gezond.
Checkpoint: Fluent Bit DaemonSet draait op elke node, geen fouten in de logs, verbonden met Elasticsearch.
Controleren of logs in Kibana aankomen
Stap 10: Maak een Kibana data view
Port-forward Kibana:
kubectl port-forward svc/kibana-kb-http 5601:5601 -n logging
Open https://localhost:5601 in je browser (accepteer het self-signed certificaat). Log in met gebruikersnaam elastic en het wachtwoord uit Stap 4.
Ga naar Stack Management -> Data Views -> Create data view:
- Index pattern:
kubernetes-* - Timestamp field:
@timestamp - Name: Kubernetes Logs
Sla de data view op. In Kibana 8.x vervangt "Data Views" het oudere "Index Patterns" concept.
Ga naar Discover, selecteer de "Kubernetes Logs" data view, en je zou logrecords moeten zien met Kubernetes-metadatavelden: kubernetes.pod_name, kubernetes.namespace_name, kubernetes.container_name en kubernetes.labels.*.
Checkpoint: Logs zichtbaar in Kibana Discover met Kubernetes-metadata.
Productie-hardening
De setup tot nu toe werkt, maar mist productieduurzaamheid. Drie gebieden vragen aandacht.
Index lifecycle management
Een 50-node cluster dat 50 GB/dag genereert, verzamelt 4,5 TB over 90 dagen. Zonder Index Lifecycle Management (ILM) groeit de opslag tot Elasticsearch geen schijfruimte meer heeft.
Een verstandig defaultbeleid:
| Fase | Leeftijd | Actie |
|---|---|---|
| Hot | 0-7 dagen | Actieve write-index op snelle opslag. Rollover bij 50 GB of 7 dagen. |
| Warm | 7-30 dagen | Read-only, force-merge naar 1 segment, gecomprimeerd. |
| Cold | 30-90 dagen | Frozen op goedkoopste opslagtier. |
| Delete | 90+ dagen | Verwijderen. |
ILM werkt omdat Fluent Bit's logstash_format: true date-stamped indices aanmaakt (kubernetes-2026.04.09) die op een natuurlijke manier passen bij rollover-policies.
Filesystem buffering
Als Elasticsearch down gaat, loopt de in-memory buffer van Fluent Bit vol. Zonder filesystem buffering worden logs gedroopt of past Fluent Bit backpressure toe op de Tail-input, waardoor het lezen van logs helemaal stopt.
Voeg toe aan de service-sectie van de ConfigMap:
service:
storage.path: /var/log/flb-storage/
storage.sync: normal
storage.checksum: false
storage.max_chunks_up: 128
storage.backlog.mem_limit: 5M
En stel storage.type: filesystem in op de Tail-input:
inputs:
- name: tail
storage.type: filesystem # schrijft naar disk als geheugen vol is
# ... rest van de inputconfig
Wanneer storage.type: filesystem is ingesteld, bepaalt de storage.max_chunks_up parameter hoeveel chunks in het geheugen blijven. De rest gaat naar het pad in storage.path.
Elasticsearch resource-hardening
Voor productie-workloads stel je requests gelijk aan limits op de Elasticsearch-pods. Dit geeft ze Guaranteed QoS-klasse, waardoor kubelet-eviction onder geheugendruk wordt voorkomen. Voeg een PodDisruptionBudget toe met maxUnavailable: 1 om te voorkomen dat er meer dan een ES-node tegelijk wordt gedraineerd.
Veelvoorkomende problemen
| Symptoom | Waarschijnlijke oorzaak | Oplossing |
|---|---|---|
| Geen logs in Kibana | Fluent Bit bereikt ES niet | Check kubectl logs daemonset/fluent-bit -n logging op failed to flush chunk fouten. Controleer de TLS CA-mount en credentials. |
_type unknown fout in Fluent Bit-logs |
ES 8.x heeft mapping types verwijderd | Voeg suppress_type_name: true toe aan de ES-output. |
| Logs missen na herstart van Fluent Bit | Geen positie-tracking | Voeg db: /var/log/flb_kube.db toe aan de Tail-input. |
| Hoog geheugengebruik op Fluent Bit-pods | Oneindige retries tijdens ES-uitval | Stel retry_limit: 5 in in plaats van false (de Helm chart default). |
| Geen Kubernetes-metadata op logs | RBAC ontbreekt | Controleer of de ClusterRole get, list, watch geeft op pods en namespaces. |
| Kibana data view toont geen resultaten | Indexpatroon-mismatch | De logstash_prefix in Fluent Bit moet overeenkomen met het data view patroon. Als de prefix kubernetes is, moet het patroon kubernetes-* zijn. |
Voor diepere isolatie voeg je tijdelijk een stdout output toe aan de Fluent Bit-config (name: stdout, match: kube.*). Als logs verschijnen in de output van de Fluent Bit-pod maar niet in Elasticsearch, dan zit het probleem in connectiviteit of authenticatie, niet in collectie.
Wat je hebt geleerd
Je hebt een complete gecentraliseerde logging-pipeline gedeployd: Fluent Bit leest container-logs van het nodebestandssysteem via de Tail-input, verrijkt records met Kubernetes-metadata (podnaam, namespace, labels), stuurt ze via TLS naar Elasticsearch 8.x, en Kibana biedt de query-interface. Je weet waarom het db-bestand belangrijk is voor herstart-veiligheid, waarom suppress_type_name vereist is voor ES 8.x, en waar je moet beginnen met hardening voor productie (ILM, filesystem buffering, resource-garanties).
Verder lezen
- Als je nog geen metriek-monitoring naast logs hebt, behandelt de Prometheus en kube-prometheus-stack tutorial het opzetten van metriekcollectie en alerting op hetzelfde cluster.
- Om te begrijpen waarom Fluent Bit-pods (of welke pod dan ook) CPU-throttling kunnen laten zien ondanks laag gemiddeld gebruik, zie Kubernetes CPU-throttling: waarom pods stilvallen bij laag gebruik.
- Het Fluent Bit-project ondersteunt ook Loki als output-bestemming. Als je al Grafana draait voor metrics via kube-prometheus-stack, kan Loki plus Grafana Elasticsearch plus Kibana vervangen met een lagere opslag-footprint, ten koste van minder krachtige full-text search.
- Wil je ook distributed traces en een uniform wire-formaat naast logs, dan behandelt de OpenTelemetry op Kubernetes-tutorial het deployen van de OpenTelemetry Collector. Fluent Bit voor logs houden en OpenTelemetry alleen voor traces en metrics gebruiken is een prima splitsing als je log-volume hoog is.