Table of Contents
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:
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
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:
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.