Ravindra BagaleCourses & study guides मराठी

Chapter 61: Kubernetes Storage in Depth: Volumes, PV, PVC, StorageClass, CSI and Snapshots

61.6 ConfigMap and Secret volumes, projected volumes and live config reloads

Why: the problem

Suppose MakeMyTrip runs an nginx front end for its flight search. The nginx.conf file, a list of feature flags and a TLS certificate must all be inside the container. Baking them into the image means a new image for every config change, and putting the private key in the image means anyone who pulls the image can read it. Kubernetes solves this with three volume types that come from API objects instead of disks. A configMap volume turns each key of a ConfigMap into a file. A secret volume does the same for a Secret, on a RAM-backed tmpfs so the key never touches the node's disk. A projected volume combines several sources (ConfigMaps, Secrets, the downward API and a service account token) into one folder. These volumes have one big advantage over environment variables: when you update the ConfigMap or Secret, kubelet updates the files inside running pods within about a minute. The app still has to notice the change (nginx needs a reload), and there is one trap: a subPath mount never gets updates. In this lesson we mount an nginx config from a ConfigMap and watch it update, mount a TLS Secret with safe file modes, build one projected folder with config, pod info and a short-lived token, and see why subPath and immutable objects behave differently.

What: every term in plain English

  • configMap volume — Each key in a ConfigMap becomes a file in the mount folder.
  • secret volume — Same for a Secret; files sit on tmpfs in RAM.
  • items — Choose which keys to mount and under what file names.
  • defaultMode — File permissions for mounted keys, for example 0440.
  • projected volume — One folder built from several sources.
  • downwardAPI — Exposes pod fields like name, namespace and labels as files.
  • serviceAccountToken — A short-lived, audience-bound token file that kubelet refreshes.
  • subPath — Mounts one file or subfolder; it does not receive updates.
  • immutable: true — Marks a ConfigMap or Secret read-only forever; kubelet stops watching it.
  • ..data symlink — How kubelet swaps all files at once during an update.
Create a namespace and the ConfigMap with nginx.conf. Mount it in an nginx pod and curl it. Change the ConfigMap and wait. Create a TLS Secret and mount it with mode 0400. Build a projected volume. Compare subPath and immutable. Clean up.
ConfigMap and Secret volumes: config as files that update live, except subPath

How: step by step

  1. Create a namespace and the ConfigMap with nginx.conf. Use kubectl create configmap --from-file.
  2. Mount it in an nginx pod and curl it. The config is live.
  3. Change the ConfigMap and wait. The file changes in the pod; reload nginx.
  4. Create a TLS Secret and mount it with mode 0400. Check the tmpfs mount.
  5. Build a projected volume. Config, labels and a token in one folder.
  6. Compare subPath and immutable. See which ones update.
  7. Clean up. Keep the cluster for 61.7.

Example 1: Serve nginx with a config file from a ConfigMap and update it live

Goal. Serve nginx with a config file from a ConfigMap and update it live.

default.conf

server {
    listen 80;
    location /health { return 200 "ok v1\n"; }
    location / { return 200 "MakeMyTrip flights v1\n"; }
}

nginx-cm.yaml

apiVersion: v1
kind: Pod
metadata:
  name: web
  namespace: storage
  labels:
    app: web
spec:
  volumes:
    - name: conf
      configMap:
        name: nginx-conf
  containers:
    - name: nginx
      image: nginx:1.27
      volumeMounts:
        - name: conf
          mountPath: /etc/nginx/conf.d
          readOnly: true
      resources:
        requests: {cpu: 20m, memory: 32Mi}
        limits: {memory: 64Mi}

What each line does

  • --from-file — The file name default.conf becomes the key, and the key becomes the file.
  • mountPath /etc/nginx/conf.d — Replaces the folder, so only our file is there.
  • readOnly — Config should never be edited from inside the pod.
  • Update delay — kubelet sync period plus cache, usually under 60-90 seconds.
  • nginx -s reload — nginx reads files only at start or reload.

Green output

$ kubectl create configmap nginx-conf -n storage --from-file=default.conf
$ kubectl apply -f nginx-cm.yaml
$ kubectl exec -n storage web -- curl -s localhost/
MakeMyTrip flights v1
$ sed -i 's/v1/v2/g' default.conf
$ kubectl create configmap nginx-conf -n storage --from-file=default.conf --dry-run=client -o yaml | kubectl apply -f -
$ sleep 60; kubectl exec -n storage web -- cat /etc/nginx/conf.d/default.conf | grep v2
    location / { return 200 "MakeMyTrip flights v2\n"; }
$ kubectl exec -n storage web -- nginx -s reload
$ kubectl exec -n storage web -- curl -s localhost/
MakeMyTrip flights v2

Expected result. The file inside the running pod changed without a restart. nginx served v2 only after the reload; many teams add a small sidecar that watches the folder and reloads.

Example 2: Mount a TLS Secret safely with defaultMode and items

Goal. Mount a TLS Secret safely with defaultMode and items.

tls-pod.yaml

apiVersion: v1
kind: Pod
metadata:
  name: tls-check
  namespace: storage
spec:
  securityContext:
    runAsUser: 101
    fsGroup: 101
  volumes:
    - name: certs
      secret:
        secretName: flights-tls
        defaultMode: 0440
        items:
          - key: tls.crt
            path: server.crt
          - key: tls.key
            path: server.key
            mode: 0400
  containers:
    - name: app
      image: busybox:1.36
      command: ["sh", "-c", "sleep 3600"]
      volumeMounts:
        - name: certs
          mountPath: /etc/tls
          readOnly: true
      resources:
        requests: {cpu: 10m, memory: 16Mi}
        limits: {memory: 32Mi}

What each line does

  • openssl req — Makes a self-signed test cert for flights.example.com; never commit real keys.
  • kubectl create secret tls — Stores tls.crt and tls.key keys.
  • items — Renames keys to server.crt and server.key.
  • mode: 0400 — Only the owner can read the private key.
  • fsGroup: 101 — Files are group-owned by 101 so the non-root user can read them.

Green output

$ openssl req -x509 -nodes -newkey rsa:2048 -days 30 -subj "/CN=flights.example.com" -keyout tls.key -out tls.crt
$ kubectl create secret tls flights-tls -n storage --cert=tls.crt --key=tls.key
$ kubectl apply -f tls-pod.yaml
$ kubectl exec -n storage tls-check -- ls -lL /etc/tls
-r--r-----    1 root     101    1123 Oct 25 02:10 server.crt
-r--------    1 root     101    1704 Oct 25 02:10 server.key
$ kubectl exec -n storage tls-check -- df -h /etc/tls
Filesystem      Size  Used Available Use% Mounted on
tmpfs           7.6G   12K      7.6G   0% /etc/tls
$ rm tls.key

Expected result. The key is readable only with the right mode, sits on tmpfs, and was deleted from the laptop after upload.

Example 3: Build one projected folder with config, pod labels and a short-lived token

Goal. Build one projected folder with config, pod labels and a short-lived token.

projected.yaml

apiVersion: v1
kind: Pod
metadata:
  name: all-in-one
  namespace: storage
  labels:
    app: search
    team: flights
spec:
  volumes:
    - name: runtime
      projected:
        defaultMode: 0444
        sources:
          - configMap:
              name: nginx-conf
              items:
                - key: default.conf
                  path: config/default.conf
          - downwardAPI:
              items:
                - path: pod/labels
                  fieldRef:
                    fieldPath: metadata.labels
                - path: pod/namespace
                  fieldRef:
                    fieldPath: metadata.namespace
          - serviceAccountToken:
              audience: flights-api
              expirationSeconds: 3600
              path: token/flights-api
  containers:
    - name: app
      image: busybox:1.36
      command: ["sh", "-c", "sleep 3600"]
      volumeMounts:
        - name: runtime
          mountPath: /runtime
          readOnly: true
      resources:
        requests: {cpu: 10m, memory: 16Mi}
        limits: {memory: 32Mi}

What each line does

  • projected.sources — Three different sources in one mount.
  • path with folders — config/, pod/ and token/ keep things tidy.
  • downwardAPI labels — The app can read its own labels without calling the API.
  • audience: flights-api — The token is only valid for that audience.
  • expirationSeconds — kubelet refreshes the token before it expires.

Green output

