14 Config Map and Secret How to Configure and Customize My Application

14 ConfigMap and Secret How to Configure and Customize My Application #

Hello, I’m Chrono.

In the previous two lessons, we learned about the three API objects in Kubernetes: Pods, Jobs, and CronJobs. Although we haven’t covered more advanced objects yet, we can use these objects to orchestrate and run some actual business operations on the cluster.

However, there is an important aspect that we cannot overlook when it comes to running our applications smoothly, and that is configuration management.

You should be familiar with configuration files. Typically, an application will have one to separate the parameters needed at runtime from the code. This allows us to easily adjust and optimize our applications when they are running. Examples include nginx.conf for Nginx, redis.conf for Redis, and my.cnf for MySQL.

In the “Getting Started” guide on container technology, we talked about two ways to manage configuration files. The first is to write a Dockerfile and use the COPY instruction to package the configuration file into the image. The second is to use docker cp or docker run -v at runtime to copy the file from the host into the container.

However, both of these approaches have their drawbacks. The first method fixes the configuration file in the image, making it difficult to modify and inflexible. The second method seems a bit “clumsy” and is not suitable for automated operations and management in a cluster.

Kubernetes has its own solution for this problem, and you probably guessed it right – it uses the YAML language to define API objects and combines them to achieve dynamic configuration.

Today, I will explain two objects in Kubernetes that are specifically used to manage configuration information: ConfigMap and Secret. We can use them to flexibly configure and customize our applications.

ConfigMap/Secret #

First, you need to know that there are many categories of configuration information for applications, but from a data security perspective, they can be divided into two types:

  • One type is plaintext configuration, which is not confidential and can be queried and modified at will, such as service ports, runtime parameters, file paths, etc.
  • The other type is confidential configuration, which needs to be kept confidential due to sensitive information and cannot be viewed randomly, such as passwords, keys, certificates, etc.

Both types of configuration information are essentially strings, but due to security reasons, there are some differences in storage and usage, so Kubernetes defines two API objects, ConfigMap for storing plaintext configuration, and Secret for storing confidential configuration.

What is ConfigMap #

Let’s start with ConfigMap. We can still use the kubectl create command to create a YAML template for it. Note that it has an abbreviated name “cm”, so there is no need to write its full name on the command line:

export out="--dry-run=client -o yaml"        # Define a shell variable
kubectl create cm info $out

The generated template file looks like this:

apiVersion: v1
kind: ConfigMap
metadata:
  name: info

You may be a little surprised that the YAML of ConfigMap is different from what we have learned for Pod and Job. Besides the familiar “apiVersion” and “kind” fields, there are no other fields. Where is the most important field “spec”? This is because ConfigMap stores configuration data, which is static strings and not containers, so they don’t need the “spec” field to indicate runtime “specs”.

Since ConfigMap needs to store data, we need to use another field that has a more explicit meaning, “data”.

To generate a YAML template with the “data” field, you need to add an additional --from-literal parameter after kubectl create, which means generating some data from literals:

kubectl create cm info --from-literal=k=v $out

Note that because the data in ConfigMap is in a Key-Value structure, the --from-literal parameter needs to be in the form of k=v.

Modify the YAML template file by adding more Key-Value pairs, and you will get a more complete ConfigMap object:

apiVersion: v1
kind: ConfigMap
metadata:
  name: info

data:
  count: '10'
  debug: 'on'
  path: '/etc/systemd'
  greeting: |
    say hello to kubernetes.    

Now you can use kubectl apply to hand over this YAML to Kubernetes and let it create the ConfigMap object:

kubectl apply -f cm.yml

After successful creation, you can still use kubectl get and kubectl describe to view the status of ConfigMap:

kubectl get cm
kubectl describe cm info

image

image

You can see that the Key-Value information of the ConfigMap has been stored in the etcd database and can be used by other API objects.

What is Secret #

After understanding the ConfigMap object, it will be much easier to understand the Secret object. It has a similar structure and usage as ConfigMap. However, in Kubernetes, the Secret object is further divided into many categories, such as:

  • Authentication information for accessing private image repositories
  • Credentials for identity recognition
  • Certificates and private keys for HTTPS communication
  • General confidential information (the format is interpreted by the user)

We don’t need the first few types for now, so let’s just focus on the last one. The command to create a YAML template is kubectl create secret generic, and again, you need to use the --from-literal parameter to provide Key-Value pairs:

kubectl create secret generic user --from-literal=name=root $out

The generated Secret object looks like this:

apiVersion: v1
kind: Secret
metadata:
  name: user

data:
  name: cm9vdA==

At first glance, the Secret object is very similar to ConfigMap, except that the “kind” field has changed from “ConfigMap” to “Secret”, and the following is still the “data” field, which contains Key-Value data.

However, since its name is Secret, we cannot store plaintext directly like ConfigMap, and we need to “manipulate the data” a bit. You will notice that the value of “name” is a string of “garbled code”, not the plaintext “root” written in the command line.

