ConfigMaps and Secrets
Keep configuration out of your images with ConfigMaps, handle passwords and keys with Secrets, and know what base64 does and does not protect.
The last post covered cluster maintenance: upgrades and etcd backups. This one turns to applications, and how they get their configuration and sensitive data.
Kubernetes has two resources for this. ConfigMaps hold configuration that isn't confidential. Secrets hold sensitive information such as passwords and API keys. Both help keep applications portable and secure.
ConfigMaps
Say your application needs different settings in development, staging and production, such as API endpoints or logging levels. If you hardcode those values into the application code or the container image, the application is inflexible, and every change to an environment means rebuilding the image.
A ConfigMap is a Kubernetes API object that stores non-confidential data as key-value pairs. It separates configuration from your application code and container images, which makes your applications more portable and easier to manage.
Your container images stay generic, because the configuration is injected when the container runs. You can change the configuration without rebuilding the image. And you can manage the configuration of many Pods from one place.
Creating ConfigMaps
You can create a ConfigMap from literal values, from files or from an entire directory.
From literal values
For simple key-value pairs:
kubectl create configmap my-app-config --from-literal=log_level=INFO --from-literal=feature_flag=trueFrom files
For configuration files, first create the file, for example config.properties:
# config.properties
app.name=MyApplication
app.version=1.0Then create the ConfigMap from it:
kubectl create configmap my-file-config --from-file=config.propertiesTo store the file under a different key name in the ConfigMap, put the key name before the file:
kubectl create configmap my-file-config-renamed --from-file=app_settings=config.propertiesFrom a directory
If you have a directory full of configuration files, you can create one ConfigMap from all of them:
# Assuming you have a directory named 'config-files' with multiple files
# mkdir config-files
# echo "db.host=localhost" > config-files/db.conf
# echo "api.url=http://api.example.com" > config-files/api.conf
kubectl create configmap my-dir-config --from-file=config-files/Using ConfigMaps in Pods
A Pod can consume a ConfigMap in several ways:
As environment variables
apiVersion: v1
kind: Pod
metadata:
name: my-app-pod-env
spec:
containers:
- name: my-container
image: busybox:1.36
command: ["/bin/sh", "-c", "echo Log Level: $LOG_LEVEL && echo Feature Flag: $FEATURE_FLAG && sleep 3600"]
env:
- name: LOG_LEVEL
valueFrom:
configMapKeyRef:
name: my-app-config # Name of the ConfigMap
key: log_level # Key within the ConfigMap
- name: FEATURE_FLAG
valueFrom:
configMapKeyRef:
name: my-app-config
key: feature_flagAs files in a volume
This is often the better choice for configuration files, because your application can read them from an ordinary file path:
apiVersion: v1
kind: Pod
metadata:
name: my-app-pod-volume
spec:
containers:
- name: my-container
image: busybox:1.36
command: ["/bin/sh", "-c", "cat /etc/config/config.properties && sleep 3600"]
volumeMounts:
- name: config-volume
mountPath: "/etc/config" # Directory where config files will be mounted
readOnly: true
volumes:
- name: config-volume
configMap:
name: my-file-config # Name of the ConfigMapEach key in the ConfigMap appears as a separate file in the mounted directory, here
/etc/config/config.properties.
Secrets
ConfigMaps are fine for general configuration, but not for sensitive information. Passwords, API keys, database credentials and other confidential data belong in Secrets.
Secrets are a safer way to handle sensitive data than embedding it in code or in a ConfigMap. They keep it separate from your application code and images, and Kubernetes has built-in ways to inject them into Pods.
Base64 is not encryption
The data in a Kubernetes Secret is stored as base64-encoded strings, and base64 encoding is not encryption. It only turns binary data into ASCII text, so the data can be sent and stored safely by text-based systems. Anyone who can read the encoded Secret can decode it.
Protecting Secrets properly takes more: encryption at rest for etcd, RBAC and external secret management.
Creating Secrets
With kubectl create secret
From literal values:
kubectl create secret generic my-db-secret --from-literal=username=admin --from-literal=password=sUp3rS3cr3tP@ssw0rdValues typed on the command line end up in your shell history, which is a security risk.
Creating the Secret from files is the better way to use kubectl create. First write the values
to files:
echo -n 'admin' > username.txt
echo -n 'sUp3rS3cr3tP@ssw0rd' > password.txtThen create the Secret. The username= and password= prefixes set the key names, so the Pods
below find the keys they expect:
kubectl create secret generic my-db-secret --from-file=username=username.txt --from-file=password=password.txtWith a YAML manifest
For declarative management, define the Secret in YAML. Values under data must be base64
encoded:
apiVersion: v1
kind: Secret
metadata:
name: my-yaml-secret
type: Opaque # Default type for general-purpose secrets
data:
username: YWRtaW4= # echo -n 'admin' | base64
password: c1VwM3JTM2NyM3RQQHNzdzByZA== # echo -n 'sUp3rS3cr3tP@ssw0rd' | base64For readability, use stringData instead, and Kubernetes base64-encodes the values for you:
apiVersion: v1
kind: Secret
metadata:
name: my-yaml-secret-stringdata
type: Opaque
stringData:
username: admin
password: sUp3rS3cr3tP@ssw0rdApply it with kubectl apply -f my-secret.yaml.
Using Secrets in Pods
As environment variables
apiVersion: v1
kind: Pod
metadata:
name: my-app-pod-secret-env
spec:
containers:
- name: my-container
image: busybox:1.36
command: ["/bin/sh", "-c", "echo DB Username: $DB_USERNAME && echo DB Password: $DB_PASSWORD && sleep 3600"]
env:
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: my-db-secret # Name of the Secret
key: username # Key within the Secret
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: my-db-secret
key: passwordEnvironment variables are easy to expose by accident. Mounting the Secret as files is more secure.
As files in a volume
apiVersion: v1
kind: Pod
metadata:
name: my-app-pod-secret-volume
spec:
containers:
- name: my-container
image: busybox:1.36
command: ["/bin/sh", "-c", "echo DB Username: $(cat /etc/db-credentials/username) && echo DB Password: $(cat /etc/db-credentials/password) && sleep 3600"]
volumeMounts:
- name: db-credentials-volume
mountPath: "/etc/db-credentials" # Directory where secret files will be mounted
readOnly: true
volumes:
- name: db-credentials-volume
secret:
secretName: my-db-secret # Name of the Secret
defaultMode: 0400 # Optional: set file permissionsEach key in the Secret becomes a file in the mounted directory.
For pulling images from a private registry
If you use a private container registry, Kubernetes needs credentials to pull images from it.
Store them in a docker-registry Secret:
kubectl create secret docker-registry my-registry-secret \
--docker-server=private-registry.example.com \
--docker-username=your-username \
--docker-password=your-password \
--docker-email=your-email@example.comThen reference it from the Pod's imagePullSecrets:
apiVersion: v1
kind: Pod
metadata:
name: my-private-image-pod
spec:
containers:
- name: my-container
image: private-registry.example.com/my-app:latest
imagePullSecrets:
- name: my-registry-secret # Name of the Secret containing registry credentialsGood practices for Secrets
Configure your cluster to encrypt etcd data at rest, and use RBAC to restrict who can read or change Secrets.
Rotate Secrets regularly, so a leaked one is useful for less time.
For high-security needs, integrate an external secret manager such as HashiCorp Vault. With one in place, the Secrets Store CSI Driver fetches the secret and mounts it into the Pod as a volume:
Prefer mounting Secrets as files over environment variables, and use stringData to keep Secret
manifests readable.
For GitOps, encrypt Secret manifests with Sealed Secrets or SOPS before you commit them to Git.
The next post continues with application development: probes and health checks, which keep applications running and ready for traffic. Liveness, readiness and startup probes.