Secrets and ConfigMaps
One of the tenants of Twelve Factor application design is the concept of externalizing configuration and secrets. In the past, you may have had your configuration externalized to a resource bundle or bundled directly into your application artifact. The problem with externalized resource bundles is that they tend to drift over time. Bundling the secret into the application artifact is even worse as it increases the barrier to regularly rotate the secret since you must rebuild and redeploy the application. While this can be automated in a build pipeline there is a better way.
Docker decouple configuration from the deployable container using environment variables.
1spec:
2 containers:
3 - image: mysql:5.6
4 name: mysql
5 env:
6 - name: MYSQL_ROOT_PASSWORD
7 value: "helloworld!"
8 ports:
9 - containerPort: 3306
10 name: mysql
11 volumeMounts:
12 - name: mysql-persistent-storage
13 mountPath: /var/lib/mysql
Kubernetes provides a few ways to pass an environment variable to the running container.
Secrets
The above spec for the MySQL container passes in the desired root password for the database via the environment. Since the YAML file used for this is stored in source control we have effectively reduced the overall security of our system because anyone who has access to the source control will potentially have access to your production secrets.
If you look closely at my last kubernetes post I made a small change to the password.
1env:
2- name: MYSQL_ROOT_PASSWORD
3 valueFrom:
4 secretKeyRef:
5 name: cloudsql-db-credentials
6 key: password
The above YAML decouples the credential further from the deployment configuration. Kubernetes allows us to set this credential using kubectl
kubectl create secret generic cloudsql-db-credentials \
--from-literal=password=[PASSWORD]
If we have different passwords for different environments such as dev and production they can be entered by the appropriate person who is allowed to know that information and not shared organization wide. In addition, changing the password can be done via kubectl
rather than redeploying the entire application.
kubectl
can also be used to import files that are passed to the container. This can be handy for things like SSL certificates or resource bundles with sensitive information within them.
kubectl create secret generic cloudsql-instance-credentials \
--from-file=credentials.json=\Users\jellin\credentials.json
The credentials.json
file referenced above contains the SSL certificate used for the Cloud SQL Proxy. It can be referenced in the Yaml as show below.
1name: cloudsql-proxy
2image: gcr.io/cloudsql-docker/gce-proxy:1.11
3command: ["/cloud_sql_proxy",
4 "-instances=labs-jellin:us-central1:wordpress=tcp:3306",
5 "-credential_file=/secrets/cloudsql/jellin-e4ccff43f21b.json"]
6volumeMounts:
7 - name: cloudsql-instance-credentials
8 mountPath: /secrets/cloudsql
9 readOnly: true
The credential file is extracted to /secrets/cloudsql via a volumeMount. The file can then be read by the container at startup as if it existed on the filesystem in that directory.
ConfigMaps
Another handy feature of Kubernetes is Configuration Maps. ConfigMaps are combined with the Pod at runtime and can be used to set an environment that the application inside the container reads.
Creating the ConfigMap
ConfigMaps can be defined directly inside the YAML definition.
1apiVersion: v1
2data:
3 my-config.txt: |
4 # This is a sample config file that I might use to configure an application
5 parameter1 = value1
6 parameter2 = value2
7kind: ConfigMap
8metadata:
9 name: my-config
Config Maps can also be created using kubectl
kubectl create configmap my-config \
--from-file=my-config.properties
It is also possible to add bare values which can be read into the container environment.
kubectl create configmap my-config \
--from-literal=some-param=foo
Using ConfigMaps
The real magic happens when you try and use the values from the ConfigMap. Kubernetes provides two common ways to use a Config Map.
-
Filesystem
If you have loaded a ConfigMap using a file. This file can then be placed mounted on the container file system. The application can then read the file directly.
1containers: 2 - name: test-container 3 image: gcr.io/jeffellin/some-springboot-app 4 volumeMounts: 5 - name: config-volume 6 mountPath: /config 7volumes: 8 - name: config-volume 9 configMap: 10 name: my-config
In the above example the my-config.properties
file loaded previously is extracted to /config
-
Environment variable
The container environment can also be set via a ConfigMap
1 - name: test-container 2 image: gcr.io/jeffellin/some-springboot-app 3 imagePullPolicy: Always 4 env: 5 - name: ENV_VAR 6 valueFrom: 7 configMapKeyRef: 8 name: my-config 9 key: some-param 10 volumeMounts: 11 - name: config-volume 12 mountPath: /config
In the above example the previously set value for some-param
is passed to the environment variable ENV_VAR
as foo
Updating Config and Secrets
Updating a secret is that it won’t automatically pass the new values to the running Pod. This can be accomplished with zero downtime with the rolling update feature of a Kubernetes deployment.
ConfigMaps, on the other hand, are updated without the need to restart the Pod. However, you will want to make sure the application that you deploy within the Container is sensitive to these changes and acts accordingly.