Storage
Containers are ephemeral by default — when a pod restarts, any data written to the container’s filesystem is lost. Kubernetes provides several ways to persist or share data.
Volumes
Section titled “Volumes”A Volume is a directory accessible to containers in a pod. Its lifecycle is tied to the pod (not the container). Common types:
- emptyDir — Created when a pod starts, deleted when the pod is removed. Useful for scratch space or sharing files between containers in the same pod.
- hostPath — Mounts a file or directory from the host node’s filesystem. Use with caution (ties pod to a specific node).
spec: containers: - name: app volumeMounts: - name: scratch mountPath: /tmp/data volumes: - name: scratch emptyDir: {}PersistentVolumes and PersistentVolumeClaims
Section titled “PersistentVolumes and PersistentVolumeClaims”For data that outlives a pod (databases, file uploads), use PersistentVolumes (PV) and PersistentVolumeClaims (PVC).
- PV — A piece of storage provisioned by an admin or dynamically by a StorageClass.
- PVC — A request for storage by a user. Kubernetes binds the PVC to a matching PV.
apiVersion: v1kind: PersistentVolumeClaimmetadata: name: data-pvcspec: accessModes: - ReadWriteOnce resources: requests: storage: 10Gi storageClassName: standardUse the PVC in a pod:
spec: containers: - name: db volumeMounts: - name: data mountPath: /var/lib/data volumes: - name: data persistentVolumeClaim: claimName: data-pvcStorageClasses
Section titled “StorageClasses”A StorageClass defines how storage is provisioned (e.g. SSD vs HDD, which cloud provider backend). When a PVC references a StorageClass, Kubernetes automatically provisions a PV.
kubectl get storageclassVolume binding: Immediate vs WaitForFirstConsumer
Section titled “Volume binding: Immediate vs WaitForFirstConsumer”volumeBindingMode: Immediate— the provisioner may bind and provision before a pod exists. Fast, but can pin a volume to a zone that later conflicts with scheduling constraints.volumeBindingMode: WaitForFirstConsumer— provisioning waits until a pod is scheduled; the volume lands in a topology compatible with that pod. Prefer this for multi-zone clusters and zone-scoped storage.
Topology-aware StorageClasses
Section titled “Topology-aware StorageClasses”When nodes span availability zones, ensure allowedTopologies (or CSI driver defaults) line up with where your workloads run. Misaligned topology is a common reason for Pending pods even when “storage exists.”
Pod delete vs reschedule — what happens to PVC data?
Section titled “Pod delete vs reschedule — what happens to PVC data?”- Deleting a Pod does not delete its PVC by default — data usually remains for StatefulSets and standard Deployments using PVCs.
- Rescheduling to another node requires the volume’s access mode and CSI driver to support attach/detach in the target zone (ReadWriteOnce is single-writer — cannot mount twice).
- For StatefulSets, the same claim name follows the stable identity when the replacement pod can attach in the same topology.
PVC stuck Pending — triage
Section titled “PVC stuck Pending — triage”kubectl describe pvc <name> -n <ns>— read Events at the bottom.- Common causes: no default StorageClass and missing
storageClassName, StorageClass not found, quota exceeded, WaitForFirstConsumer with no schedulable pod, topology mismatch, provisioner outage. - Cross-check namespace ResourceQuota and LimitRange if events mention quota.
For EKS-oriented storage triage, see the EKS troubleshooting cheat sheet.
CSI drivers and the attach–detach–mount path
Section titled “CSI drivers and the attach–detach–mount path”Container Storage Interface (CSI) splits responsibilities between control-plane controllers and per-node plugins:
| Step | Who owns it |
|---|---|
| Provisioning | CSI controller + StorageClass provisioner — creates backing LUNs/disks/filesystems when a PVC should exist. |
| Attach | CSI attacher (controller sidecar) — tells the backend to present the volume to the node where the pod is scheduled. |
| Mount | kubelet invokes the CSI node plugin — format (if volumeMode: Filesystem and empty), then bind-mount into the pod’s mount namespace. |
| Snapshot / resize | Optional capabilities exposed as CRDs (for example VolumeSnapshot) when the driver supports them. |
The kubelet does not phone the storage array directly; it drives CSI NodePublishVolume RPCs. That is why topology and WaitForFirstConsumer matter: scheduling and volume placement must line up.
volumeMode: Filesystem vs Block
Section titled “volumeMode: Filesystem vs Block”spec.volumeMode defaults to Filesystem — the volume is formatted (when needed) and mounted as a directory tree.
volumeMode | Use when |
|---|---|
| Filesystem | Typical databases and apps that expect files under a path. |
| Block | Raw device without a cluster-managed filesystem — some databases and high-throughput workloads manage layout themselves; consumers use volumeDevices instead of volumeMounts. |
Not every CSI driver supports Block equally; confirm access modes and attach limits in the driver’s matrix before relying on it in production.
Replicated CSI vs NFS for live data
Section titled “Replicated CSI vs NFS for live data”On self-hosted clusters, a replicated CSI driver (for example Longhorn) keeps synchronous copies of each volume on multiple nodes, so a volume survives node or host loss. A shared NFS mount is tempting for live PVs but makes the NFS server a runtime single point of failure — better kept as a backup target that replicated volumes ship copies to. A worked example of that split (Longhorn for live PVs, NFS for daily backups): HA Kubernetes on Two Proxmox Hosts.
ConfigMaps
Section titled “ConfigMaps”Store non-sensitive configuration as key-value pairs. Inject into pods as environment variables or mounted files.
apiVersion: v1kind: ConfigMapmetadata: name: app-configdata: DATABASE_HOST: "db.example.com" LOG_LEVEL: "info"Use as environment variables:
env: - name: DATABASE_HOST valueFrom: configMapKeyRef: name: app-config key: DATABASE_HOSTOr mount as a file:
volumeMounts: - name: config mountPath: /etc/configvolumes: - name: config configMap: name: app-configSecrets
Section titled “Secrets”Like ConfigMaps but for sensitive data (passwords, tokens, TLS certs). Values are base64-encoded (not encrypted by default — enable encryption at rest for production).
apiVersion: v1kind: Secretmetadata: name: db-secrettype: Opaquedata: password: cGFzc3dvcmQxMjM= # base64 of "password123"Use as environment variables or volume mounts, same pattern as ConfigMaps:
env: - name: DB_PASSWORD valueFrom: secretKeyRef: name: db-secret key: passwordEncryption at rest vs real secret hygiene
Section titled “Encryption at rest vs real secret hygiene”Encryption at rest for etcd (often backed by a cloud KMS) protects etcd data on disk and in backups. It is still not a substitute for:
- RBAC and audit on who can
getSecrets via the API. - Rotation, short-lived credentials, and safe CI/CD handling of manifests.
- Runtime secret delivery when you need centralized rotation — see Helm templating — External Secrets and Vault for Vault Agent and External Secrets Operator patterns.
Key Takeaways
Section titled “Key Takeaways”- Use emptyDir for scratch space; PVC for persistent data.
- StorageClasses enable dynamic provisioning — no need to pre-create PVs; CSI separates provision/attach from kubelet mount.
- volumeMode chooses Filesystem (default mount) vs Block (raw device) when the driver supports it.
- ConfigMaps for config, Secrets for sensitive data. Both inject into pods as env vars or files.
- Secrets are base64-encoded in the API; enable encryption at rest and still treat RBAC + rotation + external stores as the full story.