Containers & Kubernetes

Configuration Management


Learning Objectives

  • You know how to manage configuration data with ConfigMaps and Secrets in Kubernetes.

Kubernetes provides the possibility to manage configuration data separately from the container images and code. This is done using ConfigMaps and Secrets. ConfigMaps are used for non-sensitive configuration information, while Secrets are used for sensitive data.

ConfigMaps

A ConfigMap is a key-value store for holding non-sensitive configuration data. It allows you to separate environment-specific settings from your application logic. For example, you might put application settings, feature flags, or connection strings in a ConfigMap rather than hard-coding them or baking them into your container image. This way, you can update configuration independently of your application code.

The primary way to use ConfigMaps is to load them as environment variables. To try this out, create a file called minikube-demo-configmap.yaml to the k8s folder with the following content.

apiVersion: v1
kind: ConfigMap
metadata:
  name: minikube-demo-configmap
data:
  WELCOME_MESSAGE: "Hello from config!"
  FEATURE_FLAG: "true"

Then, modify the minikube-demo-server-deployment.yaml file to load the ConfigMap into environment variables. This is done by adding an envFrom property with a configMapRef that has a name that refers to the name of the above ConfigMap (minikube-demo-configmap).

apiVersion: apps/v1
kind: Deployment
metadata:
  name: minikube-demo-server-deployment
  labels:
    app: minikube-demo-server
spec:
  replicas: 1
  selector:
    matchLabels:
      app: minikube-demo-server
  template:
    metadata:
      labels:
        app: minikube-demo-server
    spec:
      containers:
        - name: minikube-demo-server
          image: minikube-demo-server:1.0
          imagePullPolicy: Never
          ports:
            - containerPort: 8000
          envFrom:
            - configMapRef:
                name: minikube-demo-configmap

Now, the image minikube-demo-server:1.0 will have access to the WELCOME_MESSAGE and FEATURE_FLAG environment variables. You can access these environment variables in your application code to read the configuration data.

Modify server/app.js to read the WELCOME_MESSAGE environment variables and use that as the message as follows.

import { Hono } from "@hono/hono";
import { cors } from "@hono/hono/cors";
import { logger } from "@hono/hono/logger";

const app = new Hono();

const message = Deno.env.get("WELCOME_MESSAGE") || "Hello world!";

app.use("/*", cors());
app.use("/*", logger());

app.get("/", (c) => c.json({ message }));

export default app;

And then, build and deploy the updated server image to Minikube. Let’s use the tag 1.1 for the updated image. In the folder server, run the following command.

minikube image build -t minikube-demo-server:1.1 .

Then, modify the minikube-demo-server-deployment.yaml file to use the new image version 1.1.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: minikube-demo-server-deployment
  labels:
    app: minikube-demo-server
spec:
  replicas: 1
  selector:
    matchLabels:
      app: minikube-demo-server
  template:
    metadata:
      labels:
        app: minikube-demo-server
    spec:
      containers:
        - name: minikube-demo-server
          image: minikube-demo-server:1.1
          imagePullPolicy: Never
          ports:
            - containerPort: 8000
          envFrom:
            - configMapRef:
                name: minikube-demo-configmap

And finally, apply the changes to the Minikube cluster.

kubectl apply -f k8s/

The output should be something like the following:

configmap/minikube-demo-configmap created
deployment.apps/minikube-demo-server-deployment configured
deployment.apps/minikube-demo-server-fetcher-deployment unchanged
service/minikube-demo-server-fetcher-service unchanged
service/minikube-demo-server-service unchanged

Now, if you access the service minikube-demo-server-service, you should see the message Hello from config!.

$ minikube service minikube-demo-server-service --url
http://192.168.49.2:31843
$ curl http://192.168.49.2:31843
{"message":"Hello from config!"}%

You can also login to the running Pod and check the environment variables. Let’s first get the name of the running Pod.

$ kubectl get pods
NAME                                                       READY   STATUS    RESTARTS   AGE
minikube-demo-server-deployment-55f4fc7b57-62fmv           1/1     Running   0          9m38s
minikube-demo-server-fetcher-deployment-6548f75dd4-gm27q   1/1     Running   0          3h32m

The newer Pod is minikube-demo-server-deployment-55f4fc7b57-62fmv. Let’s login to this Pod.

$ kubectl exec -it minikube-demo-server-deployment-55f4fc7b57-62fmv -- /bin/sh

