Kubernetes - Using ConfigMap SubPaths to Mount Files
Tags: kubernetes, devops, volumes, subpaths
Published: 2019-11-13 14:47:56

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
```

### 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
```

### 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
```

### The problem

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/uvn531nfaqko5391okhp.png)

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
```

### 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
```

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/lxe364b3pu5bfn8txvlj.png)


## 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](https://kubernetes.io/docs/tasks/run-application/run-single-instance-stateful-application/)
[Add ConfigMap data to a specific path in the Volume](https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/#add-configmap-data-to-a-specific-path-in-the-volume)