EKS Logging with Fluent Bit: How Kubernetes Logs Reach CloudWatch

Kubernetes logs are written to the node where your container ran. When that pod is deleted or the node terminates, those log files are deleted too. Without a log forwarder in place before that happens, you have no record of what your application did during the incident.

Fluent Bit solves this. It runs on every EKS node as a DaemonSet, reads log files in near real-time, and forwards them to CloudWatch Logs before anything can delete them.

This page covers how the Kubernetes logging model works, why kubectl logs is not sufficient for production, and how to deploy and configure Fluent Bit on EKS.

Simple explanation

Here is the full journey of a log line, in plain terms:

  1. Your application writes a message to stdout or stderr, the standard output streams of the process.
  2. The kubelet on the node captures that output and writes it to a file on the node’s local filesystem, under /var/log/containers/.
  3. When you run kubectl logs pod-name, Kubernetes reads that file from the node and returns it to you.
  4. When the pod is deleted or the node terminates, that file is deleted too. The log is gone.
  5. Fluent Bit runs on every node as a DaemonSet. It continuously reads from those log files and copies each line to an external system (CloudWatch, S3, OpenSearch) before anything can delete them.

That last step is the one that matters for production. Without it, you are debugging from memory.

Analogy

Think of container logs like notes written on a restaurant table’s paper liner. While you are sitting there, a waiter can read them back to you. When you leave (pod deleted) or the restaurant closes for the night (node terminates), the table gets cleared and the notes are gone. Fluent Bit is the waiter who photographs each table every few seconds and uploads those photos to permanent storage before the table is reset.

How logging works in Kubernetes and EKS

Every container in Kubernetes is expected to write output to stdout and stderr. The kubelet on each EKS node captures this output and writes it to log files on the node’s local filesystem. Files follow a naming convention: <pod-name>_<namespace>_<container-name>-<container-id>.log.

The end-to-end flow looks like this:

  • Container writes to stdout/stderr
  • Kubelet writes to /var/log/containers/ on the node
  • kubectl logs reads from that file while it exists
  • Fluent Bit DaemonSet reads from the same files and forwards to CloudWatch, OpenSearch, S3, or Firehose in near real-time
  • External destination stores the logs durably, independent of pod and node lifecycle

This is the node-level logging model. Kubernetes does not natively ship logs anywhere. It leaves that entirely to an external agent. Fluent Bit is that agent on EKS.

Two limitations of the native model you must account for in production:

  1. Logs are local to the node. A Spot interruption, a scale-down event, or a hardware failure takes the log files with it.
  2. Logs are per-pod. Once a pod is deleted, its log files are cleaned up. The pre-crash window is exactly when you need them most.

Why Fluent Bit on EKS

Fluent Bit is a lightweight log processor and forwarder written in C. It runs as a DaemonSet, one pod per node, so it is always co-located with the log files it needs to read. Because it runs on the node itself, it reads directly from /var/log/containers/ without any changes to your application.

Why a DaemonSet specifically? Log files live on the node where the container ran. The only process that can reliably read them is one running on the same node. A DaemonSet guarantees coverage: as EKS node groups scale out and new nodes join, a Fluent Bit pod is placed on each new node automatically.

Analogy

A DaemonSet is like a security guard stationed at every building entrance, not one guard patrolling the whole building. If a new entrance opens (a new node joins the cluster), a new guard is posted there automatically. That is what makes it the right model for log collection: you need one collector per node, not one collector trying to reach all nodes from outside.

Why Fluent Bit rather than something else? AWS maintains an EKS-optimised Fluent Bit container image and a Helm chart that pre-configures it for common EKS destinations. The amazon-cloudwatch-observability EKS add-on includes Fluent Bit out of the box. Compared to Fluentd, Fluent Bit has a smaller memory footprint per node, which matters when running one forwarder instance on every node in a large cluster.

Kubernetes metadata enrichment. Fluent Bit’s Kubernetes filter annotates every log record with pod_name, namespace_name, container_name, and the pod’s labels, automatically, without any code changes to your services. This makes it possible to filter CloudWatch Logs queries by namespace or service name even when all logs flow into a single log group.

Deploying Fluent Bit on EKS

Option 1: EKS add-on

The amazon-cloudwatch-observability add-on installs both the CloudWatch metrics agent and Fluent Bit together. This is the lowest-effort path for teams using Amazon EKS and sending logs to CloudWatch.

aws eks create-addon \
  --cluster-name my-cluster \
  --addon-name amazon-cloudwatch-observability \
  --region eu-west-1

Logs are sent to CloudWatch Logs automatically once the add-on is installed and the required IAM permissions are in place.

Start here

If you are setting up logging for the first time, use the EKS add-on. It takes under two minutes and covers the most common case. You can always migrate to the Helm chart later if you need custom destinations or filtering.

Option 2: Helm chart

Use the Helm chart when you need to customise destinations, log filtering, or output format.

helm repo add aws-observability https://aws-observability.github.io/helm-charts
helm repo update

helm install aws-for-fluent-bit aws-observability/aws-for-fluent-bit \
  --namespace amazon-cloudwatch \
  --create-namespace \
  --values fluent-bit-values.yaml

A basic fluent-bit-values.yaml to send logs to CloudWatch:

cloudWatch:
  enabled: true
  region: eu-west-1
  logGroupName: /eks/my-cluster/containers
  logStreamPrefix: from-eks-

firehose:
  enabled: false

kinesis:
  enabled: false

elasticsearch:
  enabled: false

IAM permissions via IRSA

Fluent Bit needs IAM permissions to write to CloudWatch Logs. Grant these via IRSA (IAM Roles for Service Accounts). Do not rely on the node instance profile, which grants the same permissions to every process running on the node.

The Fluent Bit service account needs these permissions:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents",
        "logs:DescribeLogStreams"
      ],
      "Resource": "arn:aws:logs:*:*:*"
    }
  ]
}

Annotate the Fluent Bit service account with the IAM role ARN:

kubectl annotate serviceaccount fluent-bit \
  -n amazon-cloudwatch \
  eks.amazonaws.com/role-arn=arn:aws:iam::123456789012:role/FluentBitRole
Silent failure risk

If the Fluent Bit pod’s IAM role is missing or misconfigured, Fluent Bit fails to write to CloudWatch and silently drops records with no application-level error. Verify the setup immediately after deployment: kubectl logs -n amazon-cloudwatch daemonset/fluent-bit

Where EKS logs can go

Fluent Bit can forward to multiple destinations simultaneously. The main options on AWS:

CloudWatch Logs is the default for teams already using CloudWatch. It supports log queries with CloudWatch Insights, integrates with alarms and metric filters, and requires no additional infrastructure. Good starting point for most EKS clusters.

Amazon OpenSearch Service is better for full-text search, complex log queries, and dashboards. Consider it when CloudWatch Insights becomes slow or expensive at your log volume, or when you need rich visualisations alongside your metrics.

Amazon S3 is the cheapest long-term storage. Use S3 as a secondary destination for compliance retention when logs must be kept for years but are rarely queried. Not suitable for real-time debugging on its own.

Amazon Kinesis Data Firehose is a streaming intermediary that delivers to S3, OpenSearch, or Splunk with batching and optional transformation. Useful when your destination requires a specific format or you need to fan out to multiple systems.

Two destinations are common

Many production clusters send to CloudWatch Logs for operational debugging and to S3 for compliance archival. Fluent Bit supports multiple outputs in a single configuration, so there is no need to pick just one.

When you need centralised log forwarding

You need Fluent Bit or an equivalent log forwarder in any of these situations:

  • Production EKS workloads. Pods restart, nodes terminate, and Spot instances disappear. You cannot reconstruct incidents from logs that no longer exist.
  • Multiple pods running the same service. kubectl logs shows one pod at a time. A centralised system lets you search across all replicas simultaneously.
  • Compliance or audit requirements. Financial services, healthcare, and other regulated industries often require log retention for months or years. CloudWatch and S3 both support configurable retention policies.
  • Alerting on log content. CloudWatch metric filters can count error-level log events and trigger alarms, but only if logs reach CloudWatch in the first place.
  • Cross-service debugging. When a request touches several microservices, searching by a trace ID across all logs at once is far faster than switching between kubectl logs commands.

For local development or short-lived test clusters, kubectl logs is usually sufficient. For anything that handles real traffic, set up log forwarding before your first deployment.

kubectl logs vs Fluent Bit vs centralised logging

kubectl logsFluent Bit aloneFluent Bit + CloudWatch / OpenSearch
Survives pod deletionNoNo (reads in-flight)Yes
Survives node terminationNoNoYes
Search across all podsNoNoYes
Setup requiredNoneDaemonSet + IRSADaemonSet + IRSA + destination config
Alerting on log contentNoNoYes
Best forLocal dev, quick checksNot useful aloneProduction EKS

The practical rule: use kubectl logs for development and quick checks on running pods. Use Fluent Bit with a centralised destination for any cluster that handles real traffic.

Structured logging and why it matters here

The most queryable log format is JSON. When your application writes structured JSON to stdout, Fluent Bit can parse individual fields and you can filter on them in CloudWatch Insights or OpenSearch. The full detail is in the structured logging guide.

Unstructured (hard to query):

