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
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
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
Top comments (6)
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
and in the deployment config:
Do you have any idea why this is not working?
I have followed the method mentioned in the blog and it is working. Thanks for this really useful content. But I am facing one issue now which is mentioned below.
I have a kind: Deployment as below where I am mounting rs-config.yaml ConfigMap(which contains a ss.yml file) as volume. With the below configuration I am able to get the ss.yml file in the specified mountPath: C:................\App_Data
Apart from ss.yml I need another directory named DATA in the same path(C:................\App_Data) for which I used the subPath. With the below code I am able to get DATA directory but the contents in the DATA directory are not getting completely copied(subdirectories and some files are missing which are required for our application to be up).
name: volume-rs
configMap:
name: rs-config
items:
key: ss.yml
path: remote/AppData/
containers:
name: remote
image: registry_name
volumeMounts:
name: volume-rs
mountPath: C:................\App_Data\ss.yml
subPath: remote/AppData/
Can anyone suggest a workaround for this? Any help would be grateful. Thanks in advance.
Appreciated well explained.
I think there's a typo, you config map is named: mysql-config but you always seem to reference: mysql-configmap in the deployment
great catch, indeed a typo. I appreciate you taking the time to point that out. I've updated the configmap name to mysql-configmap. :)
I believe you don't have to list the items inside the volume for it to work.