Kubernetes Logging Guide:
The Basics

October 31, 2022

IT system logs are essential in helping engineers to perform the following tasks::

  • Troubleshooting application performance bottlenecks
  • Determining the root cause of availability issues
  • Debugging software code
  • Investigating security breaches
  • Planning infrastructure capacity
  • Maintaining audit compliance requirements
  • … and more.

Modern cloud-based, distributed applications depend heavily on container technology. These applications often rely on Kubernetes for container orchestration that yields seamless scaling and robust fault tolerance. With this shift in deployment infrastructure from bare metal to containers, logging and monitoring techniques have changed.

Storing logs in containers or virtual machines isn’t practical because both are ephemeral. Therefore, a different approach is necessary to capture logs from Kubernetes-hosted applications.

This article is Part One of a guide that covers Kubernetes Logging fundamentals. In Part One, we will introduce the Kubernetes logging architectures and explain the node-level logging patterns in detail.

Kubernetes Logging Basics

In a production Kubernetes environment, hundreds or even thousands of containers can exist in different states—running,, stopping, restarting, or terminating—at any given moment.

Logging drivers

Kubernetes doesn’t provide a native solution for storing logs. However, it ships with some logging drivers to facilitate storing and aggregating logs. By default, Kubernetes uses the json-file driver, which formats all log entries to JSON and internally caches them. The syslog and journald logging drivers help writing to Linux log systems. Other logging drivers like FluentD, AWS CloudWatch, and GCP Logs facilitate writing logs to external log aggregator services.

Logs from Kubernetes system components

Kubernetes logs can come from the container orchestration system and from containerized applications.

Kubernetes system components include the Kubernetes Scheduler, kube-proxy, kubelet, and the container runtime. The Kubernetes scheduler and kube-proxy run inside a container and always write logs to the local /var/log directory irrespective of the driver used. The kubelet and the container runtime write logs to the systemd journal if it’s present or to the /var/log directory if it’s not.

Node-level logging

In a Kubernetes environment, the node or the cluster manages the application logs. In node-level logging, the Pod writes application logs to the node where it’s running. The container engine redirects any application log message to the stdout or stderr streams. The node’s logging driver picks up the messages and writes them to the appropriate log file. If there’s no driver configured, logging defaults to json-file.

You can use the following command to check which logs use the default driver.

kubectl logs <pod_name>

Node-level logging, however, has a significant shortcoming: A Pod’s logs are lost if the node goes down or Kubernetes evicts that Pod.

Cluster-level logging

Cluster-level logging involves a centralized logging service aggregating logs from all the nodes. This solves the problem of losing node-level logs by pushing all the node-level logs to a backend service. There are two ways to achieve cluster-level logging.

The first method uses a node-level agent configured as a DaemonSet. A DaemonSet is a Kubernetes feature where every node (or some of them) runs a copy of the Pod. In a later section of this guide, we will show how to use this approach for effective logging.

The second method uses a sidecar pattern, where every Pod contains a sidecar container that captures logs and sends those logs to an external service.

Capturing Logs Using a Node-level Logging Agent

A node-level logging agent is an always-on service that runs in a container in a separate Pod.

A node-level logging agent is useful when your environment matches the following conditions:

  • There is a resource constraint in the nodes. A single node-level agent can capture logs from all the application Pods in that node, ensuring the resource consumption remains low.
  • There is common log storage for all the Pods.
  • There is a need for basic multi-tenant isolation without complex requirements. A separate resource allocation isn’t possible here since one logging agent caters to all application Pods.

Using a node-level logging agent has several advantages:

  • It is easy to set up.
  • Resource usage is low.
  • Since the agent uses the same mechanism as the default logging configuration, the logs are available through the kubectl command.
  • There’s no need to change anything in the application Pods.

Configuring the node-level logging agent in Kubernetes