This “garbled code” is the difference between Secret and ConfigMap. The purpose is to prevent users from directly seeing the original data, which provides certain confidentiality. However, the approach is very simple. It just uses Base64 encoding, which cannot be considered as real encryption. Therefore, we can completely bypass kubectl and use the Linux utility “base64” to encode the data ourselves and then write it into the YAML file, for example:

echo -n "123456" | base64
MTIzNDU2

Note the echo command in this command, you must add the -n parameter to remove the hidden newline character in the string, otherwise the Base64-encoded string will be incorrect.

Let’s re-edit the Secret’s YAML and add two new Key-Value pairs to it, either with automatic encoding using the --from-literal parameter or manual encoding:

apiVersion: v1
kind: Secret
metadata:
  name: user

data:
  name: cm9vdA==  # root
  pwd: MTIzNDU2   # 123456
  db: bXlzcWw=    # mysql

The subsequent operations of creating and viewing objects are the same as ConfigMap, using kubectl apply, kubectl get, kubectl describe:

kubectl apply  -f secret.yml
kubectl get secret
kubectl describe secret user

image

image

In this way, a Secret object for storing sensitive information is created, and because it is confidential, you cannot directly view the content using kubectl describe, but only see the size of the data. You can compare it with ConfigMap.

How to use #

Now that we have created ConfigMap and Secret objects by writing YAML files, how do we apply them in Kubernetes?

Because ConfigMap and Secret are just strings stored in etcd, if you want them to take effect at runtime, they must be “injected” into the Pod in some way for the application to read them. Kubernetes and Docker are similar in this regard, with two approaches: environment variables and volume mounts.

Let’s start with the simpler approach of environment variables.

How to use ConfigMap/Secret as environment variables #

When we talked about Pods earlier, we mentioned that the “containers” field, which describes the containers in the Pod, has an “env” field that defines the environment variables that the containers can see.

At that time, we only used a simple “value” to hardcode the value of the environment variable in the YAML. In fact, it can also use another field called “valueFrom” to retrieve the value from a ConfigMap or Secret object. This way, the configuration information can be injected into the Pod as environment variables, decoupling the configuration from the application.

Since the “valueFrom” field has a deep nesting level in the YAML, it is best to take a look at the kubectl explain command to understand it:

kubectl explain pod.spec.containers.env.valueFrom

The “valueFrom” field specifies the source of the environment variable value, which can be either “configMapKeyRef” or “secretKeyRef”. Then, you need to further specify the name of the ConfigMap/Secret for your application, as well as the corresponding key. Be careful that the “name” field refers to the name of the API object, not the name of the key-value pair.

I’ll list a Pod that references ConfigMap and Secret objects as an example. To remind you, I have moved the “env” field to the front:

apiVersion: v1
kind: Pod
metadata:
  name: env-pod

spec:
  containers:
  - env:
      - name: COUNT
        valueFrom:
          configMapKeyRef:
            name: info
            key: count
      - name: GREETING
        valueFrom:
          configMapKeyRef:
            name: info
            key: greeting
      - name: USERNAME
        valueFrom:
          secretKeyRef:
            name: user
            key: name
      - name: PASSWORD
        valueFrom:
          secretKeyRef:
            name: user
            key: pwd

    image: busybox
    name: busy
    imagePullPolicy: IfNotPresent
    command: ["/bin/sleep", "300"]

The name of this Pod is “env-pod”, the image is “busybox”, and the command is sleep for 300 seconds. You can use the kubectl exec command to enter the Pod and observe the environment variables during this time.

The “env” field is what you need to focus on. It defines four environment variables: COUNT, GREETING, USERNAME, and PASSWORD.

For plaintext configuration data, COUNT and GREETING reference the ConfigMap object, so they use the “configMapKeyRef” field. The “name” is the name of the ConfigMap object, which is “info” in this case, and the “key” field refers to the count and greeting keys in the “info” object.

Similarly, for sensitive configuration data, USERNAME and PASSWORD reference the Secret object, so they use the “secretKeyRef” field. The “name” is the name of the Secret object, which is “user” in this case, and the “key” field refers to the name and pwd keys in the “user” object.

This explanation might be a bit complicated because the relationship between ConfigMap, Secret, and Pod is not as straightforward as Job/CronJob. So, let me use a diagram to illustrate their reference relationships:

Image

From this diagram, you should be able to see the loose coupling between the Pod and the ConfigMap/Secret. They are not directly nested, but the objects are indirectly referenced using the “KeyRef” field. This way, the same configuration information can be shared among different objects.

Now that you understand how to inject environment variables, let’s create the Pod using kubectl apply and enter the Pod using kubectl exec to verify if the environment variables take effect:

kubectl apply -f env-pod.yml
kubectl exec -it env-pod -- sh

echo $COUNT
echo $GREETING
echo $USERNAME $PASSWORD