2026-03-16 14:23:51 INFO User 12345 logged in from 203.0.113.50

Structured JSON (queryable by user_id, action, ip_address):

{"timestamp":"2026-03-16T14:23:51Z","level":"INFO","action":"login","user_id":"12345","ip_address":"203.0.113.50","duration_ms":42}

A CloudWatch Insights query to find all login events for a specific user:

fields @timestamp, action, user_id, ip_address, duration_ms
| filter action = "login" and user_id = "12345"
| sort @timestamp desc

Without structured logging, this query is not possible without manual text parsing. Switch to JSON logs early. Retrofitting it later requires application changes across every service.

Costly at scale

Unstructured logs become a serious operational burden in large clusters. During a production incident, correlating events across 50 pods using text search is painfully slow compared to filtering by a structured field. The earlier you switch, the less painful the change.

Filtering and enriching logs with Fluent Bit

Fluent Bit processes logs through an input, filter, and output pipeline. The Kubernetes filter is the most important stage for EKS. It adds pod metadata to every log record:

[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 Off

After this filter, every log record automatically includes kubernetes.pod_name, kubernetes.namespace_name, kubernetes.container_name, and the pod’s labels, without any changes to your application code.

You can also drop noisy health check logs before they consume log storage:

[FILTER]
    Name    grep
    Match   kube.*
    Exclude path /healthz

Filtering at the Fluent Bit level is cheaper than ingesting everything and filtering at query time, especially at high log volumes.

Common mistakes

  1. Waiting until after an incident to set up log forwarding. The first time most teams realise logs are ephemeral is during a production outage. Install Fluent Bit before you deploy your first production workload. The EKS add-on takes under two minutes.
  2. Missing IRSA configuration. If the Fluent Bit service account does not have the correct IAM role, it fails to write to CloudWatch and silently drops records. Verify the setup immediately after deployment with kubectl logs -n amazon-cloudwatch daemonset/fluent-bit. Use IRSA, not node instance profiles.
  3. Not setting CloudWatch Logs retention periods. Without a retention policy, logs accumulate indefinitely and storage costs grow without bound. Set a retention period on every log group from day one: aws logs put-retention-policy —log-group-name /eks/my-cluster/containers —retention-in-days 30
  4. Logging at the wrong level in production. DEBUG-level logging on a busy service generates large volumes and high ingestion costs. INFO or WARN is the right default for production. Use environment-level configuration to control log levels per environment, and use Fluent Bit grep filters to drop health check noise at the source.
  5. Over-logging health checks and liveness probes. Kubernetes hits your health check endpoint every few seconds. Logging each probe as an INFO event adds significant noise and cost. Filter them out in Fluent Bit or suppress them at the application level before they leave the node.

Frequently asked questions

Why is kubectl logs not enough in production?

kubectl logs reads the log file on the node where the container ran. When that pod is deleted or the node terminates, the log files are gone. For short-lived pods, crash loops, or Spot node interruptions, you will lose the exact logs you need at the worst possible time. A log forwarder like Fluent Bit ships logs off the node in near real-time so they survive pod and node lifecycle events.

Why is Fluent Bit deployed as a DaemonSet?

Container logs are written to files on the node where the container runs. To read those files, the log forwarder must run on the same node. A DaemonSet schedules one pod per node, so Fluent Bit automatically covers every node in the cluster, including new nodes added by autoscaling.

Fluent Bit vs Fluentd: which should I use on EKS?

Fluent Bit is the right choice for most EKS clusters. It is written in C, has a much smaller memory footprint per node than Fluentd, and AWS maintains an EKS-optimised image and Helm chart for it. Fluentd is worth considering only if you need a plugin that Fluent Bit does not support, which is uncommon for standard EKS logging use cases.

Should EKS logs go to CloudWatch Logs, OpenSearch, or S3?

For most teams starting out, CloudWatch Logs is the right first destination. It integrates with CloudWatch Alarms, supports log queries via CloudWatch Insights, and requires no additional infrastructure. Add S3 as a secondary destination when you have compliance retention requirements. Move to OpenSearch when you need full-text search, dashboards, or query volumes that make CloudWatch Insights costs prohibitive.

What IAM permissions does Fluent Bit need on EKS?

Fluent Bit needs logs:CreateLogGroup, logs:CreateLogStream, logs:PutLogEvents, and logs:DescribeLogStreams to write to CloudWatch Logs. Grant these via IRSA (IAM Roles for Service Accounts) and attach the IAM role to the Fluent Bit service account. Avoid using node instance profiles, which grant the same permissions to every process on the node.

Last verified: 4 April 2026 Cloud services change frequently. Verify details against official documentation before making infrastructure decisions.