Now, the terminal should be inside the Pod. You can check the environment variables using the env command. There are also plenty of other environment variables as you notice — these are provided by Kubernetes.

$ env
...
WELCOME_MESSAGE=Hello from config!
Feature flags

One of the configuration values in the ConfigMap above was a “feature flag”. Feature flags (or feature toggles) are a technique that allows toggling features on and off in application without changing the code. This can be useful for testing new features, rolling out features gradually, or enabling features for specific users.

There are also third-party tools that provide feature flag management as a service. As a starting point, see e.g. OpenFeature.


Loading Exercise...

Secrets

Secrets are similar to ConfigMaps but intended for confidential data such as passwords, API keys, and so on. To demonstrate the use of Secrets, let’s create a Secret with a username and password for a database. Create a file called minikube-demo-secret.yaml in the k8s folder with the following content.

apiVersion: v1
kind: Secret
metadata:
  name: minikube-demo-secret
type: Opaque
stringData:
  APP_USERNAME: supersecretusername
  APP_PASSWORD: supersecretpassword

Now, we can modify the minikube-demo-server-deployment.yaml file to use the Secret. We can pass the Secret values as environment variables in the same way as we did with the ConfigMap — however, instead of using configMapRef, we use secretRef. In the following, we read both the ConfigMap and the Secret values.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: minikube-demo-server-deployment
  labels:
    app: minikube-demo-server
spec:
  replicas: 1
  selector:
    matchLabels:
      app: minikube-demo-server
  template:
    metadata:
      labels:
        app: minikube-demo-server
    spec:
      containers:
        - name: minikube-demo-server
          image: minikube-demo-server:1.1
          imagePullPolicy: Never
          ports:
            - containerPort: 8000
          envFrom:
            - configMapRef:
                name: minikube-demo-configmap
            - secretRef:
                name: minikube-demo-secret

Then, apply the changes to the Minikube cluster.

kubectl apply -f k8s/

Now, the image minikube-demo-server:1.1 will have access to the APP_USERNAME and APP_PASSWORD environment variables. You can access these environment variables in your application code to read the configuration data.

When we login to the pod, we can see the environment variables in the pod.

$ kubectl exec -it (pod name) -- /bin/sh
$ env
...
APP_PASSWORD=supersecretpassword
APP_USERNAME=supersecretusername

But, wait… What’s the actual difference between a ConfigMap and a Secret?T he key difference is that Secrets are always stored base64-encoded within Kubernetes etcd storage. When using stringData, Kubernetes automatically encodes values to base64 before storing them.

It is also possible to use the data field and manually encode secrets to base64 before entering them to the configuration file. This can, for example, be done with the base64 command as follows.

$ echo -n 'newsupersecretusername' | base64
bmV3c3VwZXJzZWNyZXR1c2VybmFtZQ==
$ echo -n 'newsupersecretpassword' | base64
bmV3c3VwZXJzZWNyZXRwYXNzd29yZA==

Now, with the base64 encoded data, we can modify the minikube-demo-secret.yaml file to use the base64-encoded values.

apiVersion: v1
kind: Secret
metadata:
  name: minikube-demo-secret
type: Opaque
data:
  APP_USERNAME: bmV3c3VwZXJzZWNyZXR1c2VybmFtZQ==
  APP_PASSWORD: bmV3c3VwZXJzZWNyZXRwYXNzd29yZA==

And then, apply the changes to the Minikube cluster.

kubectl apply -f k8s/

For more information, including mechanisms for safely using Secrets, refer to the Kubernetes documentation on Secrets.

Loading Exercise...

Versioning and updating

Note that updating a ConfigMap or Secret doesn’t automatically trigger a Deployment rollout. Here are some strategies for handling updates:

  • Immutable ConfigMaps/Secrets: Kubernetes allows marking ConfigMaps and Secrets as immutable. If you set immutable: true in the metadata, the data cannot be changed afterwards. This forces creating a new ConfigMap/Secret for any change.

  • Naming Convention for Versioning: When creating new versions of the configuration, adopt a naming convention for the configurations. As an example, use a suffix or timestamp (app-config-v2 or app-config-20250401) in the configuration filename. Then, when updating, update the deployment to point to the new config when.

  • External Version Control: Store ConfigMaps and Secrets to version control with encryption, e.g., using SealedSecrets. This way, changes can be tracked and rolled back. See also vals.

Loading Exercise...