Image This screenshot shows the running result of the Pod. It can be seen that the echo command in the Pod does output the configuration information defined in the two YAML files, which proves that the Pod object successfully combined the ConfigMap and Secret objects.

Using ConfigMap/Secret as environment variables is relatively simple. Next, let’s take a look at the second way to load files.

How to use ConfigMap/Secret as Volume #

Kubernetes defines a concept called “Volume” for Pods, which can be translated as “storage volume”. If we consider the Pod as a virtual machine, then the Volume is like the disk inside the virtual machine.

We can “mount” multiple Volumes for the Pod to store data that the Pod can access. This method is somewhat similar to docker run -v. Although the usage is more complicated, it is also more powerful.

Mounting Volumes in a Pod is easy. You just need to add a “volumes” field in the “spec” section and define the name and reference of the ConfigMap/Secret. It is worth noting that a Volume belongs to the Pod, not the container, so it is on the same level as the “containers” field in the “spec”.

Let’s define two Volumes in the Pod, named cm-vol and sec-vol, respectively, which reference the ConfigMap and Secret:

spec:
  volumes:
  - name: cm-vol
    configMap:
      name: info
  - name: sec-vol
    secret:
      secretName: user

After defining the Volumes, we can mount them in the containers. This requires the “volumeMounts” field. As the name suggests, it mounts the defined Volumes to the specified paths in the containers. Therefore, you need to specify the mount path and the name of the Volume using the “mountPath” and “name” fields, respectively.

  containers:
  - volumeMounts:
    - mountPath: /tmp/cm-items
      name: cm-vol
    - mountPath: /tmp/sec-items
      name: sec-vol

After adding the “volumes” and “volumeMounts” fields, the configuration information can be loaded as files. Here is a diagram to illustrate their referencing relationship:

Image

As you can see, the way of mounting Volumes is different from using environment variables. Environment variables directly reference ConfigMap/Secret, while Volume adds an extra step of referencing ConfigMap/Secret using a Volume and then mounting the Volume in the container. It’s a bit of a roundabout way.

The advantage of this approach is that it abstracts all storage using the concept of Volume, which not only supports ConfigMap/Secret but also supports many other forms of storage in the future, such as temporary volumes, persistent volumes, dynamic volumes, snapshot volumes, etc. It has excellent scalability.

Now, let me show you the complete YAML description for the Pod, and then we can create it using kubectl apply:

apiVersion: v1
kind: Pod
metadata:
  name: vol-pod

spec:
  volumes:
  - name: cm-vol
    configMap:
      name: info
  - name: sec-vol
    secret:
      secretName: user

  containers:
  - volumeMounts:
    - mountPath: /tmp/cm-items
      name: cm-vol
    - mountPath: /tmp/sec-items
      name: sec-vol

    image: busybox
    name: busy
    imagePullPolicy: IfNotPresent
    command: ["/bin/sleep", "300"]

After creating it, let’s use kubectl exec to enter the Pod and see how the configuration information is loaded:

kubectl apply -f vol-pod.yml
kubectl get pod
kubectl exec -it vol-pod -- sh

Image

You will see that the ConfigMap and Secret have become directories, and their Key-Value pairs have become individual files. The file names are the Keys.

Because of these differences in format, using ConfigMap/Secret as Volumes is different from using them as environment variables. Environment variables have a simple usage and are more suitable for storing short strings, while Volumes are more suitable for storing configuration files with large amounts of data, which can be loaded as files in the Pod and directly used by applications.

Summary #

Alright, today we learned about two API objects in Kubernetes for managing configuration information: ConfigMap and Secret. They represent plaintext information and sensitive confidential information, respectively, and are stored in etcd. They can be injected into Pods for use when needed.

Let’s summarize today’s main points:

  1. ConfigMap records Key-Value format string data, and the field for the description is “data”, not “spec”.
  2. Secret is similar to ConfigMap, also using “data” to store string data, but it requires the data to be Base64 encoded to provide a certain level of confidentiality.
  3. ConfigMap and Secret can be referenced in the “env.valueFrom” field of a Pod to make them accessible as environment variables for applications.
  4. ConfigMap and Secret can be referenced in the “spec.volumes” field of a Pod to turn them into volumes, which can then be loaded as files in the “spec.containers.volumeMounts” field.
  5. There is no limit on the size of data that ConfigMap and Secret can store. However, small data is more suitable to be used as environment variables, while large data should be stored in volumes and applied flexibly according to specific scenarios.

Homework #

Now it’s time for homework, and I have two questions for you to consider:

  1. Can you explain your understanding of the objects ConfigMap and Secret? What are their similarities and differences?
  2. If we modify the YAML of a ConfigMap/Secret and update the object using the kubectl apply command, will the information associated with the Pod be synchronized? You can try verifying this yourself.

Please feel free to share your findings in the comments section. In the next class, we will have a hands-on session for this chapter. See you in the next class.

Image