SaveMyCert
Log in
5 of 5 free questions left today·for 10 a day
Container Orchestration

Kubernetes Storage: Volumes, PersistentVolumes, PVCs, and CSI

15 min readKCNA · Container OrchestrationUpdated

Kubernetes storage is a layered answer to one problem: a container's filesystem is ephemeral, so anything written inside it vanishes when the container is replaced. Volumes attach storage to a Pod for the Pod's lifetime; PersistentVolumes (PV) and PersistentVolumeClaims (PVC) decouple storage from any single Pod so data survives rescheduling; StorageClasses add dynamic provisioning so the cluster creates storage on demand; and the Container Storage Interface (CSI) is the standard plugin API that lets any storage vendor supply the actual disks. KCNA tests this stack at recognition depth: which object requests storage versus supplies it, what the four access modes permit, what Retain and Delete reclaim policies do, and how a StatefulSet gives each replica its own claim. This lesson works up the stack layer by layer - ephemeral volumes, the PV/PVC binding model, access modes and reclaim policies, StorageClasses, CSI, and finally stateful workloads - so each acronym lands as a concrete role.

What you’ll learn
  • Explain why container filesystems are ephemeral and how spec.volumes and per-container volumeMounts deliver storage
  • Identify what emptyDir, configMap, secret, and hostPath volumes are used for, and why sensitive values belong in a Secret
  • Describe the PersistentVolume and PersistentVolumeClaim model, how claims bind to volumes, and how namespaces scope them
  • Name the four access modes and their abbreviations, and state exactly what ReadWriteOnce permits
  • Contrast the Retain and Delete reclaim policies, including the StorageClass default
  • Explain what StorageClasses, volume binding modes, dynamic provisioning, and CSI each contribute, and how StatefulSets use volumeClaimTemplates

Why Kubernetes separates storage from Pods

A container's writable filesystem lives exactly as long as the container. When a container crashes and the kubelet restarts it, the replacement starts from the image again and everything written at runtime is gone. Pods add a second cliff: when a Pod is deleted or rescheduled to another node, even storage tied to the Pod disappears with it. Any real application - a database, a message queue, anything with user data - needs its data to outlive both events, and that is the problem the Kubernetes storage stack solves in layers.

The layers are worth naming up front, because every storage question on the exam is really asking which layer does what. A Volume is storage declared in a Pod spec and mounted into its containers; it outlives container restarts but, for the ephemeral types, not the Pod itself. A PersistentVolume (PV) is a piece of storage that exists as a cluster resource in its own right, independent of any Pod. A PersistentVolumeClaim (PVC) is a request for such storage that a Pod consumes by name. A StorageClass describes a type of storage the cluster can create on demand, enabling dynamic provisioning. And the Container Storage Interface (CSI) is the plugin standard through which external storage drivers actually attach and mount the disks.

LayerObjectRoleLifetime
In the PodVolumeMountable storage declared in the Pod specEphemeral types die with the Pod
Cluster resourcePersistentVolumeThe supplied storage itselfIndependent of any Pod
Namespaced requestPersistentVolumeClaimA request that binds to a PVUntil the claim is deleted
Provisioning templateStorageClassRecipe for creating PVs on demandCluster configuration
Driver interfaceCSIStandard API for storage vendorsCluster infrastructure

Declaring and mounting a volume

A volume is defined once, at Pod level, in spec.volumes, and each container that needs it mounts it with its own volumeMounts entry (volume name plus mountPath). The two halves are separate on purpose. A volume is visible only to the containers that list it in their own volumeMounts: a second container with no mount entry for it sees nothing at that path, even though the volume exists in the same Pod. Options such as readOnly: true are also set per mount, so one container can write a volume that another only reads.

Two look-alikes do not give you a Kubernetes volume. A Dockerfile VOLUME instruction is image metadata, not an entry in spec.volumes; whatever the runtime does with it, it is not a Kubernetes volume and gives no persistence beyond the container. And writing to a path in the container's own filesystem, without a volume mounted there, is lost when the container is replaced.

Ephemeral volumes: emptyDir, configMap, secret, and hostPath

