Kubernetes ConfigMaps and Secrets Part 2

This is a continuation from Part 1.

While environment variables are great for small bits of information, sometimes you might have a lot of data that needs to be passed to your app. A common solution is to group this data into a file, and have your app read from this file.

Kubernetes let’s you mount ConfigMaps and Secrets as files. Unlike environment variables, if these files change the new files will be pushed to the running pods without needing a restart, so they are a lot more powerful. You can also map multiple files to a single ConfigMap or Secret, and mount them all at once as a directory!

Read from a file

Let’s modify our code from Part 1 to read from files instead of environment variables.

First, create a two subdirectories called “config” and “secret”, and config.json and secret.json files with the data we used in part one:

mkdir config && mkdir secret
echo '{"LANGUAGE":"English"}' > ./config/config.json
echo '{"API_KEY":"123-456-789"}' > ./secret/secret.json

Now, edit the code so it reads in these files instead of environment variables.

https://gist.github.com/thesandlord/6e297d7ceb807e6f0243255ab7885d83#file-file-js
Important: This code will re-read the file on every request. If you read the file once when the program starts, updates to the file will not be captured and you will need to restart the container to update the files. A common pattern that is more efficient that re-reading the file every time is to use a file watcher that will reload the file only when it changes.

Mount the files using Docker volumes

The first step is to test everything works by using Docker volumes to simulate the ConfigMaps and Secrets.

Rebuild the container:

docker build -t envtest .

After building the container, run it with the following command:

docker run -p 3000:3000 -ti \
-v $(pwd)/secret/:/usr/src/app/secret/ \
-v $(pwd)/config/:/usr/src/app/config/ \
envtest

This will run the Docker container, and mount the data folders into the container.

Note: the onbuild container that is used for the base image puts the code into the /usr/src/app directory. That is why the folders are mounted there.

If you visit localhost:3000 the container should be serving traffic.

Because the file is mounted into the container and the code re-reads the file on every request, you can change the files and see the change without restarting anything!

For example:

echo '{"LANGUAGE":"Spanish"}' > ./config/config.json

Creating the ConfigMap and Secret

Create the Secret from the file

kubectl create secret generic my-secret \
--from-file=./secret/secret.json

Then create the ConfigMap from the other file

kubectl create configmap my-config --from-file=./config/config.json

You can check that these are created with the following commands:

kubectl get secret

And

kubectl get configmap

Using ConfigMaps and Secrets as files

The final step is using creating a deployment that will use the ConfigMap and Secret as a file instead of an environment variable


Note: Remember you need to update and push the Docker image in your registry to use the new code. You can do that with this command if you are using Google Cloud.

gcloud container builds submit --tag gcr.io/$(gcloud config list project --format=text | awk '{print $2}')/envtest:file .

In your deployment YAML, you can use the ConfigMap and Secret as a volume. This will automatically mount them as a directory in your container, just like with Docker.

https://gist.github.com/thesandlord/6e297d7ceb807e6f0243255ab7885d83#file-file-yaml

Notice that the Secret and ConfigMap are mapped to volumes, and these volumes are used in the volumeMount section of the container spec.

Updating with no downtime!

Unlike Part 1’s environment variables, volumes can be dynamically remounted into running containers. This means that new ConfigMap and Secret values will be available to the container without needed to restart the running processes.

For example, change the language to Klingon and update the ConfigMap.

echo '{"LANGUAGE":"Klingon"}' > ./config/config.json
kubectl create configmap my-config \
--from-file=./config/config.json \
-o yaml --dry-run | kubectl replace -f -

In a few seconds (up to a minute depending on the cache) the new file will be pushed to the running containers automatically!

If you want to update the secret, you can follow the same process:

echo '{"API_KEY":"0987654321"}' > ./secret/secret.json
kubectl create secret generic my-secret \
--from-file=./secret/secret.json \
-o yaml --dry-run | kubectl replace -f -
Note: These updates are eventually consistent. It is possible that some containers get the update before others, creating inconsistencies in your deployments. If this is an issue, do not use the auto-update feature. Instead, create a new ConfigMap or Secret, and update or create a new Deployment to use the new one.