Skip to content

CKAD concept notes: ConfigMaps and Secrets

  • kubernetes
  • ckad
  • configmap
  • secret
  • downward-api

Based on: Kubernetes v1.35 (commands and YAML checked on kind kindest/node:v1.35.8)

Contents

Concepts

ConfigMap

Non-secret configuration data as key-value pairs. Used to keep environment-specific settings out of the container image.

  • unlike most objects it has no spec; it has data and binaryData fields
    • data: UTF-8 strings
    • binaryData: base64-encoded binary
    • keys can’t overlap between the two fields
  • key names: only letters, digits, -, _, .
  • size limit: 1 MiB. Anything bigger goes in a volume or a separate store
  • no secrecy or encryption. Sensitive values go in a Secret
  • must be in the same namespace as the Pod to be referenced. Static Pods can’t reference ConfigMaps

The official docs list four ways to use one:

  1. referenced inside a container’s command/args
  2. container environment variables
  3. files in a read-only volume
  4. read directly from the Kubernetes API inside the Pod

In the exam you mostly write 2 and 3 by hand.

Secret

A small amount of sensitive data like passwords, tokens, or keys. Structurally similar to a ConfigMap, but it has a type field and data values must be base64-encoded.

  • data: base64-encoded values
  • stringData: write plain text and the API server encodes it and merges it into data. If the same key is in both, stringData wins
  • size limit: 1MiB per Secret
  • a Secret is only sent to nodes running a Pod that needs it. When mounted as a volume, the kubelet copies it into tmpfs so it never hits disk, and deletes the local copy when the Pod is deleted

base64 != encryption

To quote the official docs’ warning: by default, Secrets are stored unencrypted in the API server’s data store (etcd). Anyone with API access, or with access to etcd, can read and modify Secrets. And anyone allowed to create a Pod in a namespace can use that Pod to indirectly read any Secret in the same namespace.

The minimum measures the docs suggest:

  • enable Encryption at Rest for Secrets
  • restrict Secret access with least-privilege RBAC
  • limit Secret access to the containers that need it
  • consider an external Secret store provider

A value in data goes back to plain text with a single base64 -d.

Secret types

typeUse
Opaquedefault. Arbitrary user data
kubernetes.io/service-account-tokenServiceAccount token
kubernetes.io/dockercfgserialized ~/.dockercfg
kubernetes.io/dockerconfigjsonserialized ~/.docker/config.json
kubernetes.io/basic-authbasic authentication credentials
kubernetes.io/ssh-authSSH authentication data
kubernetes.io/tlsTLS certificate and key
bootstrap.kubernetes.io/tokenbootstrap token

How the kubectl create secret subcommands map: generic -> Opaque, docker-registry -> kubernetes.io/dockerconfigjson, tls -> kubernetes.io/tls.

immutable

Both ConfigMaps and Secrets can set immutable: true (stable since v1.21). Once immutable, you can’t undo it or change data; you have to delete and recreate. Immutable objects also close their watches, which reduces load on kube-apiserver.

How it works

Behavior by injection method

MethodFieldsResult
one key -> one env varenv[].valueFrom.configMapKeyRef / secretKeyRefyou pick the name
all keys -> env varsenvFrom[].configMapRef / secretRefkey names become variable names as-is
filesvolumes[].configMap / secret + volumeMountsone file per key. Use items to pick specific keys and set paths
  • if a ConfigMap or key referenced by env doesn’t exist, the Pod doesn’t start. Mark it optional: true and it starts anyway
  • envFrom uses keys directly as variable names. Since v1.34 the variable name rules are relaxed (any printable ASCII except =), so on a v1.35 cluster keys like Y-Z, 1abc, and .dot aren’t skipped; they become variables. The explanation “invalid keys are skipped and recorded in an InvalidVariableNames event” is from versions with the stricter rules
  • file permissions on a Secret volume are set with defaultMode (the whole volume) or items[].mode (per file)
  • keys starting with . become hidden files (check with ls -la)

Update propagation

How it’s consumedWhen the source changes
volume mountpicked up automatically. The delay is at most the kubelet sync period + cache propagation delay
volume mounted with subPathnot picked up
env / envFromnot picked up. The Pod needs a restart
direct API readsup to the application to watch and handle it

How the kubelet detects changes is set by configMapAndSecretChangeDetectionStrategy, default Watch. The other options are a TTL-based cache and querying the API server on every sync.

Even when a volume’s value changes, it has no effect unless the application rereads the file. Whether it takes effect depends on how the app is written.

projected volumes

Merges several volume sources into one directory. Supported sources:

  • secret
  • configMap
  • downwardAPI
  • serviceAccountToken
  • clusterTrustBundle
  • podCertificate

Things to watch:

  • every source must be in the same namespace as the Pod
  • mounting with subPath means no updates
  • a regular secret volume uses secretName, but secret inside a projected volume uses name
  • defaultMode applies to the whole projected volume; override per source with items[].mode
  • serviceAccountToken: set audience, expirationSeconds (default 1 hour, minimum 10 minutes), path

Downward API

A way to expose the Pod’s and container’s own fields to the container. The environment variable form and the downwardAPI volume together are called the Downward API.

Fields you can expose with fieldRef:

Fieldenvvolume
metadata.name, metadata.namespace, metadata.uidOO
metadata.labels['<KEY>'], metadata.annotations['<KEY>']OO
metadata.labels, metadata.annotations (all)XO
spec.nodeName, spec.serviceAccountNameOX
status.podIP, status.podIPs, status.hostIP, status.hostIPsOX