Not all storage needs to persist. Kubernetes ships several volume types whose lifetime is deliberately tied to the Pod, and the exam expects you to recognize what each is for.

An emptyDir volume is created empty when the Pod is assigned to a node and deleted when the Pod is removed. It survives container crashes and restarts within the Pod, which makes it the standard scratch space: caches, temporary processing files, and - importantly - a shared directory between containers in the same Pod, such as an app writing files that a sidecar ships elsewhere. A configMap volume projects the keys of a ConfigMap as read-only files inside the container - one file per key, named after the key and holding its value - the usual way to deliver configuration files without baking them into the image. A secret volume does the same for Secrets - credentials, TLS certificates. Mounting is not the only way in: a container can also take individual keys as environment variables (env with valueFrom) or all keys at once (envFrom). Mounted files are refreshed when the object changes (except when mounted with subPath); environment variables are fixed when the container starts. A hostPath volume mounts a directory from the node's own filesystem into the Pod. It has legitimate niche uses, mostly for node-level agents that need to read host logs or sockets, but it couples the Pod's data to one specific node and opens access to the host, so it is flagged as a security risk and avoided for ordinary applications.

Volume typeBacked byTypical useCaveat
emptyDirNode disk or memoryScratch space; sharing files between containers in a PodDeleted when the Pod goes away
configMapA ConfigMap objectConfiguration filesRead-only projection
secretA Secret objectCredentials and certificates as filesRead-only projection
hostPathThe node's filesystemNode agents reading host pathsNode-tied and a security risk

Why sensitive values belong in a Secret

A Secret, not a ConfigMap, is the object for passwords, tokens and keys, and the reasons are more than the name. Secrets can be governed by their own RBAC rules, separate from ConfigMaps; the cluster can be configured to encrypt them at rest in etcd; and a secret volume is backed by tmpfs (memory), so the values are not written to the node's disk. Know the limit too: by default Secret data is only base64-encoded, which is an encoding, not encryption - anyone allowed to read the Secret can decode it, and encryption at rest must be turned on deliberately. Security controls around Secrets are covered in the Kubernetes security lesson.

hostPath in practice

The canonical legitimate use of hostPath is a node agent run as a DaemonSet - for example a log shipper that mounts the node's /var/log so each copy reads the logs of the node it runs on. The flip side shows why ordinary apps should avoid it: hostPath data lives on one node's disk. If a Pod writes, say, a cache directory to a hostPath and is later rescheduled to another node (for example after the original node fails), the new Pod mounts the same path on the new node and finds none of the earlier files; nothing was deleted, they are simply on the other machine.

The common thread: none of these give an application durable data. For that, Kubernetes switches from volumes declared inline in a Pod to storage managed as its own resource - the subject of the next section.

PersistentVolumes and PersistentVolumeClaims: supply meets demand

Kubernetes splits persistent storage into two objects on purpose. A PersistentVolume is the supply side: a cluster-scoped resource representing an actual piece of storage - a cloud disk, an NFS export, a SAN LUN - with a capacity, access modes, and a reclaim policy. PVs are created by an administrator in advance (static provisioning) or by the cluster on demand (dynamic provisioning). A PersistentVolumeClaim is the demand side: a namespaced object in which an application requests storage by size, access mode, and optionally StorageClass, without knowing or caring what technology sits underneath. The separation is the same consumer/provider split as Pods and nodes: developers ask for what they need, infrastructure supplies it.

The two meet through binding. The control plane matches a pending PVC to a suitable PV - enough capacity, compatible access mode, matching class - and binds them one-to-one; a bound PV belongs to exactly one claim, so a second claim in any namespace cannot bind it and needs another PV. A claim receives the whole PV it binds to: a 2Gi request matched to an 8Gi volume gets all 8Gi, and the unused capacity is not shared with anyone else. The Pod then consumes the claim by name:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: data-claim
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi
---
apiVersion: v1
kind: Pod
metadata:
  name: db
spec:
  containers:
  - name: postgres
    image: postgres:16
    volumeMounts:
    - name: data
      mountPath: /var/lib/postgresql/data
  volumes:
  - name: data
    persistentVolumeClaim:
      claimName: data-claim