$ kubectl apply -f projected.yaml
$ kubectl exec -n storage all-in-one -- find /runtime -type f -o -type l | grep -v '\.\.'
/runtime/config/default.conf
/runtime/pod/labels
/runtime/pod/namespace
/runtime/token/flights-api
$ kubectl exec -n storage all-in-one -- cat /runtime/pod/labels
app="search"
team="flights"
$ kubectl exec -n storage all-in-one -- sh -c 'cut -d. -f2 /runtime/token/flights-api | base64 -d 2>/dev/null' | head -c 120
{"aud":["flights-api"],"exp":1793336400,"iat":1793332800,"iss":"https://kubernetes.default.svc.cluster.local",

Expected result. One read-only folder now gives the app its config, its own identity, and a token that expires in an hour. The default kube-api-access volume in every pod is built the same way.

Every command, step by step (kind lab with kubectl)

  1. Create the ConfigMap

    kubectl create configmap nginx-conf -n storage --from-file=default.conf
    kubectl get cm nginx-conf -n storage -o yaml | head -8
    

    What it does: Key default.conf holds the file.

    data:
      default.conf: |
        server {
            listen 80;
    
  2. Run nginx

    kubectl apply -f nginx-cm.yaml
    kubectl exec -n storage web -- curl -s localhost/health
    

    What it does: Our config is live.

    ok v1
    
  3. See the symlink trick

    kubectl exec -n storage web -- ls -la /etc/nginx/conf.d
    

    What it does: Files point into ..data, which kubelet swaps in one step.

    lrwxrwxrwx  ..data -> ..2026_10_25_02_01_12.3417
    lrwxrwxrwx  default.conf -> ..data/default.conf
    
  4. Update and watch

    sed -i 's/v1/v2/g' default.conf
    kubectl create configmap nginx-conf -n storage --from-file=default.conf --dry-run=client -o yaml | kubectl apply -f -
    kubectl exec -n storage web -- sh -c 'while ! grep -q v2 /etc/nginx/conf.d/default.conf; do sleep 5; done; echo updated'
    

    What it does: Usually within a minute.

    updated
    
  5. Reload nginx

    kubectl exec -n storage web -- nginx -s reload
    kubectl exec -n storage web -- curl -s localhost/
    

    What it does: Now v2 is served.

    MakeMyTrip flights v2
    
  6. Try subPath

    kubectl run sub -n storage --image=busybox:1.36 --overrides='{"spec":{"volumes":[{"name":"c","configMap":{"name":"nginx-conf"}}],"containers":[{"name":"sub","image":"busybox:1.36","command":["sleep","3600"],"volumeMounts":[{"name":"c","mountPath":"/app/default.conf","subPath":"default.conf"}]}]}}'
    kubectl exec -n storage sub -- grep -c v2 /app/default.conf
    

    What it does: Shows the file at the time the pod started.

    2
    
  7. Change again and check subPath

    sed -i 's/v2/v3/g' default.conf
    kubectl create configmap nginx-conf -n storage --from-file=default.conf --dry-run=client -o yaml | kubectl apply -f -
    sleep 90; kubectl exec -n storage sub -- grep -c v3 /app/default.conf
    

    What it does: subPath never updates.

    0
    command terminated with exit code 1
    
  8. Create and mount the TLS Secret

    openssl req -x509 -nodes -newkey rsa:2048 -days 30 -subj "/CN=flights.example.com" -keyout tls.key -out tls.crt
    kubectl create secret tls flights-tls -n storage --cert=tls.crt --key=tls.key
    kubectl apply -f tls-pod.yaml
    kubectl exec -n storage tls-check -- ls -lL /etc/tls
    

    What it does: Modes 0440 and 0400.

    -r--r-----  server.crt
    -r--------  server.key
    
  9. Run the projected pod

    kubectl apply -f projected.yaml
    kubectl exec -n storage all-in-one -- cat /runtime/pod/namespace
    

    What it does: One folder, many sources.

    storage
    
  10. Make a ConfigMap immutable

    kubectl patch cm nginx-conf -n storage -p '{"immutable":true}'
    kubectl create configmap nginx-conf -n storage --from-file=default.conf --dry-run=client -o yaml | kubectl apply -f -
    

    What it does: Changes are now rejected; you must create a new name.

    The ConfigMap "nginx-conf" is invalid: data: Forbidden: field is immutable when `immutable` is set
    
  11. Clean up

    kubectl delete pod -n storage web tls-check all-in-one sub
    kubectl delete cm nginx-conf -n storage; kubectl delete secret flights-tls -n storage
    rm -f tls.key tls.crt
    

    What it does: Keep the cluster for 61.7.

    pod "web" deleted
    

When it is red 1

MountVolume.SetUp failed for volume "conf" : configmap "nginx-conf" not found

Cause. The ConfigMap is missing or in another namespace.

Fix, step by step

  1. Create it in the pod's namespace first.
  2. Or set optional: true if the app can start without it.

When it is red 2

open() "/etc/tls/server.key" failed (13: Permission denied)

Cause. The non-root user cannot read the key with mode 0400 owned by root.

Fix, step by step

  1. Set fsGroup and use 0440, or runAsUser matching the owner.

When it is red 3

Config changed but the app still uses the old value

Cause. Env vars never update, subPath never updates, or the app did not reload.

Fix, step by step

  1. Use a volume without subPath.
  2. Reload the app, or roll the Deployment with a checksum annotation.

When it is red 4

data: Forbidden: field is immutable when `immutable` is set

Cause. The object is immutable.

Fix, step by step

  1. Create a new name like nginx-conf-v4 and point the pod to it.

Practice tasks

Easy Task 1: ConfigMap

Why: Mount.

What to do: Serve nginx from a ConfigMap.

How:
  1. --from-file.
Solution
kubectl exec -n storage web -- curl -s localhost/

Easy Task 2: Files

Why: Inspect.

What to do: List the ..data symlinks.

How:
  1. ls -la.
Solution
kubectl exec -n storage web -- ls -la /etc/nginx/conf.d

Easy Task 3: Secret

Why: Mount.

What to do: Mount the TLS Secret.

How:
  1. items.
Solution
kubectl exec -n storage tls-check -- ls -lL /etc/tls

Medium Task 4: Update

Why: Live.

What to do: Update the ConfigMap and wait for the file.

How:
  1. About 60 seconds.
Solution
kubectl exec -n storage web -- cat /etc/nginx/conf.d/default.conf

Medium Task 5: Reload

Why: App.

What to do: Reload nginx after the update.

How:
  1. nginx -s reload.
Solution
kubectl exec -n storage web -- nginx -s reload

Medium Task 6: subPath

Why: Trap.

What to do: Show that subPath does not update.

How:
  1. Compare two pods.
Solution
kubectl exec -n storage sub -- cat /app/default.conf

Medium Task 7: Projected

Why: Combine.

What to do: Run the all-in-one pod.

How:
  1. find /runtime.
Solution
kubectl exec -n storage all-in-one -- find /runtime

Medium Task 8: Immutable

Why: Lock.

What to do: Make a ConfigMap immutable and try to change it.

How:
  1. Forbidden.
Solution
kubectl patch cm nginx-conf -n storage -p '{"immutable":true}'

Hard Task 9: Flights

Why: Main task.

What to do: Build the full Deployment with one projected volume.

How:
  1. mode 0400 for the key.
Solution
kubectl get deploy -n storage

Hard Task 10: Explain

Why: Interview.

What to do: Explain env vars vs volume files for config.

How:
  1. Updates.
Solution
kubectl explain pod.spec.volumes.projected

Homework: try at home

For MakeMyTrip flights, create a ConfigMap with nginx.conf and a flags.json, a TLS Secret, and a Deployment whose pods mount config and certs in one projected volume at /etc/app with the key at mode 0400. Change a flag and prove the file updated in the running pod without a restart.

Recap

  • ConfigMap and Secret volumes turn keys into files.
  • Volume files update in running pods; env vars and subPath do not.
  • Secret files live on tmpfs; set tight modes like 0400.
  • Projected volumes combine config, pod info and tokens in one folder.
  • Next: expanding volumes, snapshots and backups.

FAQ

Q. Are Secrets encrypted?

Only base64 in the API by default. Turn on encryption at rest (EKS uses KMS envelope encryption) and limit RBAC.

Q. How big can a ConfigMap be?

1 MiB. Large files belong in a volume or object storage.

Q. How do I restart pods on config change?

Put a checksum of the config in a pod template annotation, as Helm charts do, or run kubectl rollout restart.

Q. Can I use AWS Secrets Manager instead?

Yes, with the Secrets Store CSI driver or External Secrets Operator.

Q. Why is the token in a projected volume better?

It expires and is bound to an audience, unlike old long-lived service account secrets.

Q. Can a ConfigMap be optional?

Yes, optional: true lets the pod start if it is missing.

Interview questions

  1. ConfigMap as env vs as volume?

    Model answer: Env is read once at start; volume files update live, except subPath.

  2. How are Secrets mounted on the node?

    Model answer: As files on a tmpfs, so they are not written to disk.

  3. What is a projected volume?

    Model answer: A single mount combining ConfigMaps, Secrets, downward API and service account tokens.

  4. Why does subPath not update?

    Model answer: It bind-mounts a single file at start instead of following the ..data symlink.

  5. When would you use immutable ConfigMaps?

    Model answer: For large fleets, to cut API server load and prevent accidental changes.

Quick quiz

Q1. Where do Secret volume files live on the node?

On tmpfs in RAM.

Q2. Does a subPath mount get updates?

No.

Q3. Which source gives pod labels as files?

downwardAPI.

Q4. How do you set file permissions for mounted keys?

defaultMode or per-item mode.

Q5. What does immutable: true do?

Blocks changes and stops kubelet watching it.

Samajla ka?