resourceFieldRef exposes the container’s requests/limits values (cpu, memory, ephemeral-storage, hugepages-*). If no limit is set, you get the node’s allocatable maximum. divisor defaults to 1, so limits.cpu: 500m in the example below comes out rounded up to 1. To see millicores, use divisor: 1m.

All labels or all annotations are only available through a volume. If resources are resized while running, the volume side gets updated, but env stays the same until the container restarts.

Examples

Creating them

# ConfigMap
kubectl create configmap app-config --from-literal=MODE=prod --from-literal=LOG_LEVEL=info
kubectl create configmap app-files --from-file=app.properties            # key = file name
kubectl create configmap app-files2 --from-file=config=app.properties    # custom key name
kubectl create configmap app-env --from-env-file=app.env                 # one VAR=VAL per line

# Secret
kubectl create secret generic db-cred --from-literal=username=admin --from-literal=password='s3cr3t'
kubectl create secret tls web-tls --cert=tls.crt --key=tls.key
kubectl create secret docker-registry regcred \
  --docker-server=registry.example.com --docker-username=user --docker-password=pass

# dump YAML
kubectl create configmap app-config --from-literal=MODE=prod --dry-run=client -o yaml > cm.yaml

# check a value
kubectl get secret db-cred -o jsonpath='{.data.password}' | base64 -d

--from-file vs --from-env-file:

  • --from-file=app.env: the whole file is one value. The key is app.env
  • --from-env-file=app.env: one key per VAR=VAL line. # comments and blank lines are ignored

Secret manifest

apiVersion: v1
kind: Secret
metadata:
  name: db-cred
type: Opaque
data:
  username: YWRtaW4= # "admin"
stringData:
  password: s3cr3t # plain text, converted to base64 on create

env / envFrom / volume

apiVersion: v1
kind: Pod
metadata:
  name: config-demo
spec:
  containers:
    - name: app
      image: busybox:1.36
      command: ["sh", "-c", "env; ls /etc/config /etc/secret; sleep 3600"]
      env:
        - name: DB_USER
          valueFrom:
            secretKeyRef:
              name: db-cred
              key: username
        - name: LOG_LEVEL
          valueFrom:
            configMapKeyRef:
              name: app-config
              key: LOG_LEVEL
              optional: true
      envFrom:
        - configMapRef:
            name: app-config
      volumeMounts:
        - name: config-vol
          mountPath: /etc/config
          readOnly: true
        - name: secret-vol
          mountPath: /etc/secret
          readOnly: true
  volumes:
    - name: config-vol
      configMap:
        name: app-files
    - name: secret-vol
      secret:
        secretName: db-cred
        defaultMode: 0400
        items:
          - key: password
            path: db-password

projected volume + Downward API

apiVersion: v1
kind: Pod
metadata:
  name: projected-demo
  labels:
    app: demo
spec:
  containers:
    - name: app
      image: busybox:1.36
      command: ["sh", "-c", "sleep 3600"]
      resources:
        limits:
          cpu: 500m
          memory: 128Mi
      env:
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: NODE_NAME
          valueFrom:
            fieldRef:
              fieldPath: spec.nodeName
      volumeMounts:
        - name: all-in-one
          mountPath: /projected
          readOnly: true
  volumes:
    - name: all-in-one
      projected:
        sources:
          - configMap:
              name: app-config
          - secret:
              name: db-cred # inside projected it's name, not secretName
              items:
                - key: password
                  path: secret/password
          - downwardAPI:
              items:
                - path: labels
                  fieldRef:
                    fieldPath: metadata.labels
                - path: cpu_limit
                  resourceFieldRef:
                    containerName: app
                    resource: limits.cpu

Easily confused

ItemConfigMapSecret
Usenon-secret settingssensitive values
Value fieldsdata (UTF-8), binaryData (base64)data (base64), stringData (plain text, write-only)
typenoneOpaque, etc.
Size limit1 MiB1MiB
env referenceconfigMapKeyRef / configMapRefsecretKeyRef / secretRef
volume fieldconfigMap.namesecret.secretName (name inside projected)
Stored on the node-tmpfs when mounted as a volume
ComparisonDifference
env vs envFromone key under a name you choose vs every key as-is
volume vs subPath volumepicks up updates vs doesn’t
--from-file vs --from-env-fileone file = one key vs one line = one key
fieldRef vs resourceFieldRefPod metadata and status vs container requests/limits
Downward API env vs volumefixed while running vs all labels/annotations, updates picked up

Exam tips

Commands

  • create ConfigMaps and Secrets directly with kubectl create configmap / kubectl create secret generic, and dump YAML with --dry-run=client -o yaml
  • the Pod side (env/envFrom/volumes) has no imperative command, so write it in YAML. For an existing Deployment, kubectl set env deployment/<name> --from=configmap/<cm> also works
  • check a Secret value: kubectl get secret <name> -o jsonpath='{.data.<key>}' | base64 -d
  • check environment variables: kubectl exec <pod> -- env

kubectl explain

kubectl explain pod.spec.containers.env.valueFrom   # configMapKeyRef, secretKeyRef, fieldRef
kubectl explain pod.spec.containers.envFrom         # configMapRef, secretRef, prefix
kubectl explain pod.spec.volumes.configMap          # name, items, defaultMode
kubectl explain pod.spec.volumes.secret             # secretName (field name differs from configMap)
kubectl explain pod.spec.volumes.projected.sources  # sources you can put in a projected volume
kubectl explain pod.spec.volumes.downwardAPI.items  # Downward API volume
kubectl explain secret.stringData                   # the plain-text field

References