Notice what the Pod references: the claim, never the PV. If the Pod dies and is recreated on another node, it mounts the same claim and finds its data intact - persistence is achieved because the storage's lifecycle is anchored to the PVC and PV, not to the Pod. One scoping fact recurs on the exam: PVCs are namespaced, PVs are cluster-scoped - which is why kubectl get pv takes no namespace flag while kubectl get pvc -n <ns> does. It also limits which claim a Pod can use: a claimName is resolved only in the Pod's own namespace. If the claim exists only in another namespace, the Pod stays Pending with an event saying the persistentvolumeclaim was not found; the fix is a claim in the Pod's namespace, not a cross-namespace reference.

Access modes: what ReadWriteOnce actually means

Every PV and PVC declares access modes, which describe how the storage can be mounted. There are exactly four, each with a standard abbreviation, and inventing a fifth is a favorite distractor.

ModeAbbreviationWhat it allowsTypical backing storage
ReadWriteOnceRWORead-write mount by a single nodeCloud block disks (one attachment at a time)
ReadOnlyManyROXRead-only mount by many nodesShared reference data
ReadWriteManyRWXRead-write mount by many nodesFile and network storage such as NFS
ReadWriteOncePodRWOPRead-write mount by a single Pod in the whole clusterCSI volumes needing exclusive access

The trap the exam sets is in the word Once. ReadWriteOnce restricts the mount to one node, not one Pod. Several Pods scheduled onto that same node can all use an RWO volume simultaneously. If you genuinely need to guarantee that only one Pod anywhere in the cluster can write, that is ReadWriteOncePod, a newer mode supported only by CSI volumes. Keep the pair straight: RWO = one node, RWOP = one Pod.

What happens when RWO is exceeded? A cloud block disk can attach to only one node at a time. If a second Pod using the same RWO claim is scheduled to a different node, it cannot attach the volume: it stays in ContainerCreating and its events show a Multi-Attach error (the volume is already exclusively attached to another node). A Pod on the node that already holds the volume mounts it fine. The abbreviations matter in practice too: kubectl get pv and kubectl get pvc print access modes in their ACCESS MODES column as RWO, ROX, RWX and RWOP, not the full names.

Two supporting facts round out the picture. A PV may list several supported access modes, but it is mounted with only one mode at a time. And access modes are a matching constraint during binding: a claim requesting ReadWriteMany can only bind to a PV whose storage actually supports it, which is why RWX is common on NFS-style file storage and generally unavailable on plain cloud block disks. When a scenario mentions many Pods on different nodes writing to one shared volume, the answer is RWX and file or network storage underneath.

Reclaim policies: what happens to data when the claim goes away

Deleting a Pod never deletes its PVC, and deleting a PVC does not automatically mean the data is destroyed - what happens next is decided by the PV's persistentVolumeReclaimPolicy. Two policies matter.

Retain keeps everything. When the claim is deleted, the PV is not removed and its data is untouched; the volume moves to the Released state, but it is not made available to a new claim automatically, because it still contains the previous owner's data. An administrator must intervene before the storage is reused: either delete the PV object (the backing disk and its data remain) and create a new PV for the same storage after backing up or scrubbing the data, or edit the PV to clear its claimRef so it returns to Available and can bind a new claim - with the old data still on it. Retain is the safety-first choice for data you cannot afford to lose to an accidental deletion.

Delete removes the PV object and the underlying storage asset in the external infrastructure - the cloud disk itself is deleted along with the claim. This is the automation-first choice and the default for dynamically provisioned volumes: a dynamically provisioned PV inherits the reclaimPolicy of its StorageClass, and a StorageClass that sets no reclaimPolicy defaults to Delete. (A manually created PV defaults to Retain.) So deleting and recreating a claim on such a class does not get the data back - the old PV and disk are already gone, and the new claim gets a fresh, empty volume. The convenience cuts both ways: deleting the wrong PVC can destroy real data, which is exactly why the distinction is exam-worthy.

  • Need the data to survive claim deletion, with manual cleanup? Retain.
  • Want storage to be created and destroyed automatically with the claim? Delete.
  • A third historical policy, Recycle, performed a basic wipe for reuse; it is deprecated, and recognizing it as obsolete is all KCNA requires.

