# ConfigMap Conundrum: Subtleties of Dynamic Updates in Kubernetes Configurations

ConfigMaps are a powerful tool in Kubernetes that help you *(dynamically)* update configuration within a Kubernetes deployment. In this tutorial, you will learn how to change the configuration for a running application. More importantly, you will understand the subtle variation between ConfigMaps mounted as Volumes and ConfigMaps defined as environment variables.

## Objectives

* Update configuration via a ConfigMap mounted as a Volume
    
* Update environment variables of a Pod via a ConfigMap
    
* Learn the difference between the two
    

## Prerequisites

1. A local Kubernetes cluster such as [minikube](https://minikube.sigs.k8s.io/docs/start/), [kind](https://kind.sigs.k8s.io/), [K3s](https://k3s.io/) or [microk8s](https://microk8s.io/).
    
2. The [kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl) command line tool to interact with the Kubernetes cluster.
    

## Update configuration via a ConfigMap mounted as a Volume

Use the `kubectl create configmap` command to create a ConfigMap from [literal values](https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/#create-configmaps-from-literal-values):

```plaintext
kubectl create configmap sport --from-literal=sport=football
```

Below is an example of a Deployment manifest with the ConfigMap *sport* mounted as a [volume](https://kubernetes.io/docs/concepts/storage/volumes/) into the Pod's only container.

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: configmap-volume
  labels:
    app.kubernetes.io/name: configmap-volume
spec:
  replicas: 3
  selector:
    matchLabels:
      app.kubernetes.io/name: configmap-volume
  template:
    metadata:
      labels:
        app.kubernetes.io/name: configmap-volume
    spec:
      containers:
        - name: alpine
          image: alpine:3
          command:
            - /bin/sh
            - -c
            - while true; do echo "$(date) My preferred sport is $(cat /etc/config/sport)";
              sleep 10; done;
          ports:
            - containerPort: 80
          volumeMounts:
            - name: config-volume
              mountPath: /etc/config
      volumes:
        - name: config-volume
          configMap:
            name: sport
```

Create the Deployment:

```plaintext
kubectl apply -f https://raw.githubusercontent.com/adityasamant25/courses/main/kubernetes/configmaps/examples/deployment-with-configmap-as-volume.yaml
```

Check the Deployment:

```plaintext
kubectl get deployment configmap-volume
```

You should see an output similar to:

```plaintext
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
configmap-volume   3/3     3            3           19s
```

Check the pods for this Deployment (matching by [selector):](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/)

```plaintext
kubectl get pods --selector=app.kubernetes.io/name=configmap-volume
```

You should see an output similar to:

```plaintext
NAME                                READY   STATUS    RESTARTS   AGE
configmap-volume-6b976dfdcf-qxvbm   1/1     Running   0          72s
configmap-volume-6b976dfdcf-skpvm   1/1     Running   0          72s
configmap-volume-6b976dfdcf-tbc6r   1/1     Running   0          72s
```

On each node where one of these Pods is running, the `kubelet` fetches the data for that ConfigMap and translates it to files in a local volume. The `kubelet` then mounts that volume into the container, as specified in the Pod template. The code running in that container loads the information from the file and uses it to print a report to stdout. You can check this report by viewing the logs for one of the Pods in that Deployment:

```plaintext
kubectl logs deployments/configmap-volume
```

You should see an output similar to:

```plaintext
Found 3 pods, using pod/configmap-volume-76d9c5678f-x5rgj
Thu Jan  4 14:06:46 UTC 2024 My preferred sport is football
Thu Jan  4 14:06:56 UTC 2024 My preferred sport is football
Thu Jan  4 14:07:06 UTC 2024 My preferred sport is football
Thu Jan  4 14:07:16 UTC 2024 My preferred sport is football
Thu Jan  4 14:07:26 UTC 2024 My preferred sport is football
```

Edit the ConfigMap:

```plaintext
kubectl edit configmap sport
```

In the editor that appears, change the value of key `sport` from `football` to `cricket`. Save your changes. The kubectl tool updates the ConfigMap accordingly (if you see an error, try again).

Here's an example of how that manifest could look after you edit it:

```yaml
apiVersion: v1
data:
  sport: cricket
kind: ConfigMap
# You can leave the existing metadata as they are.
# The values you'll see won't exactly match these.
metadata:
  creationTimestamp: "2024-01-04T14:05:06Z"
  name: sport
  namespace: default
  resourceVersion: "1743935"
  uid: 024ee001-fe72-487e-872e-34d6464a8a23
```

You should see the following output:

```plaintext
configmap/sport edited
```

Tail (follow the latest entries in) the logs of one of the pods that belongs to this Deployment:

```plaintext
kubectl logs -f deployments/configmap-volume
```

After few seconds, you should see the log output change as follows:

```plaintext
Thu Jan  4 14:11:36 UTC 2024 My preferred sport is football
Thu Jan  4 14:11:46 UTC 2024 My preferred sport is football
Thu Jan  4 14:11:56 UTC 2024 My preferred sport is football
Thu Jan  4 14:12:06 UTC 2024 My preferred sport is cricket
Thu Jan  4 14:12:16 UTC 2024 My preferred sport is cricket
```

> When you have a ConfigMap that is mapped into a running Pod using either a `configMap` volume or a `projected` volume, and you update that ConfigMap, the running Pod sees the update almost immediately. However, your application only sees the change if it is written to either poll for changes, or watch for file updates. An application that loads its configuration once at startup will not notice a change.
> 
> The total delay from the moment when the ConfigMap is updated to the moment when new keys are projected to the Pod can be as long as kubelet sync period. Also check [Mounted ConfigMaps are updated automatically.](https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/#mounted-configmaps-are-updated-automatically)

## Update environment variables of a Pod via a ConfigMap

Use the *kubectl create configmap* command to create a ConfigMap from [literal values:](https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/#create-configmaps-from-literal-values)

```plaintext
kubectl create configmap fruits --from-literal=fruits=apples
```

Below is an example of a Deployment manifest with an environment variable configured via the ConfigMap `fruits`.

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: configmap-env-var
  labels:
    app.kubernetes.io/name: configmap-env-var
spec:
  replicas: 3
  selector:
    matchLabels:
      app.kubernetes.io/name: configmap-env-var
  template:
    metadata:
      labels:
        app.kubernetes.io/name: configmap-env-var
    spec:
      containers:
        - name: alpine
          image: alpine:3
          env:
            - name: FRUITS
              valueFrom:
                configMapKeyRef:
                  key: fruits
                  name: fruits
          command:
            - /bin/sh
            - -c
            - while true; do echo "$(date) The basket is full of $FRUITS";
                sleep 10; done;
          ports:
            - containerPort: 80
```

Create the Deployment:

```plaintext
kubectl apply -f https://raw.githubusercontent.com/adityasamant25/courses/main/kubernetes/configmaps/examples/deployment-with-configmap-as-envvar.yaml
```

Check the Deployment:

```plaintext
kubectl get deployment configmap-env-var
```

You should see an output similar to:

```plaintext
NAME                READY   UP-TO-DATE   AVAILABLE   AGE
configmap-env-var   3/3     3            3           7s
```

Check the pods for this Deployment (matching by [selector):](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/)

```plaintext
kubectl get pods --selector=app.kubernetes.io/name=configmap-env-var
```

You should see an output similar to:

```plaintext
NAME                                 READY   STATUS    RESTARTS   AGE
configmap-env-var-59cfc64f7d-74d7z   1/1     Running   0          46s
configmap-env-var-59cfc64f7d-c4wmj   1/1     Running   0          46s
configmap-env-var-59cfc64f7d-dpr98   1/1     Running   0          46s
```

The key-value pair in the ConfigMap is configured as an environment variable in the container of the Pod. Check this by viewing the logs of the Pod.

```plaintext
kubectl logs deployment/configmap-env-var
```

You should see an output similar to:

```plaintext
Found 3 pods, using pod/configmap-env-var-7c994f7769-l74nq
Thu Jan  4 16:07:06 UTC 2024 The basket is full of apples
Thu Jan  4 16:07:16 UTC 2024 The basket is full of apples
Thu Jan  4 16:07:26 UTC 2024 The basket is full of apples
```

Edit the ConfigMap:

```plaintext
kubectl edit configmap fruits
```

In the editor that appears, change the value of key `fruits` from `apples` to `mangoes`. Save your changes. The kubectl tool updates the ConfigMap accordingly (if you see an error, try again).

Here's an example of how that manifest could look after you edit it:

```yaml
apiVersion: v1
data:
  fruits: mangoes
kind: ConfigMap
# You can leave the existing metadata as they are.
# The values you'll see won't exactly match these.
metadata:
  creationTimestamp: "2024-01-04T16:04:19Z"
  name: fruits
  namespace: default
  resourceVersion: "1749472"
```

You should see the following output:

```plaintext
configmap/fruits edited
```

Tail the logs of the Deployment and observe the output for few seconds:

```plaintext
kubectl logs deployment/configmap-env-var
```

Notice that the output remains `unchanged`, even though you edited the ConfigMap:

```plaintext
Thu Jan  4 16:12:56 UTC 2024 The basket is full of apples
Thu Jan  4 16:13:06 UTC 2024 The basket is full of apples
Thu Jan  4 16:13:16 UTC 2024 The basket is full of apples
Thu Jan  4 16:13:26 UTC 2024 The basket is full of apples
```

> Although the value of the key inside the ConfigMap has changed, the environment variable in the Pod still shows the earlier value. This is because environment variables for a process running inside a Pod are ***not*** updated when the source data changes; if you wanted to force an update, you would need to have Kubernetes replace your existing Pods. The new Pods would then run with the updated information.

You can trigger that replacement. Perform a rollout for the Deployment, using [kubectl rollout:](https://kubernetes.io/docs/reference/kubectl/generated/kubectl_rollout/)

```plaintext
# Trigger the rollout
kubectl rollout restart deployment configmap-env-var

# Wait for the rollout to complete
kubectl rollout status deployment configmap-env-var --watch=true
```

Next, check the Deployment:

```plaintext
kubectl get deployment configmap-env-var
```

You should see an output similar to:

```plaintext
NAME                READY   UP-TO-DATE   AVAILABLE   AGE
configmap-env-var   3/3     3            3           12m
```

Check the Pods:

```plaintext
kubectl get pods --selector=app.kubernetes.io/name=configmap-env-var
```

The rollout caused Kubernetes to make a new ReplicaSet for the Deployment; that means that the existing Pods were terminated, and new ones have been created. You should finally see an output similar to:

```plaintext
NAME                                 READY   STATUS        RESTARTS   AGE
configmap-env-var-6d94d89bf5-2ph2l   1/1     Running       0          13s
configmap-env-var-6d94d89bf5-74twx   1/1     Running       0          8s
configmap-env-var-6d94d89bf5-d5vx8   1/1     Running       0          11s
```

View the logs for a Pod in this Deployment:

```plaintext
kubectl logs deployment/configmap-env-var
```

You should see an output similar to the below:

```plaintext
Found 3 pods, using pod/configmap-env-var-6d9ff89fb6-bzcf6
Thu Jan  4 16:30:35 UTC 2024 The basket is full of mangoes
Thu Jan  4 16:30:45 UTC 2024 The basket is full of mangoes
Thu Jan  4 16:30:55 UTC 2024 The basket is full of mangoes
```

This demonstrates the scenario of updating environment variables in a Pod that are derived from a ConfigMap. Changes to the ConfigMap values are applied to the Pod during the subsequent rollout. If Pods get created for another reason, such as scaling up the Deployment, then the new Pods also use the latest configuration values; if you don't trigger a rollout, then you might find that your app is running with a mix of old and new environment variable values.

## Summary

Changes to a ConfigMap mounted as a Volume on a Pod are available seamlessly after the subsequent kubelet sync.

Changes to a ConfigMap that configures environment variables for a Pod are available after the subsequent rollout for the Pod.

## Cleaning up

Delete the resources created during the tutorial:

```plaintext
kubectl delete deployment configmap-volume configmap-env-var
kubectl delete configmap sport fruits
```
