ConfigMaps and Secrets
Creating ConfigMaps and Secrets from literals, files and env files, consuming them as environment variables or volumes, Secret types, immutability, and when an update actually reaches a running Pod.
Exam tasks: 2.2 (use ConfigMaps and Secrets to configure applications)
The decision: should this setting reach the container as an environment variable or as a file, and what do you have to do after changing it so the app actually sees the new value?
Env var or volume
| Env vars | Volume (whole directory) | Volume with subPath | |
|---|---|---|---|
| Picks up changes | No, until the container restarts | Yes, eventually (kubelet sync plus cache, often under a minute or two) | No |
| Granularity | One key (valueFrom) or all keys (envFrom) | All keys as files, or chosen keys with items | One file into an existing directory |
| Visible in | kubectl exec ... -- env | ls of the mount path | The single file |
| Good for | Flags, URLs, small values | Config files, certificates, anything that should hot-reload | Dropping one file next to others in an image directory |
Exam signal
"The app must see the new value without restarting" means a volume mount without subPath. If the task only
says "make the Pods use the new value", a kubectl rollout restart deploy/... after the edit is the quick,
reliable answer for env vars.
Creating them
k create cm vane-settings -n wx --from-literal=LOG_LEVEL=debug --from-literal=REGION=north
k create cm vane-files -n wx --from-file=app.ini # key = app.ini
k create cm vane-files2 -n wx --from-file=config.ini=app.ini # key renamed to config.ini
k create cm vane-env -n wx --from-env-file=vane.env # each KEY=value line becomes a key
k create secret generic vane-db -n wx --from-literal=DB_USER=vane --from-literal=DB_PASS='s3cr3t!'
k create secret tls vane-tls -n wx --cert=tls.crt --key=tls.key
k create secret docker-registry vane-pull -n wx \
--docker-server=registry.wx.example --docker-username=ci --docker-password='p4ss'- Quote values with shell metacharacters (
!,$) in single quotes, or the shell rewrites them. - In YAML, Secret
datavalues must be base64;stringDatatakes plain text and the API server encodes it. Useecho -nwhen you encode by hand, or the trailing newline ends up in the password. - ConfigMaps and Secrets are namespaced, and the Pod must be in the same namespace to use them.
| Secret type | Created with | Used for |
|---|---|---|
Opaque | create secret generic | Arbitrary key/value data |
kubernetes.io/tls | create secret tls | tls.crt and tls.key for Ingress, Gateway, webhooks |
kubernetes.io/dockerconfigjson | create secret docker-registry | imagePullSecrets for private registries |
kubernetes.io/basic-auth, kubernetes.io/ssh-auth | YAML with type: | Credentials with required keys (username/password, ssh-privatekey) |
kubernetes.io/service-account-token | YAML with an annotation naming the ServiceAccount | Long-lived token, only when you explicitly need one |
bootstrap.kubernetes.io/token | kubeadm token create | Node join tokens in kube-system |
Base64 is not encryption
Anyone who can get a Secret can decode it with base64 -d. Protection comes from RBAC on secrets and from
encryption at rest, which you turn on with an EncryptionConfiguration file passed to kube-apiserver via
--encryption-provider-config. An answer that "secures" a Secret by base64-encoding it twice changes nothing.
Consuming them
apiVersion: v1
kind: Pod
metadata:
name: vane
namespace: wx
spec:
imagePullSecrets:
- name: vane-pull
containers:
- name: vane
image: registry.wx.example/vane:3.2
env:
- name: DATABASE_USER # name inside the container
valueFrom:
secretKeyRef:
name: vane-db
key: DB_USER
envFrom:
- configMapRef:
name: vane-settings # every key becomes a variable
prefix: CFG_ # optional: CFG_LOG_LEVEL, CFG_REGION
volumeMounts:
- name: conf
mountPath: /etc/vane # directory: refreshes on change
readOnly: true
- name: tls
mountPath: /etc/vane/tls
readOnly: true
volumes:
- name: conf
configMap:
name: vane-files
items: # optional: only these keys, renamed
- key: app.ini
path: vane.ini
- name: tls
secret:
secretName: vane-tls # note: secretName, not name
defaultMode: 0400- The volume field is
configMap.namebutsecret.secretName. Mixing them up is the most common YAML error on this topic. - A missing ConfigMap or key keeps the Pod in
ContainerCreatingorCreateContainerConfigError. Addoptional: trueto the reference if the app can start without it. - A volume mount replaces the target directory's contents. Mounting into
/etchides everything else in/etc; use a dedicated directory orsubPath. - Secret volumes are backed by tmpfs on the node, so they never hit the node's disk.
projectedvolumes merge several ConfigMaps, Secrets, the downward API and ServiceAccount tokens into one directory.
Immutable ConfigMaps and Secrets
apiVersion: v1
kind: ConfigMap
metadata:
name: vane-settings-v4
data:
LOG_LEVEL: info
immutable: true- Once
immutable: trueis set,datacan't change and the flag can't be removed. To change it, create a new object (often with a version suffix) and point the workload at it, which also triggers a rollout. - Immutable objects aren't watched by kubelets, which cuts API server load in clusters with many Pods.
Legacy: use projected ServiceAccount tokens from the TokenRequest API instead
Clusters before v1.24 created a long-lived token Secret for every ServiceAccount automatically. Current
Kubernetes mounts short-lived, auto-rotated tokens through a projected volume instead. Create a
kubernetes.io/service-account-token Secret only when something outside the cluster truly needs a
non-expiring token, or use kubectl create token <sa> for a temporary one.
Scenarios
Environment variables are resolved once when the container starts, so only new containers see the change. A rollout restart replaces the Pods gradually. The kubelet never refreshes env vars, immutability stops changes instead of propagating them, and recreating the ConfigMap still leaves existing containers with the old value.
subPath mounts bind one file at Pod start and are never refreshed. A whole-directory ConfigMap mount does
refresh, so the fix is either mounting a directory (for example /etc/nginx/conf.d) or restarting the Pods.
optional only controls whether a missing ConfigMap blocks the Pod. nginx caching is plausible for reloads,
but the file itself really didn't change here.
A Pod can only reference ConfigMaps and Secrets in its own namespace, and there is no namespace field in the volume source. RBAC on the ServiceAccount doesn't apply, because the kubelet fetches the Secret on the Pod's behalf. A projected volume has the same same-namespace rule.
Drill
kubectl config use-context drill-w2. In namespace kiosk:
- Create ConfigMap
kiosk-uiwith keysTHEME=darkandLANG=fi. - Create Secret
kiosk-apiwith keytokenset totk-55aa21. - Create Pod
kiosk(imagebusybox:1.36, commandsleep 3600) that gets every key ofkiosk-uias env vars and mountskiosk-apiread-only at/run/kiosk.
kubectl config use-context drill-w2
k create ns kiosk --dry-run=client -o yaml | k apply -f -
k create cm kiosk-ui -n kiosk --from-literal=THEME=dark --from-literal=LANG=fi
k create secret generic kiosk-api -n kiosk --from-literal=token=tk-55aa21
k run kiosk -n kiosk --image=busybox:1.36 --dry-run=client -o yaml -- sleep 3600 > kiosk.yamlAdd to the container and Pod spec in kiosk.yaml:
spec:
containers:
- name: kiosk
image: busybox:1.36
args: ["sleep", "3600"]
envFrom:
- configMapRef:
name: kiosk-ui
volumeMounts:
- name: api
mountPath: /run/kiosk
readOnly: true
volumes:
- name: api
secret:
secretName: kiosk-apik apply -f kiosk.yaml
k exec -n kiosk kiosk -- env | grep -E 'THEME|LANG' # THEME=dark, LANG=fi
k exec -n kiosk kiosk -- cat /run/kiosk/token # tk-55aa21Further reading
Deployments, rolling updates and rollbacks
How a Deployment rolls out a new revision through ReplicaSets, tuning maxSurge and maxUnavailable, Recreate, kubectl rollout history, undo, pause and restart, and spotting a stalled rollout.
Workload autoscaling
Manual scaling, HorizontalPodAutoscaler autoscaling/v2 with metrics-server, scaling behavior and stabilization, VerticalPodAutoscaler modes, and in-place Pod resize.