The Kubernetes node-level agent uses the DaemonSet pattern. A DaemonSet runs a copy of a specific Pod in every cluster node (or some of its nodes). Every time you add a new node to the cluster, that Pod gets added to the new node as well. When you remove a node (or the cluster scales down), Kubernetes automatically garbage-collects that Pod. The DaemonSet feature allows you to run a Pod to collect logs from all the Pods in a node and send those to a log management service.

To capture logs from a specific directory, developers can use an open-source log collection agent like Fluentd. Fluentd has a GitHub repository that contains sample configurations for different logging backends.

Let’s assume that we want to send the DaemonSet Pod’s logs collected by Fluentd to an Elasticsearch backend. We would use this sample configuration from the GitHub repo.

The first step in deploying this configuration is to create a service account for the Fluentd logging agent. The code snippet below shows how to do this.

apiVersion: v1
kind: ServiceAccount
metadata:
  name: fluentd-logger-daemonset
  namespace: logging
  labels:
    app: fluentd-logger-daemonset

Save the file as fluentd_service_account.yaml, and run the command below to create the service account.

kubectl apply -f fluentd_service_account.yaml

Next, create the configuration for the DaemonSet. You can use the following configuration file to do this.

apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
  name: fluentd
  namespace: kube-system
  labels:
    k8s-app: fluentd-elasticsearch-daemonset
spec:
  template:
    metadata:
      labels:
        k8s-app: fluentd-elasticsearch-daemonset
    spec:
      containers:
      - name: fluentd
        image: fluent/fluentd-kubernetes-daemonset:elasticsearch
        env:
        - name:  FLUENT\_ELASTICSEARCH\_HOST
          value: "<elasticsearch-host-url>"
        - name:  FLUENT\_ELASTICSEARCH\_PORT
          value: "<elasticsearch-host-port>"
        volumeMounts:
        - name: logdir
          mountPath: /var/log
      volumes:
      - name: logdir
        hostPath:
          path: /var/log

The important part of this specification is the kind attribute that specifies the Pod will run as a DaemonSet. The spec template field is mandatory and defines the base image for creating the DaemonSet. This configuration uses the default fluentd image as the base image. Like all Kubernetes configuration files, it’s necessary to specify the apiVersion and the metadata keys. The application container logs are mounted to the /var/log directory.

You can save this configuration as fluentd_daemonset_account.yaml and run the command below to create the Pod in all the cluster nodes.

kubectl apply -f fluentd-daemonset.yaml

This will initiate a node-level logging agent Pod in all your cluster nodes. It’s possible to run the Pod only on specific nodes by using the nodeSelector parameter (under spec.template.spec). Without this parameter, Kubeternetes creates the Pod in every node.

Conclusion

Troubleshooting containerized microservices can be tricky. Gone are the days when an engineer could log into each physical server and tail logs from the command shell. Compared to logs from other clustered applications, Kubernetes logs—both from the orchestrator and the application—are challenging to manage because Pods are ephemeral. That’s why you must collect logs from every node and send those to a central location outside the cluster for persistence and later analysis.

As we covered in this guide, a DaemonSet pattern allows quick and easy implementation of node-level logging agents in Kubernetes. You don’t have to make any changes to the application, and its resource usage is low. However, this solution doesn’t offer separate log storage or comprehensive multi-tenant isolation features for each application. For those functionalities, you would need to use a sidecar-based approach.

In Part Two of our guide, we will discuss the sidecar implementation, specifically its pros and cons, and common use cases.

Log Everything, Answer Anything – For Free

Falcon LogScale Community Edition (previously Humio) offers a free modern log management platform for the cloud. Leverage streaming data ingestion to achieve instant visibility across distributed systems and prevent and resolve incidents.

Falcon LogScale Community Edition, available instantly at no cost, includes the following:

  • Ingest up to 16GB per day
  • 7-day retention
  • No credit card required
  • Ongoing access with no trial period
  • Index-free logging, real-time alerts and live dashboards
  • Access our marketplace and packages, including guides to build new packages
  • Learn and collaborate with an active community

Get Started Free