About Kubernetes configuration: ConfigMap

In this article, we are going to talk about serveral different ways to create and update ConfigMap in Kubernetes.

Creating ConfigMap

Create from folder

kubectl create folder-cm --from-file <folder_name>

# Create with folder
$ kubectl create folder-cm --from-file config
$ kubectl get cm/folder-cm -o yaml
apiVersion: v1
data:
  config.txt: |
    Test=test
  test.txt: |
    DONT=KNOW
kind: ConfigMap
metadata:
  creationTimestamp: "2022-05-02T15:53:44Z"
  name: folder-cm
  namespace: default
  resourceVersion: "41428"
  uid: 9f71a652-2350-4d55-be6e-5ef468cd45f8

Create from file

💡Can create with multiple files, if create with all the files in the same folder, it’s same with creating from folder:

## Create with file
$ kubectl create cm file-cm --from-file config/config.txt --from-file config/test.txt
$ kubectl get cm/file-cm -o yaml

apiVersion: v1
data:
  config.txt: |
    Test=test
  test.txt: |
    DONT=KNOW
kind: ConfigMap
metadata:
  creationTimestamp: "2022-05-02T16:03:36Z"
  name: file-cm
  namespace: default
  resourceVersion: "42433"
  uid: 17766fc1-cfd5-4b95-b00f-296844f23407

The name of the file will be the key of key-value pair in ConfigMap while the content of the file will be the value.

If we want some specific keys, we can create the ConfigMap with given key:

$ kubectl create cm key-cm --from-file=key=config.txt
$ kubectl get cm/key-cm -o yaml
apiVersion: v1
data:
  key: |
    DB_URL=localhost:3306
    DB_USERNAME=postgres
kind: ConfigMap
metadata:
  creationTimestamp: "2022-05-03T15:16:25Z"
  name: key-cm
  namespace: default
  resourceVersion: "63721"
  uid: eb3c93ca-fcfc-42bd-82ce-2d8cddbfc900

Create from key-value pairs

$ kubectl create cm value-cm --from-literal=Test=test
$ kubectl get cm/value-cm -o yaml
apiVersion: v1
data:
  Test: test
kind: ConfigMap
metadata:
  creationTimestamp: "2022-05-03T15:26:07Z"
  name: value-cm
  namespace: default
  resourceVersion: "64703"
  uid: f5431bb3-6276-48e8-85e0-33b0ef1a7243

Create from env file

đź’ˇenv file can be .env file and .txt file and so on ..

$ kubectl create cm env-cm --from-env-file=config.txt
$ kubectl get cm/env-cm -o yaml
apiVersion: v1
data:
  DB_URL: localhost:3306
  DB_USERNAME: postgres
kind: ConfigMap
metadata:
  creationTimestamp: "2022-05-03T15:30:49Z"
  name: env-cm
  namespace: default
  resourceVersion: "65184"
  uid: 60dba811-8fa9-4bb1-9e7b-9d45a02009eb

Comparing with Create from file, in which same file is used to create configMap, instead of using filename/given key as key, and file content as value, creating from env file reads the file first, and uses key-value pairs in the file to create ConfigMap.

Using configMap

Use ConfigMap as files in pod

Mount configMap

ConfigMap can be mounted to pods as volumes. Here is the yaml file of a pod who mounts all the ComfigMaps we’ve created above (folder-cm, file-cm, value-cm, env-cm):

# file-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  name: file-pod
spec:
  containers:
  - image: nginx
    name: pod
    ports:
    - containerPort: 80
    resources: {}
    volumeMounts:
    - name: folder
      mountPath: "folder"
    - name: file
      mountPath: "file"
    - name: value
      mountPath: "value"
    - name: env
      mountPath: "env"
  dnsPolicy: ClusterFirst
  restartPolicy: Always
  volumes:
  - name: folder
    configMap:
      name: folder-cm
  - name: file
    configMap:
      name: file-cm
  - name: value
    configMap:
      name: value-cm
  - name: env
    configMap:
      name: env-cm
status: {}

Then if we log into the pod and check what is going on inside:

$ kubectl exec -it pod/file-pod -- bash
# Inside pod
$ tree -L 2
...
|-- env
|   |-- DB_URL -> ..data/DB_URL
|   `-- DB_USERNAME -> ..data/DB_USERNAME
|-- file
|   |-- config.txt -> ..data/config.txt
|   `-- test.txt -> ..data/test.txt
|-- folder
|   |-- config.txt -> ..data/config.txt
|   `-- test.txt -> ..data/test.txt
|-- value
|   `-- Test -> ..data/Test
...

Apparently, no matter from what we created the ConfigMap, the pod who mounted them would generate a file for each key-value pairs in ConfigMap using key as filename and value as content.

đź’ˇMounting ConfigMap to pods as volumes is setting to Read-Only by default.

Mount ConfigMap with SubPath

It’s mentioned that when we mount a ConfigMap to a pod, a folder named as what is specified in spec.containers.volumeMounts.mountPath in the yaml file will be created (if there’s already a folder with same name, the original folder will be covered and the files in this folder cannot been seen anymore.)

But there will be a situation that we only want to create a file based on the ConfigMap and put it into a folder which already exited.

For example, folder /etc/nginx/conf.d which already has a file default.conf for Nginx. Instead of making this file disappear, we only want to add a new file into this folder. Then we need SubPath:

# file-pod2
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  name: file-pod2
spec:
  containers:
  - image: nginx
    name: pod
    ports:
    - containerPort: 80
    resources: {}
    volumeMounts:
    - name: folder
      mountPath: "folder"
    - name: file
      mountPath: "/etc/nginx/conf.d/config.txt"
      subPath: "config.txt"
    - name: value
      mountPath: "value"
    - name: env
      mountPath: "env"
  dnsPolicy: ClusterFirst
  restartPolicy: Always
  volumes:
  - name: folder
    configMap:
      name: folder-cm
  - name: file
    configMap:
      name: file-cm
  - name: value
    configMap:
      name: value-cm
  - name: env
    configMap:
      name: env-cm
status: {}

According to documentation of Kubernetes 1.24:

The volumeMounts.subPathproperty specifies a sub-path inside the referenced volume instead of its root.

So, consider each volume is a folder, then for file volume, it contains config.txt file and text.txt file. Then we can use relative path “config.txt” to point to config.txt file of file volume in spec.containers.volumeMounts.subPath property.

If we apply this yaml file again and log into the new pod:

$ ls
... env folder value ...

Other folders are still here but just file folder is gone. Then we can check /etc/nginx/conf.d folder.

$ ls /etc/nginx/conf.d
config.txt  default.conf
$ cat /etc/nginx/conf.d/config.txt
Test=test

default.conf file is still here and only config.txt file which is specified in subPath is create in this folder.

Update ConfigMap

Now, let’s see what will happen if we try to modify a ConfigMap. We edit two ConfigMaps: file-cm and value-cm:

#file-cm
apiVersion: v1
data:
  config.txt: |
    Test=file-modified
  test.txt: |
    DONT=KNOW
kind: ConfigMap
metadata:
  creationTimestamp: "2022-05-02T16:03:36Z"
  name: file-cm
  namespace: default
  resourceVersion: "281558"
  uid: 17766fc1-cfd5-4b95-b00f-296844f23407

#value-cm
apiVersion: v1
data:
  Test: value-modified
kind: ConfigMap
metadata:
  creationTimestamp: "2022-05-03T15:26:07Z"
  name: value-cm
  namespace: default
  resourceVersion: "317489"
  uid: f5431bb3-6276-48e8-85e0-33b0ef1a7243

Then we can log into file-pod2, which we created in previous section, and check what happened after we modified the ConfigMap:

$ kubectl exec -it pod/file-pod2 -- bash
# Content has been updated
$ cat value/Test
value-modified

# Content remains the same
$ cat /etc/nginx/conf.d/config.txt
Test=test

According to the result, after a ConfigMap is changed, the container who mounted this ConfigMap as volume will be updated as well. But there are several exceptions.

One of these exceptions is that if the container uses the ConfigMap as SubPath volume, then it won’t receive the update.

đź’ˇConfigMap can be set immutable, immutable ConfigMap will not be watched constantly, which can reduce load on kube-apiserver.

apiVersion: v1
kind: ConfigMap
metadata:
  ...
data:
  ...
immutable: true

Use ConfigMap as environment variables in pod

Using specific values in ConfigMap as environment variables

In order to use specific values in ConfigMap as environment variables in container, we used spec.containers.env.valueFrom.configMapKeyRef:

# env-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  name: env-pod
spec:
  containers:
  - image: nginx
    name: pod
    ports:
    - containerPort: 80
    resources: {}
    env:
    - name: FOLDER_KEY
      valueFrom:
        configMapKeyRef:
          name: folder-cm
          key: test.txt
    - name: ENV_KEY
      valueFrom:
        configMapKeyRef:
          name: env-cm
          key: DB_URL
  dnsPolicy: ClusterFirst
  restartPolicy: Always
status: {}

Then create a new pod with this yaml file and checke the environment variables of it’s container:

$ kubectl exec pod/env-pod -- env
...
FOLDER_KEY=DONT=KNOW

ENV_KEY=localhost:3306
...

The name of the variables are what we specified with spec.containers.env.name. Then it will search in the target ConfigMap which we specified with spec.env.valueFrom.configMapKeyRef.name in order to find the target key-value pair whose key equals to what we specified in spec.env.valueFrom.configMapKeyRef.key. The value of this environment variable will be the value of target key-value pair.

Using all values in ConfigMap as environment variables

Here we will use spec.containers.envFrom.configMapRef to inject all the key-value pairs of a ConfigMap to a container as environment variables:

# all-env-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  name: all-env-pod
spec:
  containers:
  - image: nginx
    name: pod
    ports:
    - containerPort: 80
    resources: {}
    envFrom:
    - configMapRef:
        name: env-cm
  dnsPolicy: ClusterFirst
  restartPolicy: Always
status: {}

After this yaml file is applied, the environment variables of the new containers are:

$ kubectl exec pod/all-env-pod -- env
...
DB_URL=localhost:3306
DB_USERNAME=postgres
...

All the key-value pairs of ConfigMap are used as environment variable and their keys are the variable names while their values are the variable values.

Update ConfigMap

Same as what we mentioned in previous section - Use ConfigMap as files in pod, we are going to modified the ConfigMap and see what will happen to corresponding containers:

## env-cm
data:
  DB_URL: localhost:3306_modified
  DB_USERNAME: postgres
kind: ConfigMap
metadata:
  creationTimestamp: "2022-05-03T15:30:49Z"
  name: env-cm
  namespace: default
  resourceVersion: "65184"
  uid: 60dba811-8fa9-4bb1-9e7b-9d45a02009eb

We changed the value of DB_URL, then environment variables of the previous two containers are:

$ kubectl exec pod/env-pod -- env
...
FOLDER_KEY=DONT=KNOW

ENV_KEY=localhost:3306
...

$ kubectl exec pod/all-env-pod -- env
...
DB_URL=localhost:3306
DB_USERNAME=postgres
...

The environment variables who are created from ConfigMap remain the same. This is the other situation that we talked about before in which corresponding containers will not receive the update of the ConfigMap.