Ultimate guide on Kubernetes ConfigMaps & Secrets with examples

In this tutorial we will learn how to set environment variables for a container using Kubernetes ConfigMaps. It is a good practice to make container images as reusable as possible. The same image should be able to be used for development, staging, and production. But it is possible these images are dependent on certain environment variables for example, the database URL to connect to so testing and versioning get riskier and more complicated if images need to be recreated for each new environment.

To overcome this problem, we can write our applications in such a way that the environment-specific data is provided to the application by the environment it is being deployed into. There are multiple ways to provide environment-specific configuration data to our application:

  • Provide command-line arguments to the Pods.
  • Provide environment variables to the Pods.
  • Mount configuration files in the containers.

 

Overview on Kubernetes ConfigMaps

  • A ConfigMap allows us to define application-related data.
  • It decouples the application data from the application so that the same application can be ported across different environments.
  • It also provides a way to inject customized data into running services from the same container image.
  • The key thing is that the ConfigMap is combined with the Pod right before it is run. This means that the container image and the Pod definition itself can be reused across many apps by just changing the ConfigMap that is used.

 

How to create ConfigMap

ConfigMaps can be created through a literal value or from a file or all the files in a directory, we will explore both these options in this tutorial. The command syntax to create a ConfigMap has the following format:

kubectl create configmap <map-name> <data-source>

Here, <map-name> is the name you want to assign to the ConfigMap and <data-source> is the directory, file, or literal value to draw the data from. The <data source> corresponds to a key-value pair in the ConfigMap, where:

  • Key is the filename or the key you provided on the command line
  • Value is the file content or the literal value you provided on the command line

When creating ConfigMaps, you can use a combination of all the options mentioned here:

$ kubectl create configmap my-config
➥   --from-file=foo.json         --> A single file
➥   --from-file=bar=foobar.conf  --> A file stored under a custom key
➥   --from-file=config-opts/     --> A whole directory
➥   --from-literal=some=thing    --> A literal value

 

Example-1: Create ConfigMap using file

Create data for the Pod

Following is a configuration file which we want to place in our nginx container. So I have created a separate file with the content to be added:

[root@controller ~]# cat nginx-custom-config.conf
server {
   listen       8888;
   server_name  localhost;
   location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
   }
}

 

Create ConfigMap

Next we will create our ConfigMap. Here cm is a short abbrevation for configmap.

[root@controller ~]# kubectl create cm nginx-cm --from-file nginx-custom-config.conf
configmap/nginx-cm created

 

Inspect ConfigMap content

So, our ConfigMap is successfully created, lets check the status:

[root@controller ~]# kubectl get cm
NAME       DATA   AGE
nginx-cm   1      6s

Let's see what the Kubernetes ConfigMap object looks like. Enter the kubectl get command as follows:

Ultimate guide on Kubernetes ConfigMaps & Secrets with examples

As you can see in the third line of the preceding output, the ConfigMap is created and the literal value we entered is available as a key-value pair in the data section of the ConfigMap. Here, the name of the file, nginx-custom-config.conf, becomes the key under the data section, and the entire file payload is the value of the key.

 

Create Pod using the ConfigMap

Now that we have defined our ConfigMap, the next step is to mount it onto a container. Create a YAML file named nginx-cm.yml to be used as our Pod configuration using the following content:

[root@controller ~]# cat nginx-cm.yml
apiVersion: v1
kind: Pod
metadata:
  name: nginx-cm
  labels:
    role: web
spec:
  containers:
  - name: nginx-cm
    image: nginx
    volumeMounts:
    - name: conf
      mountPath: /etc/nginx/conf.d
  volumes:
  - name: conf
    configMap:
      name: nginx-cm
      items:
      - key: nginx-custom-config.conf
        path: default.conf

First, let's focus on the volumes section in the preceding file. In this section, we are instructing Kubernetes to define a volume from our ConfigMap named conf. Here we want to include the key nginx-custom-config.conf and the entry's value should be stored at default.conf
Secondly, in the volumeMounts section, we are defining that Kubernetes should mount the volume in the /etc/nginx/conf.d directory. SO the value from the key would be stored under /etc/nginx/conf.d/default.conf

NOTE:

The name field under the volume and volumeMounts sections has to be the same so that Kubernetes can identify which volume is associated with which volumeMounts.

Use the following command to start a Pod using the YAML file we just created:

[root@controller ~]# kubectl create -f nginx-cm.yml
pod/nginx-cm created

 

Verify ConfigMap data inside Pod's container

Next let's connect to this Pod and verify if our content was added into /etc/nginx/conf.d/default.conf:

[root@controller ~]# kubectl exec -it nginx-cm -- cat /etc/nginx/conf.d/default.conf
server {
   listen       8888;
   server_name  localhost;
   location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
   }
}

So we have just successfully defined a ConfigMap and mounted it as a file in a Pod that printed the name of the file.

 

Example-2: Create ConfigMap using command line arguments

Assign variable as command line argument

In this section we will assign single environment variables using ConfigMap by providing the key value pair as command line arguments instead of a file. Here color is the KEY while red is the VALUE of the KEY.

[root@controller ~]# kubectl create cm myconfig --from-literal=color=red
configmap/myconfig created

 

Inspect the ConfigMap

Once we create the ConfigMap, let's confirm that it is created by issuing a command to get all the ConfigMaps in the default namespace:

[root@controller ~]# kubectl get cm
NAME       DATA   AGE
myconfig   1      9s
nginx-cm   1      66m

Let's see what the Kubernetes ConfigMap object looks like. Enter the kubectl get command as follows:

Ultimate guide on Kubernetes ConfigMaps & Secrets with examples

As you can see in the highlighted section of the preceding output, the ConfigMap is created and the literal value we entered is available as a key-value pair in the data section of the ConfigMap.

 

Create Pod using ConfigMap

Now, we will create a YAML file named test-cm-pod.yml to create a Pod into which we will inject fields from our ConfigMap as an environment variable. Using your favorite text editor, create a YAML file with the following content:

[root@controller ~]# cat test-cm-pod.yml
apiVersion: v1
kind: Pod
metadata:
  name: test-pod
spec:
  containers:
  - name: test
    image: nginx
    env:
    - name: COLOR
      valueFrom:
        configMapKeyRef:
          name: myconfig
          key: color
  restartPolicy: Never

Here,

  • We are setting the environment variable called COLOR.
  • Instead of setting a fixed value, we are initializing it from a ConfigMap key.
  • The name of the ConfigMap you’re referencing is myconfig
  • We are setting the variable to whatever is stored under this key of color in the ConfigMap.

 

Verify environment variable inside the container

Once the Pod is up and running, we can connect to the Pod's container and make sure that our variable is SET:

[root@controller ~]# kubectl exec -it test-pod -- env | grep COLOR
COLOR=red

So we have just successfully defined an environment variable for the Pod using command line arguments with ConfigMap.

 

Using Kubernetes Secrets to pass sensitive data to containers

  • While ConfigMaps are great for most configuration data, there is certain data that is extra-sensitive. This can include passwords, security tokens, or other types of private keys. Collectively, we call this type of data “secrets.”
  • Secrets enable container images to be created without bundling sensitive data. This allows containers to remain portable across environments.
  • Unlike a ConfigMap, Kubernetes Secrets are intended to store a small amount (1 MB for a Secret) of sensitive data.
  • A Secret is base64-encoded, so we cannot treat it as secure.
  • It can also store binary data such as a public or private key.
  • Kubernetes ensures that Secrets are passed only to the nodes that are running the Pods that need the respective Secrets.

 

Example-1: Defining a Secret from Literal Values

In this exercise, we will define a Secret from a literal value and load it as secrets volume type. Secrets volumes are managed by the kubelet and are created at Pod creation time. Secrets are stored on tmpfs volumes (aka RAM disks), and as such are not written to disk on nodes. Each data element of a secret is stored in a separate file under the target mount point specified in the volume mount.

Now this literal value maybe something like a password to your internal database. Since we are creating this Secret from a literal value, it would be categorized as a generic Secret:

[root@controller ~]# kubectl create secret generic test-secret  --from-literal=user=deepak --from-literal=password=test1234
secret/test-secret created

Once we define our Secret, we can use the Kubernetes get command to obtain more details about it:

[root@controller ~]# kubectl get secrets test-secret -o yaml
apiVersion: v1
data:
  password: dGVzdDEyMzQ=
  user: ZGVlcGFr
kind: Secret
metadata:
  creationTimestamp: "2021-01-10T08:46:27Z"
  managedFields:
  - apiVersion: v1
    fieldsType: FieldsV1
    fieldsV1:
      f:data:
        .: {}
        f:password: {}
        f:user: {}
      f:type: {}
    manager: kubectl-create
    operation: Update
    time: "2021-01-10T08:46:27Z"
  name: test-secret
  namespace: default
  resourceVersion: "1733368"
  selfLink: /api/v1/namespaces/default/secrets/test-secret
  uid: ca4f49d2-1bd3-4b2d-9673-9b80baf5b352
type: Opaque

As you can see under data section, the provided user and password are not in plain text format any more as we had with ConfigMap and instead they are base64 encoded for better security. But then these can also be decoded:

~]# echo dGVzdDEyMzQ= | base64 --decode
test1234

Now that our Secret is created, we will mount it as an environment variable in a Pod. To create a Pod, make a YAML file secret-busybox.yml with the following content:

apiVersion: v1
kind: Pod
metadata:
  name: secret-busybox
spec:
  containers:
  - name: secret-busybox
    image: busybox
    command:
     - sleep
     - "3600"
    volumeMounts:
    - mountPath: /data
      name: secret
  volumes:
  - name: secret
    secret:
      secretName: test-secret

We provide the secret volume details under volumes section which we created in the previous step.

Now, let's use the YAML configuration to create a Pod and check the status:

[root@controller ~]# kubectl create -f secret-busybox.yml
pod/secret-busybox created

[root@controller ~]# kubectl get pods secret-busybox
NAME             READY   STATUS    RESTARTS   AGE
secret-busybox   1/1     Running   0          61s

Now that the Pod is in RUnning state, we can connect to this Pod and verify the content of the variables which are mounted on /data volume:

[root@controller ~]# exec -it secret-busybox -- /bin/sh
/ #
/ # cat data/password
test1234
/ # cat data/user
deepak
/ #

 

Example-2: Defining a Secret from a file

In this section we will create a secret to store a TLS key and certificate for application running inside the container. I have already created a self signed certificate using openssl:

[root@controller ~]# ls server.*
server.crt  server.csr  server.key

Create a secret named secret-tls using the create secret command and this secret data can be exposed to Pods using the secrets volume type:

[root@controller ~]# kubectl create secret generic secret-tls --from-file=server.crt --from-file=server.key
secret/secret-tls created

The secret-tls secret has been created with two data elements. Run the following command to get details:

[root@controller ~]# kubectl describe secret secret-tls
Name:         secret-tls
Namespace:    default
Labels:       <none>
Annotations:  <none>

Type:  Opaque

Data
====
server.crt:  1927 bytes
server.key:  3311 bytes

Next we can create our Pod where we will mount this secret volume:

[root@controller ~]# cat secret-tls-pod.yml
apiVersion: v1
kind: Pod
metadata:
  name: secret-tls-pod
spec:
  containers:
    - name: secret-tls-pod
      image: busybox
      command:
      - sleep
      - "3600"
      volumeMounts:
      - name: tls-certs
        mountPath: "/tls"
        readOnly: true
  volumes:
    - name: tls-certs
      secret:
        secretName: secret-tls

In the preceding Pod specification, note that volumes are mounted the same way as we mounted the earlier ConfigMap. In the volumes section, we are instructing Kubernetes to define a volume from our Secret which we have created earlier with the certificates. In the volumeMounts section, we are defining the specific path on which Kubernetes should mount the volume

Check the status of the Pod:

[root@controller ~]# kubectl get pods secret-tls-pod
NAME             READY   STATUS    RESTARTS   AGE
secret-tls-pod   1/1     Running   0          57s

Now we can connect to this Pod and look out for our certificates under /tls:

[root@controller ~]# kubectl exec -it secret-tls-pod -- /bin/sh
/ # ls -l /tls/
total 0
lrwxrwxrwx    1 root     root            17 Jan 10 10:53 server.crt -> ..data/server.crt
lrwxrwxrwx    1 root     root            17 Jan 10 10:53 server.key -> ..data/server.key

As you can see, the container is displaying the contents of the Secret mounted onto the Pod.

 

Conclusion

In this Kubernetes Tutorial, we have seen the different ways that Kubernetes provides to associate environment-specific data with our applications running as containers. Kubernetes provides ways to store sensitive data as Secrets and normal application data as ConfigMaps. We have also seen how to create ConfigMaps and Secrets and associate them with our containers via CLI. Associating data with containers enables us to use the same container across different environments in our IT systems (for example, in test and production). Separating configuration from application code will make your applications more reliable and reusable.

Leave a Comment

Please use shortcodes <pre class=comments>your code</pre> for syntax highlighting when adding code.