A last vocabulary anchor: a PV's status walks through Available (unbound), Bound (attached to a claim), and Released (claim deleted, awaiting reclaim) - and with Retain, Released is where it waits for a human.

StorageClasses and dynamic provisioning

Static provisioning has an obvious scaling problem: an administrator must guess demand and hand-create PVs ahead of every claim. Dynamic provisioning removes the guesswork. A StorageClass is a cluster-scoped object that describes a kind of storage the cluster can manufacture: which provisioner (storage driver) to call, with what parameters (disk type, performance tier, replication), and what reclaim policy the resulting PVs get. When a PVC names a StorageClass and no suitable PV exists, the provisioner creates a new volume in the backing storage system, wraps it in a PV, and binds it to the claim - supply generated to order.

A cluster typically offers several classes as a menu: a fast SSD class for databases, a cheaper standard class for bulk data, an RWX-capable file storage class for shared volumes. One class can be marked as the default StorageClass, which is applied to any PVC that does not specify one - the reason storage appears by magic on managed clouds: EKS, AKS, and GKE ship a default class wired to their disk services, so a bare PVC just works. A PVC can also opt out of dynamic provisioning entirely by setting an empty class name, forcing a bind to a pre-created PV.

One refinement is worth recognizing: a StorageClass's volumeBindingMode. The default, Immediate, provisions and binds as soon as the claim is created. WaitForFirstConsumer delays provisioning until a Pod actually uses the claim, so the volume can be created in the same availability zone the scheduler picks for the Pod - avoiding the classic failure where a disk exists in zone A and the Pod lands in zone B. The binding mode explains two symptoms. On a WaitForFirstConsumer class, a new PVC stays Pending until a Pod that uses it is scheduled - expected behaviour, not an error (its events say it is waiting for the first consumer). On an Immediate class with zonal disks, the disk may be created in a zone where the Pod cannot run - for example because the Pod's scheduling constraints or available capacity put it in another zone - and the Pod stays Pending with a volume node affinity conflict; switching to a WaitForFirstConsumer class prevents it. A StorageClass's reclaimPolicy field, if unset, defaults to Delete (see the reclaim section). For the exam, the core equation is enough: StorageClass plus PVC equals a PV created on demand.

The Container Storage Interface (CSI)

Someone has to actually create the cloud disk, attach it to the right node, format it, and mount it into the Pod. Historically that code lived in-tree: volume plugins for each vendor were compiled into Kubernetes itself, so adding or fixing a storage integration meant changing and re-releasing Kubernetes core. The Container Storage Interface (CSI) ended that. CSI is an open standard API between container orchestrators and storage systems: a vendor implements a CSI driver once, ships it as its own deployable component running in the cluster, and Kubernetes calls the standard interface to provision, attach, mount, and unmount volumes. Drivers now develop and release on their own schedule, out of tree.

You can recognize CSI in the wild by names like ebs.csi.aws.com or pd.csi.storage.gke.io appearing as the provisioner in a StorageClass. The old in-tree cloud volume plugins have been migrated to CSI drivers, and new storage capabilities land in CSI first - features from earlier sections, such as the ReadWriteOncePod access mode and much of volume snapshotting and expansion, exist only through CSI drivers.

For KCNA, place CSI beside its siblings and its role becomes obvious. Kubernetes standardizes its extension points as interfaces: CRI for container runtimes, CNI for networking, and CSI for storage. A question offering those three as options and asking which one storage vendors implement is testing exactly this mapping. CSI is not a volume type you pick in a Pod spec and not a competitor to PVs and PVCs - it is the plumbing layer that StorageClass provisioners and PV attach/mount operations call into.

Stateful workloads: StatefulSets and volumeClaimTemplates

The stack comes together in stateful applications. A Deployment treats its Pods as interchangeable, and if you attach one PVC to a Deployment, every replica tries to share that single claim - unworkable for a database where each member needs its own disk. A StatefulSet solves this with volumeClaimTemplates: a template from which a PVC is stamped out for each replica, paired with the replica's stable identity. A StatefulSet named db with three replicas and a template named data yields Pods db-0, db-1, db-2 and claims data-db-0, data-db-1, data-db-2 - each Pod permanently paired with its own claim.

Walk the full lifecycle for one replica. The StatefulSet creates db-1 and its PVC data-db-1. The PVC names a StorageClass, so the CSI provisioner creates a disk, wraps it in a PV, and binds it. The scheduler places the Pod (with WaitForFirstConsumer, the disk is created in the Pod's zone), the driver attaches and mounts the volume, and the database writes to it. Now the node dies. The Pod is recreated - same name, db-1 - on another node, mounts the same PVC, and finds every byte it wrote. Scaling the StatefulSet down deletes Pods but, by default, keeps their PVCs: the StatefulSet's persistentVolumeClaimRetentionPolicy defaults to whenScaled: Retain and whenDeleted: Retain. Scaling back up recreates db-2, which reattaches the very same claim data-db-2 by name - the original volume with its earlier data, not a copy or a fresh disk. Deleting the claims is a deliberate separate act (or a policy changed to Delete).

Contrast the anti-pattern: a Deployment whose Pod template mounts one RWO claim backed by a block disk. Every replica shares that single claim, so replicas on other nodes stick in ContainerCreating with a Multi-Attach error - and even when they share a node, they share one disk instead of each having its own. Giving each replica its own durable storage is exactly what a StatefulSet's volumeClaimTemplates provide.

That is the whole point of the storage stack in one scenario: the Pod is disposable, the claim endures, the class manufactured the volume, and CSI did the physical work. If you can narrate that chain - and name which object plays which part - you can answer any KCNA storage question by locating it in the chain.

Tip. KCNA probes storage as role recognition: which object requests storage (PVC) versus supplies it (PV), what a StorageClass adds (dynamic provisioning), and which interface vendors implement (CSI, offered beside CRI and CNI as distractors). Expect the access-mode abbreviations RWO, ROX, RWX, and RWOP as kubectl prints them, with the one-node-versus-one-Pod distinction for ReadWriteOnce as the standard trap, and Retain versus Delete reclaim behavior including the StorageClass default. Scenario items describe a symptom - a Pod Pending on a claim in another namespace, a PVC waiting for its first consumer, a Multi-Attach error, hostPath data missing after a reschedule - and ask for the cause. Trigger words include emptyDir, hostPath, volumeMounts, Secret, binding, Released, default StorageClass, WaitForFirstConsumer, and volumeClaimTemplates.

Key takeaways
  • A Volume is declared once in spec.volumes and is visible only to containers that mount it via their own volumeMounts; emptyDir survives container restarts but not Pod deletion.
  • Secrets are the object for sensitive values (own RBAC, optional encryption at rest, tmpfs-backed volumes), but their data is only base64-encoded by default; ConfigMap and Secret keys can also be consumed as environment variables.
  • PersistentVolumes are the cluster-scoped supply of storage; PersistentVolumeClaims are namespaced requests that bind to them one-to-one (receiving the whole PV), and a Pod references a claim in its own namespace, never the PV.
  • The four access modes are ReadWriteOnce (RWO), ReadOnlyMany (ROX), ReadWriteMany (RWX), and ReadWriteOncePod (RWOP) - the abbreviations kubectl prints.
  • ReadWriteOnce limits the volume to one node, not one Pod - a Pod on a second node hits a Multi-Attach error; only ReadWriteOncePod guarantees a single Pod, and it requires CSI.
  • Reclaim policy Retain keeps the PV and its data (status Released, manual cleanup); Delete removes the PV and the underlying storage, and is the default when a StorageClass sets no reclaimPolicy.
  • A StorageClass defines a provisioner and parameters; a PVC referencing it (or the default class) triggers dynamic provisioning, and WaitForFirstConsumer delays it until a Pod is scheduled so zonal disks land in the right zone.
  • CSI is the standard interface storage vendors implement, alongside CRI for runtimes and CNI for networking; it replaced in-tree volume plugins.
  • StatefulSet volumeClaimTemplates create one PVC per replica (like data-db-0); the default retention policy (Retain on scale-down and delete) keeps them, so a recreated replica reattaches its own claim.

Frequently asked questions

What is the difference between a PersistentVolume and a PersistentVolumeClaim?

A PersistentVolume (PV) is the supply side: a cluster-scoped resource representing actual storage such as a cloud disk or NFS export, created by an administrator or by dynamic provisioning. A PersistentVolumeClaim (PVC) is the demand side: a namespaced request in which an application asks for storage by size, access mode, and class. Kubernetes binds a claim to a matching PV one-to-one, and Pods mount storage by referencing the claim, never the PV directly.

Is data in a Kubernetes Secret encrypted?

Not by default. Secret values are stored base64-encoded, which is an encoding anyone with read access can reverse, not encryption. Secrets are still the right object for sensitive data because they can be protected by their own RBAC rules, the cluster can be configured to encrypt them at rest in etcd, and secret volumes are backed by tmpfs so values are not written to the node's disk. Encryption at rest has to be enabled deliberately by the cluster administrator.

Does ReadWriteOnce mean only one Pod can use the volume?

No. ReadWriteOnce (RWO) means the volume can be mounted read-write by a single node, so multiple Pods running on that same node can all use it at once. If you need a guarantee that only one Pod in the entire cluster can mount the volume read-write, that is the separate ReadWriteOncePod (RWOP) access mode, which is only available through CSI volumes. Confusing one node with one Pod is the classic exam trap on access modes.

What happens to my data when I delete a PersistentVolumeClaim?

It depends on the PersistentVolume's reclaim policy. With Retain, the PV and its data are kept: the volume moves to the Released state and does not bind a new claim until an administrator deletes and recreates the PV or clears its claimRef. With Delete, the PV object and the underlying storage asset, such as the cloud disk itself, are removed automatically. Dynamically provisioned PVs take their StorageClass's reclaimPolicy, which defaults to Delete when unset, so deleting a PVC there really can destroy the data.

Why is my PersistentVolumeClaim stuck in Pending?

A Pending PVC is not always a fault. If its StorageClass uses volumeBindingMode WaitForFirstConsumer, the claim deliberately stays Pending until a Pod that uses it is scheduled, so the volume can be created in that Pod's zone. Otherwise, a claim stays Pending when no existing PV matches its size, access mode and class and no StorageClass can provision one. A Pod, by contrast, stays Pending if its claimName refers to a claim that does not exist in the Pod's own namespace.

What is dynamic provisioning in Kubernetes?

Dynamic provisioning means Kubernetes creates storage on demand instead of an administrator pre-creating PersistentVolumes. A StorageClass defines which provisioner to use and with what parameters; when a PersistentVolumeClaim references that class and no matching PV exists, the provisioner creates a new volume in the backing storage system, wraps it in a PV, and binds it to the claim. A cluster can mark one class as the default, which is applied to any PVC that does not name a class.

What is the Container Storage Interface (CSI)?

CSI is the open standard API between container orchestrators like Kubernetes and storage systems. Storage vendors implement a CSI driver once and run it in the cluster as their own component, and Kubernetes calls the standard interface to provision, attach, mount, and unmount volumes. It replaced the old in-tree volume plugins that had to be compiled into Kubernetes itself. It sits alongside CRI (container runtimes) and CNI (networking) as one of Kubernetes' three main plugin interfaces.

How does a StatefulSet give each replica its own storage?

Through volumeClaimTemplates. The StatefulSet stamps out one PersistentVolumeClaim per replica from the template, named after the replica's stable identity, for example data-db-0 for Pod db-0. Each Pod always mounts its own claim, so a replica rescheduled to another node reattaches to the same data. By default those PVCs are kept even when the StatefulSet scales down, so scaling back up reunites each replica with its previous volume.

Source

This lesson covers the "Container Orchestration" domain of the official KCNA exam guide. Vendors revise their guides — check the source for the current version.

Test yourself on this topic
Practice now

Sign up free to mark lessons complete, bookmark topics and track your exam readiness.

Spotted a mistake in this lesson?