# kube-fluentd-operator
**Repository Path**: mirrors_vmware/kube-fluentd-operator
## Basic Information
- **Project Name**: kube-fluentd-operator
- **Description**: Auto-configuration of Fluentd daemon-set based on Kubernetes metadata
- **Primary Language**: Unknown
- **License**: BSD-2-Clause
- **Default Branch**: main
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2020-08-19
- **Last Updated**: 2026-04-04
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# kube-fluentd-operator (KFO)
[](https://goreportcard.com/report/github.com/vmware/kube-fluentd-operator)
## Overview
Kubernetes Fluentd Operator (KFO) is a Fluentd config manager with batteries included, config validation, no needs to restart, with sensible defaults and best practices built-in. Use Kubernetes labels to filter/route logs per namespace!
_kube-fluentd-operator_ configures Fluentd in a Kubernetes environment. It compiles a Fluentd configuration from configmaps (one per namespace) - similar to how an Ingress controller would compile nginx configuration from several Ingress resources. This way only one instance of Fluentd can handle all log shipping for an entire cluster and the cluster admin does NOT need to coordinate with namespace admins.
Cluster administrators set up Fluentd only once and namespace owners can configure log routing as they wish. _KFO_ will re-configure Fluentd accordingly and make sure logs originating from a namespace will not be accessible by other tenants/namespaces.
_KFO_ also extends the Fluentd configuration language making it possible to refer to pods based on their labels and the container name pattern. This enables for very fined-grained targeting of log streams for the purpose of pre-processing before shipping. Writing a custom processor, adding a new Fluentd plugin, or writing a custom Fluentd plugin allow KFO to be extendable for any use case and any external logging ingestion system.
Finally, it is possible to ingest logs from a file on the container filesystem. While this is not recommended, there are still legacy or misconfigured apps that insist on logging to the local filesystem.
## Try it out
The easiest way to get started is using the Helm chart. Official images are not published yet, so you need to pass the image.repository and image.tag manually:
```bash
git clone git@github.com:vmware/kube-fluentd-operator.git
helm install kfo ./kube-fluentd-operator/charts/log-router \
--set rbac.create=true \
--set image.tag=v1.18.1 \
--set image.repository=vmware/kube-fluentd-operator
```
Alternatively, deploy the Helm chart from a Github release:
```bash
CHART_URL='https://github.com/vmware/kube-fluentd-operator/releases/download/v1.18.1/log-router-0.4.0.tgz'
helm install kfo ${CHART_URL} \
--set rbac.create=true \
--set image.tag=v1.18.1 \
--set image.repository=vmware/kube-fluentd-operator
```
Then create a namespace `demo` and a configmap describing where all logs from `demo` should go to. The configmap must contain an entry called "fluent.conf". Finally, point the kube-fluentd-operator to this configmap using annotations.
```bash
kubectl create ns demo
cat > fluent.conf << EOF
@type null
EOF
# Create the configmap with a single entry "fluent.conf"
kubectl create configmap fluentd-config --namespace demo --from-file=fluent.conf=fluent.conf
# The following step is optional: the fluentd-config is the default configmap name.
# kubectl annotate namespace demo logging.csp.vmware.com/fluentd-configmap=fluentd-config
```
In a minute, this configuration would be translated to something like this:
```xml
@type null
```
Even though the tag `**` was used in the `` directive, the kube-fluentd-operator correctly expands this to `demo.**`. Indeed, if another tag which does not start with `demo.` was used, it would have failed validation. Namespace admins can safely assume that they has a dedicated Fluentd for themselves.
All configuration errors are stored in the annotation `logging.csp.vmware.com/fluentd-status`. Try replacing `**` with an invalid tag like 'hello-world'. After a minute, verify that the error message looks like this:
```bash
# extract just the value of logging.csp.vmware.com/fluentd-status
kubectl get ns demo -o jsonpath='{.metadata.annotations.logging\.csp\.vmware\.com/fluentd-status}'
bad tag for : hello-world. Tag must start with **, $thisns or demo
```
When the configuration is made valid again the `fluentd-status` is set to "".
To see kube-fluentd-operator in action you need a cloud log collector like logz.io, papertrail or ELK accessible from the K8S cluster. A simple logz.io configuration looks like this (replace TOKEN with your customer token):
```xml
@type logzio_buffered
endpoint_url https://listener.logz.io:8071?token=$TOKEN
```
## Build
Get the code using `go get` or git clone this repo:
```bash
go get -u github.com/vmware/kube-fluentd-operator/config-reloader
cd $GOPATH/src/github.com/vmware/kube-fluentd-operator
# build a base-image
cd base-image && make build-image
# build helm chart
cd charts/log-router && make helm-package
# build the daemon
cd config-reloader
make install
make build-image
# run with mock data (loaded from the examples/ folder)
make run-once-fs
# run with mock data in a loop (may need to ctrl+z to exit)
make run-loop-fs
# inspect what is generated from the above command
ls -l tmp/
```
### Project structure
- `charts/log-router`: Builds the Helm chart
- `base-image`: Builds a Fluentd 1.2.x image with a curated list of plugins
- `config-reloader`: Builds the daemon that generates fluentd configuration files
### Config-reloader
This is where interesting work happens. The [dependency graph](config-reloader/godepgraph.png) shows the high-level package interaction and general dataflow.
- `config`: handles startup configuration, reading and validation
- `datasource`: fetches Pods, Namespaces, ConfigMaps from Kubernetes
- `fluentd`: parses Fluentd config files into an object graph
- `processors`: walks this object graph doing validations and modifications. All features are implemented as chained `Processor` subtypes
- `generator`: serializes the processed object graph to the filesystem for Fluentd to read
- `controller`: orchestrates the high-level `datasource` -> `processor` -> `generator` pipeline.
### How does it work
It works be rewriting the user-provided configuration. This is possible because _kube-fluentd-operator_ knows about the kubernetes cluster, the current namespace and
also has some sensible defaults built in. To get a quick idea what happens behind the scenes consider this configuration deployed in a namespace called `monitoring`:
```xml
@type parser
@type apache2
@type detect_exceptions
language python
@type es
```
It gets processed into the following configuration which is then fed to Fluentd:
```xml
@type record_transformer
enable_ruby true
kubernetes_pod_label_values ${record["kubernetes"]["labels"]["app"]&.gsub(/[.-]/, '_') || '_'}.${record["kubernetes"]["labels"]["server"]&.gsub(/[.-]/, '_') || '_'}
@type rewrite_tag_filter
key kubernetes_pod_label_values
pattern ^(.+)$
tag ${tag}._labels.$1
@type record_modifier
remove_keys kubernetes_pod_label_values
@type parser
@type apache2
@type rewrite_tag_filter
invert true
key _dummy
pattern /ZZ/
tag 3bfd045d94ce15036a8e3ff77fcb470e0e02ebee._proc.${tag}
@type detect_exceptions
remove_tag_prefix 3bfd045d94ce15036a8e3ff77fcb470e0e02ebee
stream container_info
@type es
```
## Configuration
### Basic usage
To give the illusion that every namespace runs a dedicated Fluentd the user-provided configuration is post-processed. In general, expressions starting with `$` are macros that are expanded. These two directives are equivalent: ``, ``. Almost always, using the `**` is the preferred way to match logs: this way you can reuse the same configuration for multiple namespaces.
### The admin namespace
Kube-fluentd-operator defines one namespace to be the _admin_ namespace. By default this is set to `kube-system`. The _admin_ namespace is treated differently. Its configuration is not processed further as it is assumed only the cluster admin can manipulate resources in this namespace. If you don't plan to use any of the advanced features described bellow, you can just route all logs from all namespaces using this snippet in the _admin_ namespace:
```xml
@type ...
# destination configuration omitted
```
`**` in this context is not processed and it means _literally_ everything.
Fluentd assumes it is running in a distro with systemd and generates logs with these Fluentd tags:
- `systemd.{unit}`: the journal of a systemd unit, for example `systemd.docker.service`
- `docker`: all docker logs, not containers. If systemd is used, the docker logs are in `systemd.docker.service`
- `k8s.{component}`: logs from a K8S component, for example `k8s.kube-apiserver`
- `kube.{namespace}.{pod_name}.{container_name}`: a log originating from (namespace, pod, container)
As the _admin_ namespace is processed first, a match-all directive would consume all logs and any other namespace configuration will become irrelevant (unless `` is used).
A recommended configuration for the _admin_ namespace is this one (assuming it is set to `kube-system`) - it captures all but the user namespaces' logs:
```xml
# all k8s-internal and OS-level logs
# destination config omitted...
```
Note the `` and the `$labels` macro to define parsing at the namespace level. For example, the config-reloader container uses the `logfmt` format. This makes it easy to use structured logging and ingest json data into a remote log ingestion service.
```xml
@type parser
reserve_data true
@type logfmt
@type logzio_buffered
# destination config omitted
```
The above config will pipe all logs from the pods labelled with `app=log-router` through a [logfmt](https://github.com/vmware/kube-fluentd-operator/blob/master/base-image/plugins/parser_logfmt.rb) parser before sending them to logz.io. Again, this configuration is valid in any namespace. If the namespace doesn't contain any `log-router` components then the `` directive is never activated. The `_container` is sort of a "meta" label and it allows for targeting the log stream of a specific container in a multi-container pod.
If you use [Kubernetes recommended labels](https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/) for the pods and deployments, then KFO will rewrite `.` characters into `_`.
For example, let's assume the following labels exist in the fluentd-config in the `testing` namespace:
This label `$labels(_container=nginx-ingress-controller)` will filter by container name pattern. The label will convert to this for example: `kube.testing.*.nginx-ingress-controller._labels.*.*.`
This label `$labels(app.kubernetes.io/name=nginx-ingress, _container=nginx-ingress-controller)` converts to this `kube.testing.*.nginx-ingress-controller._labels.*.nginx_ingress`.
This label `$labels(app.kubernetes.io/name=nginx-ingress)` converts to this `$labels(kube.testing.*.*._labels.*.nginx_ingress)`.
This fluentd configmap in the `testing` namespace:
```xml
@type concat
timeout_label @DISTILLERY_TYPES
key message
stream_identity_key cont_id
multiline_start_regexp /^(\d{4}-\d{1,2}-\d{1,2} \d{1,2}:\d{1,2}:\d{1,2}|\[\w+\]\s|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b|=\w+ REPORT====|\d{2}\:\d{2}\:\d{2}\.\d{3})/
flush_interval 10
@type relabel
@label @DISTILLERY_TYPES
```
will be rewritten inside of KFO pods as this:
```xml
@type concat
flush_interval 10
key message
multiline_start_regexp /^(\d{4}-\d{1,2}-\d{1,2} \d{1,2}:\d{1,2}:\d{1,2}|\[\w+\]\s|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b|=\w+ REPORT====|\d{2}\:\d{2}\:\d{2}\.\d{3})/
stream_identity_key cont_id
timeout_label @-DISTILLERY_TYPES-0e93f964a5b5f1760278744f1adf55d58d0e78ba
@label @-DISTILLERY_TYPES-0e93f964a5b5f1760278744f1adf55d58d0e78ba
@type relabel
@label @-DISTILLERY_TYPES-0e93f964a5b5f1760278744f1adf55d58d0e78ba
@type null
```
All plugins that change the fluentd tag are disabled for security reasons. Otherwise a rogue configuration may divert other namespace's logs to itself by prepending its name to the tag.
### Ingest logs from a file in the container
The only allowed `` directive is of type `mounted-file`. It is used to ingest a log file from a container on an `emptyDir`-mounted volume:
```xml
@type mounted-file
path /var/log/welcome.log
labels app=grafana, _container=test-container
@type none
```
The `labels` parameter is similar to the `$labels` macro and helps the daemon locate all pods that might log to the given file path. The `` directive is optional and if omitted the default `@type none` will be used. If you know the format of the log file you can explicitly specify it, for example `@type apache2` or `@type json`.
The above configuration would translate at runtime to something similar to this:
```xml
@type tail
path /var/lib/kubelet/pods/723dd34a-4ac0-11e8-8a81-0a930dd884b0/volumes/kubernetes.io~empty-dir/logs/welcome.log
pos_file /var/log/kfotail-7020a0b821b0d230d89283ba47d9088d9b58f97d.pos
read_from_head true
tag kube.kfo-test.welcome-logger.test-container
@type none
```
### Dealing with multi-line exception stacktraces (since v1.3.0)
Most log streams are line-oriented. However, stacktraces always span multiple lines. _kube-fluentd-operator_ integrates stacktrace processing using the [fluent-plugin-detect-exceptions](https://github.com/GoogleCloudPlatform/fluent-plugin-detect-exceptions). If a Java-based pod produces stacktraces in the logs, then the stacktraces can be collapsed in a single log event like this:
```xml
@type detect_exceptions
# you can skip language in which case all possible languages will be tried: go, java, python, ruby, etc...
language java
# The rest of the configuration stays the same even though quite a lot of tag rewriting takes place
@type es
```
Notice how `filter` is used instead of `match` as described in [fluent-plugin-detect-exceptions](https://github.com/GoogleCloudPlatform/fluent-plugin-detect-exceptions). Internally, this filter is translated into several `match` directives so that the end user doesn't need to bother with rewriting the Fluentd tag.
Also, users don't need to bother with setting the correct `stream` parameter. _kube-fluentd-operator_ generates one internally based on the container id and the stream.
### Reusing output plugin definitions (since v1.6.0)
Sometimes you only have a few valid options for log sinks: a dedicated S3 bucket, the ELK stack you manage, etc. The only flexibility you're after is letting namespace owners filter and parse their logs. In such cases you can abstract over an output plugin configuration - basically reducing it to a simple name which can be referenced from any namespace. For example, let's assume you have an S3 bucket for a "test" environment and you use logz.io for a "staging" environment. The first thing you do is define these two output in the _admin_ namespace:
```xml
admin-ns.conf:
@type logzio_buffered
endpoint_url https://listener.logz.io:8071?token=$TOKEN
@type s3
aws_key_id YOUR_AWS_KEY_ID
aws_sec_key YOUR_AWS_SECRET_KEY
s3_bucket YOUR_S3_BUCKET_NAME
s3_region AWS_REGION
@type logzio_buffered
endpoint_url https://listener.logz.io:8071?token=$TOKEN
```
In the above example for the admin configuration, the `match` directive is first defined to direct where to send logs for the `systemd`, `docker`, `kube-system`, and kubernetes control plane components. Below the `match` directive we have defined the `plugin` directives which define the log sinks that can be reused by namespace configurations.
A namespace can refer to the `staging` and `test` plugins oblivious to the fact where exactly the logs end up:
```xml
acme-test.conf
@type test
acme-staging.conf
@type staging
```
kube-fluentd-operator will insert the content of the `plugin` directive in the `match` directive. From then on, regular validation and postprocessing takes place.
### Retagging based on log contents (since v1.12.0)
Sometimes you might need to split a single log stream to perform different processing based on the contents of one of the fields. To achieve this you can use the `retag` plugin that allows to specify a set of rules that match regular expressions against the specified fields. If one of the rules matches, the log is re-emitted with a new namespace-unique tag based on the specified tag.
Logs that are emitted by this plugin can be consequently filtered and processed by using the `$tag` macro when specifiying the tag:
```xml
@type retag
key message
pattern /^(ERROR) .*$/
tag notifications.$1 # refer to a capturing group using $number
key message
pattern /^(FATAL) .*$/
tag notifications.$1
key message
pattern /^(ERROR)|(FATAL) .*$/
tag notifications.other
invert true # rewrite tag when unmatch pattern
# perform some extra processing
# perform different processing
# send to common output plugin
```
_kube-fluentd-operator_ ensures that tags specified using the `$tag` macro never conflict with tags from other namespaces, even if the tag itself is equivalent.
### Sharing logs between namespaces
By default, you can consume logs only from your namespaces. Often it is useful for multiple namespaces (tenants) to get access to the logs streams of a shared resource (pod, namespace). _kube-fluentd-operator_ makes it possible using two constructs: the source namespace expresses its intent to share logs with a destination namespace and the destination namespace expresses its desire to consume logs from a source. As a result logs are streamed only when both sides agree.
A source namespace can share with another namespace using the `@type share` macro:
producer namespace configuration:
```xml
@type copy
@type share
# share all logs matching the labels with the namespace "consumer"
with_namespace consumer
```
consumer namespace configuration:
```xml
# use $from(producer) to get all shared logs from a namespace called "producer"
```
The consuming namespace can use the usual syntax inside the `