Skip to content

Notes08-Storage

Storage

How a Pod gets a disk: ephemeral volumes, PersistentVolumes, claims, access modes, reclaim policy, StorageClass, and the CSI drivers that back them.

Updated Oct 1, 2026

Storage Quick Notes





What survives a restart


Lifetime

Examples

Gone when the container restarts

The container’s writable layer

Gone when the Pod is gone

emptyDir

Stays on that node

hostPath

Stays, and can move with the Pod

PersistentVolume backed by a network disk or CSI volume

A Deployment that needs real data uses a PVC. emptyDir is for cache and for sharing files between containers in the same Pod.

Volume types you will type

  • emptyDir: Created when the Pod is scheduled. Scratch space shared by that Pod’s containers. medium: Memory uses tmpfs.
  • hostPath: Mounts a file or directory from the node. The Pod is then tied to nodes that have that path. Static Pods and control-plane manifests use hostPath for /etc/kubernetes/pki and /var/lib/etcd. Workload Pods should prefer a PVC.
  • configMap / secret: Project a ConfigMap or Secret as files in the container. Keys become filenames unless items renames them. Env vars from the same objects are copied only at container start. A volume mount of the object (without subPath) is updated by the kubelet when the object changes. The process has to re-read the file.


yaml
volumes:
- name: cfg
  configMap:
    name: web-config
containers:
- name: app
  volumeMounts:
  - name: cfg
    mountPath: /etc/app


  • projected: One mount that combines a ServiceAccount token, ConfigMap, Secret, and downward API.
  • nfs: server and path. The NFS server must already exist. Access from multiple nodes is why people pair it with ReadWriteMany.
  • persistentVolumeClaim: The volume source inside a Pod. It names a PVC. The Pod does not name the PV.


yaml
volumes:
- name: cache
  emptyDir: {}
- name: data
  persistentVolumeClaim:
    claimName: web-data
containers:
- name: app
  image: nginx:1.27
  volumeMounts:
  - name: cache
    mountPath: /var/cache/nginx
  - name: data
    mountPath: /usr/share/nginx/html


mountPath is the path inside the container. The volume name links the two lists.

PersistentVolumes and claims

  • PersistentVolume (PV): A piece of storage in the cluster. Cluster-scoped. Either an admin created it (static) or a provisioner created it because a claim asked (dynamic).
  • PersistentVolumeClaim (PVC): A namespaced request: “give me 5Gi, ReadWriteOnce, from this StorageClass.”
  • Binding: The control loop matches a Pending claim to a PV that fits capacity, access modes, and class. A smaller claim can bind to a larger PV. The extra space is not split. One PV binds to one PVC.

Phases:

Object

Phases you will see

PV

Available → Bound → Released (claim deleted, volume kept) → or back to available after reclaim. Failed if reclaim could not finish

PVC

Pending (nothing fits, or the provisioner has not finished) → Bound


bash
kubectl get pv,pvc -A
kubectl describe pvc web-data


A Pending PVC’s events say which requirement failed: class, size, access mode, or WaitForFirstConsumer waiting for a Pod.


Access modes

Mode

Short

Meaning

ReadWriteOnce

RWO

One node may mount it read-write. All Pods using it must be on that node

ReadOnlyMany

ROX

Many nodes, read-only

ReadWriteMany

RWX

Many nodes, read-write (NFS, some filers, some CSI drivers)

ReadWriteOncePod

RWOP

Only one Pod in the whole cluster may mount it read-write

The mode is a request. The backend must actually support it. Asking for RWX on a disk that is RWO leaves the claim Pending.


Reclaim policy

What happens to the PV after the PVC is deleted:

Policy

Result

Delete

The PV object and the underlying disk are removed. Default for many dynamic classes

Retain

The PV stays Released. The disk is kept. An admin reclaims it (delete the PV object, clean the disk, create a new PV)

Recycle

Retired. Do not use it

Retain is what you want when deleting a claim must not destroy the only copy of the data. Change it on the StorageClass (reclaimPolicy) or on an existing PV (spec.persistentVolumeReclaimPolicy).


Static PV example


yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: web-pv
spec:
  capacity:
    storage: 5Gi
  accessModes: ["ReadWriteOnce"]
  persistentVolumeReclaimPolicy: Retain
  storageClassName: manual
  hostPath:
    path: /mnt/data
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: web-data
  namespace: shop
spec:
  storageClassName: manual
  accessModes: ["ReadWriteOnce"]
  resources:
    requests:
      storage: 5Gi


  • storageClassName must match on the PV and the PVC, including the empty string. A PVC with no class binds only to a PV with no class. A PVC that names manual ignores PVs in class standard.
  • storageClassName: "" on a PVC opts out of the default class.


StorageClass and dynamic provisioning

A StorageClass is a template for the provisioner. Creating a PVC that names the class causes a controller to create the disk and the PV for you.


yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast
  annotations:
    storageclass.kubernetes.io/is-default-class: "true"
provisioner: ebs.csi.aws.com
reclaimPolicy: Delete
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumer
parameters:
  type: gp3


Field

Why it matters

provisioner

The CSI driver name (kubernetes.io/no-provisioner means you bind PVs yourself)

reclaimPolicy

Applied to PVs this class creates

allowVolumeExpansion

Lets you edit the PVC request upward. The driver resizes the disk. File-system grow may need the Pod to be restarted

volumeBindingMode: Immediate

PV is created as soon as the PVC exists. Wrong zone is a classic failure

volumeBindingMode: WaitForFirstConsumer

Binding waits until a Pod uses the claim, so the disk is created in that Pod’s zone

Default class

The annotation above. A PVC that omits storageClassName uses this class. Only one default


bash
kubectl get storageclass
kubectl patch pvc web-data -p '{"spec":{"resources":{"requests":{"storage":"10Gi"}}}}'


Expansion only increases size. The StorageClass must set allowVolumeExpansion: true.


CSI

Container Storage Interface is how vendors plug disks into Kubernetes without a change to the core API.

  • A CSI driver runs as Pods (controller Deployment plus a node DaemonSet) and registers a CSIDriver object.
  • Sidecars do the Kubernetes chores: external-provisioner (create the PV), external-attacher (attach to the node), external-resizer, external-snapshotter.
  • The kubelet calls the node plugin to stage, publish, and mount the volume into the Pod.
  • You install a driver the same way as any cluster addon: its manifests, or a Helm chart. After that, a StorageClass names provisioner: <driver.example.com>.

You rarely debug the gRPC calls. You check: driver Pods Running in their namespace, kubectl describe pvc events (ProvisioningFailed, FailedAttachVolume, FailedMount), and that the node can reach the storage network.


Mount failures to recognize

Event

Usual cause

Pending PVC, no PV

Class name mismatch, no default class, size larger than any PV, access mode the PV does not offer

WaitForFirstConsumer

No Pod is using the claim yet. Create the Pod

FailedAttachVolume

Disk is still attached to another node, or the cloud identity of the node cannot attach

Multi-Attach error

RWO volume already mounted on a different node. A stuck terminating Pod often still holds it

FailedMount hostPath

The directory does not exist on the node you scheduled onto

kubectl get pod -o wide plus kubectl get volumeattachment shows where the disk is attached.

Official docs

Storage · Diwakar Rai