Chapter 18. EnvVar Configuration

In this EnvVar Configuration pattern, we look into the simplest way to configure applications. For small sets of configuration values, the easiest way to externalize configuration is by putting them into universally supported environment variables. We see different ways of declaring environment variables in Kubernetes but also the limitations of using environment variables for complex configurations.

Problem

Every nontrivial application needs some configuration for accessing data sources, external services, or production-level tuning. And we knew well before The Twelve-Factor App manifesto that it is a bad thing to hardcode configurations within the application. Instead, the configuration should be externalized so that we can change it even after the application has been built. That provides even more value for containerized applications that enable and promote sharing of immutable application artifacts. But how can this be done best in a containerized world?

Solution

The Twelve-Factor App manifesto recommends using environment variables for storing application configurations. This approach is simple and works for any environment and platform. Every operating system knows how to define environment variables and how to propagate them to applications, and every programming language also allows easy access to these environment variables. It is fair to claim that environment variables are universally applicable. When using environment variables, a typical usage pattern is to define hardcoded default values during build time, which we then can overwrite at runtime. Let’s see some concrete examples of how this works in Docker and Kubernetes.

For Docker images, environment variables can be defined directly in Dockerfiles with the ENV directive. You can define them line by line or all in a single line, as shown in Example 18-1.

Example 18-1. Example Dockerfile with environment variables
FROM openjdk:11
ENV PATTERN "EnvVar Configuration"
ENV LOG_FILE "/tmp/random.log"
ENV SEED "1349093094"

# Alternatively:
ENV PATTERN="EnvVar Configuration" LOG_FILE=/tmp/random.log SEED=1349093094
...

Then a Java application running in such a container can easily access the variables with a call to the Java standard library, as shown in Example 18-2.

Example 18-2. Reading environment variables in Java
public Random initRandom() {
  long seed = Long.parseLong(System.getenv("SEED"));
  return new Random(seed);      1
}
1

Initializes a random-number generator with a seed from an EnvVar

Directly running such an image will use the default hardcoded values. But in most cases, you want to override these parameters from outside the image.

When running such an image directly with Docker, environment variables can be set from the command line by calling Docker, as in Example 18-3.

Example 18-3. Set environment variables when starting a Docker container
docker run -e PATTERN="EnvVarConfiguration" 
           -e LOG_FILE="/tmp/random.log" 
           -e SEED="147110834325" 
           k8spatterns/random-generator:1.0

For Kubernetes, these types of environment variables can be set directly in the Pod specification of a controller like Deployment or ReplicaSet (as in Example 18-4).

Example 18-4. Deployment with environment variables set
apiVersion: v1
kind: Pod
metadata:
  name: random-generator
spec:
  containers:
  - image: k8spatterns/random-generator:1.0
    name: random-generator
    env:
    - name: LOG_FILE
      value: /tmp/random.log                   1
    - name: PATTERN
      valueFrom:
        configMapKeyRef:                       2
          name: random-generator-config        3
          key: pattern                         4
    - name: SEED
      valueFrom:
        secretKeyRef:                          5
          name: random-generator-secret
          key: seed
1

EnvVar with a literal value

2

EnvVar from a ConfigMap

3

ConfigMap’s name

4

Key within the ConfigMap to look for the EnvVar value

5

EnvVar from a Secret (lookup semantic is the same as for a ConfigMap)

In such a Pod template, you not only can attach values directly to environment variables (like for LOG_FILE), but also can use a delegation to Kubernetes Secrets (for sensitive data) and ConfigMaps (for non-sensitive configuration). The advantage of ConfigMap and Secret indirection is that the environment variables can be managed independently from the Pod definition. Secret and ConfigMap and their pros and cons are explained in detail in Chapter 19, Configuration Resource.

In the preceding example, the SEED variable comes from a Secret resource. While that is a perfectly valid use of Secret, it is also important to point out that environment variables are not secure. Putting sensitive, readable information into environment variables makes this information easy to read, and it may even leak into logs.

Discussion

Environment variables are easy to use, and everybody knows about them. This concept maps smoothly to containers, and every runtime platform supports environment variables. But environment variables are not secure, and they are good only for a decent number of configuration values. And when there are a lot of different parameters to configure, the management of all these environment variables becomes unwieldy.

In these cases, many people use an extra level of indirection and put configuration into various configuration files, one for each environment. Then a single environment variable is used to select one of these files. Profiles from Spring Boot are an example of this approach. Since these profile configuration files are typically stored within the application itself, which is within the container, it couples the configuration tightly with the application. This often leads to configuration for development and production ending up side by side in the same Docker image, which requires an image rebuild for every change in either environment. All that shows us that environment variables are suitable for small sets of configurations only.

The patters Configuration Resource, Immutable Configuration, and Configuration Template described in the following chapters are good alternatives when more complex configuration needs come up.

Environment variables are universally applicable, and because of that, we can set them at various levels. This option leads to fragmentation of the configuration definitions and makes it hard to track for a given environment variable where it comes from. When there is no central place where all environments variables are defined, it is hard to debug configuration issues.

Another disadvantage of environment variables is that they can be set only before an application starts, and we cannot change them later. On the one hand, it’s a drawback that you can’t change configuration “hot” during runtime to tune the application. However, many see this as an advantage, as it promotes immutability even to the configuration. Immutability here means you throw away the running application container and start a new copy with a modified configuration, very likely with a smooth Deployment strategy like rolling updates. That way, you are always in a defined and well-known configuration state.

Environment variables are simple to use, but are applicable mainly for simple use cases and have limitations for complex configuration requirements. The next patterns show how to overcome those limitations.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset