Volumes and volume types

Why a container needs a volume, the volume types the exam expects you to write from memory, and how two containers in one Pod share a directory.

Storage · 10%30 min2 tasks

After this lesson

  • 01You can write a Pod with emptyDir, hostPath, configMap, secret and projected volumes without looking up where volumeMounts goes.
  • 02You can share one volume between two containers and prove from inside the Pod that it works.
  • 03You can explain why hostPath is a security risk, where the control plane still depends on it, and how to inspect the result on the node.

Storage is the smallest domain on the CKA at 10%, and this lesson covers the volume-type half of the competency Configure volume types, access modes and reclaim policies. Volume questions are quick points when you can type the YAML from memory: a Pod with an emptyDir shared by two containers, a hostPath pinned to one node, a ConfigMap mounted as files. They cost minutes when you have to look up where volumeMounts goes. The grader never sees your typing; it reads the Pod spec and, for hostPath, the file on the node.

Why a container needs a volume

A container's writable layer lives exactly as long as the container. When it restarts after a crash or a failed liveness probe, every file it wrote is gone. Two containers in the same Pod each get their own filesystem, so they cannot hand each other files through a plain directory either. A volume solves both: it is declared once at Pod level in spec.volumes, it outlives container restarts, and any container in the Pod can mount it with a volumeMounts entry that names the volume and a mountPath. Whether the data outlives the Pod depends on the volume type.

Volume typeData lives as long asBacked byTypical use
emptyDirThe Pod stays on its node (survives container restarts)Node disk, or RAM with medium: MemoryScratch space, caches, sidecar hand-off
configMap, secret, downwardAPI, projectedThe PodAPI objects, kept in sync by the kubeletConfig files, certificates, tokens, Pod metadata
hostPathThe nodeA path on that node's filesystemStatic control plane Pods, node agents
persistentVolumeClaimBeyond the PodA PersistentVolume (next lesson)Databases, anything that must survive rescheduling

emptyDir

An emptyDir is created empty on the node when the Pod is bound to it and deleted when the Pod is removed from that node. By default it is a directory on the node's disk and counts against the Pod's ephemeral-storage limit. With medium: Memory it becomes a tmpfs: faster, lost on node reboot, and charged against the container's memory limit. sizeLimit caps the volume; if the kubelet finds it exceeded, the Pod is evicted. Both fields are optional, and emptyDir: {} is the whole definition for the plain case.

Two emptyDir volumes: one on disk with a cap, one in memoryyaml
apiVersion: v1
kind: Pod
metadata:
  name: scratch
  namespace: st-vol
spec:
  containers:
    - name: app
      image: busybox:1.36
      command: ["sh", "-c", "sleep 3600"]
      volumeMounts:
        - name: scratch
          mountPath: /scratch
        - name: fast
          mountPath: /fast
  volumes:
    - name: scratch
      emptyDir:
        sizeLimit: 256Mi
    - name: fast
      emptyDir:
        medium: Memory
        sizeLimit: 64Mi

hostPath

A hostPath volume mounts a file or directory from the node into the container. The optional type says what the kubelet checks before mounting: left empty, nothing is checked; DirectoryOrCreate creates a missing directory (mode 0755, owned like the kubelet process); Directory, File, FileOrCreate, Socket, CharDevice and BlockDevice require exactly that kind of path to exist. For a directory your Pod will write to, DirectoryOrCreate is the usual answer. The data is node-local: a Pod rescheduled to another node sees a different, probably empty, directory, so pin such Pods with nodeName or a nodeSelector. To check the result, open a shell on the node. On the lab that is docker exec cka-worker ls /opt/node-writer; on the exam it is ssh cka-worker followed by the same ls.

hostPath is the volume type that breaks the container boundary. A Pod that mounts /, /etc or the container runtime socket can read every credential on the node, rewrite the kubelet's files or start containers outside Kubernetes, which is why the baseline and restricted Pod Security Standards forbid it and why shared clusters block it with admission policy. Where it remains the right tool is the control plane itself: on a kubeadm cluster etcd, kube-apiserver, kube-controller-manager and kube-scheduler run as static Pods whose manifests in /etc/kubernetes/manifests mount /etc/kubernetes/pki and /var/lib/etcd as hostPath volumes. Node agents installed as DaemonSets (the CNI, log shippers, node exporters) use it for the same reason. Look at docker exec cka-control-plane cat /etc/kubernetes/manifests/etcd.yaml and you will find both.

A Pod pinned to cka-worker that writes into a node directoryyaml
apiVersion: v1
kind: Pod
metadata:
  name: node-writer
  namespace: st-vol
