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.
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 |
|
Stays on that node |
|
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: Memoryuses 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/pkiand/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
itemsrenames them. Env vars from the same objects are copied only at container start. A volume mount of the object (withoutsubPath) is updated by the kubelet when the object changes. The process has to re-read the file.
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:
serverandpath. The NFS server must already exist. Access from multiple nodes is why people pair it withReadWriteMany. - persistentVolumeClaim: The volume source inside a Pod. It names a PVC. The Pod does not name the PV.
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 |
|
PVC |
|
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 |
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
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
storageClassNamemust 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 namesmanualignores PVs in classstandard.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.
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 ( |
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 |
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
CSIDriverobject. - 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.