DEV Community

loading...

Kubernetes - Using ConfigMap SubPaths to Mount Files

Josh Duffney
Author of becomeansible.com, Pluralsight author, Former Microsoft MVP
Updated on ・4 min read

What if you want to mount a configuration file from a ConfigMap, but do not want to mount it as a volume? You can accomplish this by using SubPaths. Before I explain how to use a SubPath, let's look at what happens if I don't.

Using a ConfigMap as a Mounted Volume

By default ConfigMaps are shared with Pods in two ways; environment variables or mounted volumes. In this example I have a config map that contains a mysql configuration. Using environment variables for that isn't feasible, which leaves me with using a volume.

What you see here is a manifest for a configMap. The manifest defines the configMap name and associates some labels in the metadata. In the data section it contains a map name mysql_binlog_format.cnf and the data source which is the content of the file.

configMap

# mysql-configmap.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: mysql-configmap
  labels:
    app: mysql
data:
  mysql_binlog_format.cnf: |
    [mysqld]
    binlog-format=mixed
Enter fullscreen mode Exit fullscreen mode

Deployment Using a configMap Volume

As I mentioned configMaps can be used as volumes. The volumeMounts inside the template.spec are the same as any other volume. However, the volumes section is different. Instead of specifying a persistentVolumeClaim or other volume type you reference the configMap by name. This takes all the map names and data sources of the configMap named mysql-configmap and mounts it as a volume at /etc/mysql/conf.d

# mysql-deployment.yaml

apiVersion: v1
kind: Service
metadata:
  name: mysql
spec:
  ports:
  - port: 3306
  selector:
    app: mysql
  clusterIP: None
---
apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2
kind: Deployment
metadata:
  name: mysql
spec:
  selector:
    matchLabels:
      app: mysql
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - image: mysql:5.6
        name: mysql
        env:
          # Use secret in real usage
        - name: MYSQL_ROOT_PASSWORD
          value: password
        ports:
        - containerPort: 3306
          name: mysql
        volumeMounts:
        - name: mysql-configmap-volume
          mountPath: /etc/mysql/conf.d
      volumes:
      - name: mysql-configmap-volume
        configMap:
          name: mysql-configmap
Enter fullscreen mode Exit fullscreen mode

Apply the Manifests

#save the above manifests (mysql-configmap.yaml & mysql-deployment.yaml)
kubectl apply -f .

# Use kubctl exec to list /etc/mysql/conf.d contents
kubectl exec -it mysql-59fcc88776-g768b ls /etc/mysql/conf.d
Enter fullscreen mode Exit fullscreen mode

The problem

Alt Text

The screen shot above tells us the volume mount worked. Kubernetes took the map name of mysql_binlog_format.cnf present it as a file with the contents that were stored in the data source of the configMap. The problem however is it laid that volume on top of the existing directory. The default configuration files for mysql are no longer present. I'd have to create all the mysql configuration files and store them into the configMap. Or, I can use a subPath.

Using a configMap subPath

Nothing needs to change with the actual configMap resources that was created earlier. However, I do have to make a few changes to our deployment manifest to use a subPath. First I'll have to update template.spec.volumeMounts. I need to update the mountPath to include the file name I want it to mount. The mountPath is now /etc/mysql/conf.d/binlog_format.cnf instead of /etc/mysql/conf.d. Next I need to add the subPath property. The value for the subPath must match the path specified in template.volumes section.

I also need to update the templates.volumes section. Instead of simply providing the configMap name I now also need to provide and items list of the entries I want to include from the configMap. Under items I've specified the key, which is the map name and the path. The path value must match the subPath value define din template.spec.volumeMounts.

apiVersion: v1
kind: Service
metadata:
  name: mysql
spec:
  ports:
  - port: 3306
  selector:
    app: mysql
  clusterIP: None
---
apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2
kind: Deployment
metadata:
  name: mysql
spec:
  selector:
    matchLabels:
      app: mysql
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - image: mysql:5.6
        name: mysql
        env:
          # Use secret in real usage
        - name: MYSQL_ROOT_PASSWORD
          value: password
        ports:
        - containerPort: 3306
          name: mysql
        volumeMounts:
        - name: mysql-configmap-volume
          mountPath: /etc/mysql/conf.d/binlog_format.cnf
          subPath: binlog_format.cnf
      volumes:
      - name: mysql-configmap-volume
        configMap:
          name: mysql-configmap
          items:
          - key: mysql_binlog_format.cnf
            path: binlog_format.cnf
Enter fullscreen mode Exit fullscreen mode

Re-apply the Manifests

#save the above manifests (mysql-configmap.yaml & mysql-deployment.yaml)
kubectl apply -f .

# Use kubctl exec to list /etc/mysql/conf.d contents
kubectl exec mysql-7848ff5588-d4f6p ls /etc/mysql/conf.d

# cat contents of binlog_format.cnf
kubectl exec mysql-7848ff5588-d4f6p cat /etc/mysql/conf.d/binlog_format.cnf
Enter fullscreen mode Exit fullscreen mode

Alt Text

Disadvantages

SubPaths are not automatically updated when a ConfigMap is modified. Changes to a ConfigMap will need to be a new deployment which would result in the pods being recreated with the updated ConfigMap content.

Sources

run-single-instance-stateful-application
Add ConfigMap data to a specific path in the Volume

Discussion (4)

Collapse
thoki123 profile image
thoki123

I tried your steps with OpenShift v4.19 (Kubernetes 1.19) and it didn't work. The directory (which is a Tomcat 9 deployment) is still overwritten by the single file (logback.xml) in the volume

kind: ConfigMap
apiVersion: v1
metadata:
  name: logback-configmap
data:
  logback: |
    <?xml version="1.0" encoding="UTF-8"?>
    <configuration scan="true" scanPeriod="60 seconds">
[...]
Enter fullscreen mode Exit fullscreen mode

and in the deployment config:

[...]
spec:
      volumes:
        - name: logback-configmap-volume
          configMap:
            name: logback-configmap
            items:
              - key: logback
                path: logback.xml
            defaultMode: 420
[..]
         volumeMounts:
            - name: logback-configmap-volume
              mountPath: /deployments/zfaRouter/WEB-INF/classes/logback.xml
              subPath: logback.xml
[..]
Enter fullscreen mode Exit fullscreen mode
sh-4.4$ ls -la /deployments/zfaRouter/WEB-INF/classes/
total 4
drwxr-xr-x. 2 root root         25 May  4 16:35 .
drwxr-xr-x. 3 root root         21 May  4 16:35 ..
-rw-r--r--. 1 root 1015170000 1084 May  4 16:35 logback.xml
Enter fullscreen mode Exit fullscreen mode

Do you have any idea why this is not working?

Collapse
tejsinghrana profile image
Tej-Singh-Rana

Appreciated well explained.

Collapse
nicksc423 profile image
nicksc423

I think there's a typo, you config map is named: mysql-config but you always seem to reference: mysql-configmap in the deployment

Collapse
joshduffney profile image
Josh Duffney Author

great catch, indeed a typo. I appreciate you taking the time to point that out. I've updated the configmap name to mysql-configmap. :)