spec:
  nodeName: cka-worker
  containers:
    - name: writer
      image: busybox:1.36
      command: ["sh", "-c", "date > /data/started; sleep 3600"]
      volumeMounts:
        - name: data
          mountPath: /data
  volumes:
    - name: data
      hostPath:
        path: /opt/node-writer
        type: DirectoryOrCreate

Volumes built from API objects

configMap and secret volumes turn every key into a file named after the key under mountPath. items picks a subset and chooses file names, defaultMode sets permissions (0400 for private keys), and optional: true lets the Pod start when the object does not exist. The kubelet rewrites the files when the object changes, with a delay of up to its sync period, except for mounts that use subPath, which never update. A downwardAPI volume exposes Pod fields such as labels, annotations and resource limits as files. A projected volume merges several of these sources into one directory and is the only place you can ask for a short-lived, audience-bound serviceAccountToken.

The volumes block of a Pod that mounts configurationyaml
volumes:
  - name: settings
    configMap:
      name: app-settings
      items:
        - key: app.properties
          path: app.properties
  - name: tls
    secret:
      secretName: app-tls
      defaultMode: 0400
  - name: bundle
    projected:
      sources:
        - configMap:
            name: app-settings
        - secret:
            name: app-tls
        - downwardAPI:
            items:
              - path: labels
                fieldRef:
                  fieldPath: metadata.labels
        - serviceAccountToken:
            path: token
            expirationSeconds: 3600
            audience: api

volumeMounts and sharing a volume between containers

Every mount needs a name from spec.volumes and a mountPath. Mounting over a directory hides whatever the image put there, which is how /usr/share/nginx/html gets replaced in the first task. subPath mounts a single file or subdirectory from the volume instead of the whole thing, so one config file can land in /etc/nginx/conf.d/ without hiding the rest of that directory. readOnly: true is enforced per mount. Two containers share a volume by naming the same entry in their own volumeMounts; the mountPath can differ per container, and one side can be read-only. This is the sidecar pattern: one container produces a log, a rendered page or a fetched artifact, the other consumes it. Because containers in a Pod also share a network namespace, the producer can test the consumer over localhost, which is exactly how the first task checks itself.

In the docs · the tab you get on exam day

Taskst-vol-1
Storage4%~7 min

context · kubectl config use-context kind-cka

Two containers sharing an emptyDir

In namespace st-vol create a Pod named shared-web with two containers that share an emptyDir volume named content limited to 64Mi. Container writer uses image busybox:1.36, mounts the volume at /out and runs sh -c 'echo hello from writer > /out/index.html; while true; do sleep 3600; done'. Container web uses image nginx:1.27 and mounts the same volume read-only at /usr/share/nginx/html. When the Pod is running, a request to http://localhost/ from inside the writer container must return the line that writer wrote.
  1. 01

    Set up the scenario

    curl -fsSL https://codewithmuh.com/labs/cka/t/st-vol-1/setup | bash
  2. 02

    Solve it in your terminal

    Work the way the exam makes you: kubectl, the docs, nothing else. Put any answer files under /tmp/cka/.

  3. 03

    Check your work

    curl -fsSL https://codewithmuh.com/labs/cka/t/st-vol-1/verify | bash
    Counts toward your readiness score.
  4. 04

    Clean up

    curl -fsSL https://codewithmuh.com/labs/cka/t/st-vol-1/cleanup | bash
Taskst-vol-2
Storage4%~6 min

context · kubectl config use-context kind-cka

A hostPath volume on a specific node

Create a Pod named node-logger in namespace st-vol that runs on node cka-worker. Mount the node directory /opt/st-logs into the container at /host-logs through a hostPath volume named logs; the directory must be created on the node if it does not exist yet. The container uses image busybox:1.36 and runs sh -c 'echo node-logger was here > /host-logs/marker.txt; while true; do sleep 3600; done'. When you are done, /opt/st-logs/marker.txt must exist on cka-worker.
  1. 01

    Set up the scenario

    curl -fsSL https://codewithmuh.com/labs/cka/t/st-vol-2/setup | bash
  2. 02

    Solve it in your terminal

    Work the way the exam makes you: kubectl, the docs, nothing else. Put any answer files under /tmp/cka/.

  3. 03

    Check your work

    curl -fsSL https://codewithmuh.com/labs/cka/t/st-vol-2/verify | bash
    Counts toward your readiness score.
  4. 04

    Clean up

    curl -fsSL https://codewithmuh.com/labs/cka/t/st-vol-2/cleanup | bash

Finished reading?

0 of 2 tasks passed. Readiness counts tasks, so run them before you move on.