# Configuring Storage Classes and Dynamic Provisioning

> Chapter 12 of the Courseiva CNCF-CKA curriculum — https://courseiva.com/learn/cncf-cka/configuring-storage-classes-and-dynamic-provisioning

**Official objective:** Storage - 10% — Configure StorageClasses and dynamic volume provisioning.

## Introduction

How do you stop manually carving out storage for every application and instead let Kubernetes handle it automatically? That is the problem dynamic provisioning solves, and it is a skill you must demonstrate in the CKA exam's Storage section. With StorageClasses, you define the 'type' of storage (fast SSD, slow HDD, replicated, encrypted) once, and then any Pod that needs a volume can request the right type without you ever touching a cloud console.

## The Self-Service Buffet Analogy

Because a restaurant kitchen used to pre-plate every meal in the back, which led to long waits and wasted food when diners changed their minds.

You walk into a self-service buffet. You don't tell the kitchen exactly what plate you want, how many napkins, and where the utensils should go – that would be like static provisioning. Instead, you grab a tray (a claim), walk past the food stations (different storage backends), and serve yourself exactly what you want. The buffet doesn't pre-make every possible dish; it dynamically gives you access to what it has when you ask.

In Kubernetes terms, the StorageClass is like a sign above each station: 'This station gives you SSD plates', 'This station gives you HDD plates', 'This station replicates to another aisle'. The PersistentVolumeClaim is your tray – you walk up and say: 'I need a plate for my salad (ReadWriteOnce access) and it must be at least 10 inches wide (10Gi size).' The buffet manager (the provisioner) sees your tray, reads the sign, and instantly hands you a clean plate from that station – no waiting, no ordering ahead.

If you decide you want dessert later, you just grab another plate. When you're done, you return the tray to the bin, and the buffet washes the plate for the next customer. This is dynamic provisioning: storage appears the moment you need it, configured exactly as the sign promises, without human plate-washers (sysadmins) needing to pre-wash every plate in advance.

## Core explanation

In Kubernetes, storage can be provisioned in two main ways: statically and dynamically. Static provisioning is like having a cupboard full of pre-cut pieces of wood – you must measure and cut each one in advance, label it, and store it until someone asks for it. Dynamic provisioning is like having a saw that cuts the wood to size the moment you hand over a blueprint. To understand dynamic provisioning, you need to know four key components: the PersistentVolume (PV), the PersistentVolumeClaim (PVC), the StorageClass, and the provisioner.

A PersistentVolume (PV) is a piece of storage in the cluster that an administrator has already created. It has a specific capacity (e.g., 10Gi), access modes (ReadWriteOnce, ReadOnlyMany, ReadWriteMany – more on these in a moment), and a particular storage backend (like an AWS EBS volume, a GCEPersistentDisk, or an NFS share). A PV exists independently of any Pod – it is like a server rack in a data centre that is plugged in and waiting for someone to use it.

A PersistentVolumeClaim (PVC) is a request for storage by a user or a Pod. It describes what the Pod needs: 'I want 5Gi of disk space with ReadWriteOnce access'. When Kubernetes sees a PVC, it tries to find a PV that matches the request. If it finds one, it 'binds' the PVC to that PV. If it cannot find a suitable PV, the PVC stays in a 'Pending' state forever unless you have dynamic provisioning enabled.

A StorageClass provides a way for administrators to describe different 'classes' of storage. For example, you can define a StorageClass called 'fast' that uses SSDs and another called 'slow' that uses traditional hard drives. Each StorageClass has a 'provisioner' field that tells Kubernetes which plugin should automatically create the underlying storage. For example, if you are on Amazon Web Services (AWS), the provisioner might be 'kubernetes.io/aws-ebs'. If you are on Google Cloud, it might be 'kubernetes.io/gce-pd'. There are also community and in-tree provisioners for many different systems.

The provisioner is the component that actually does the work. When a PVC requests a StorageClass that has dynamic provisioning enabled, the provisioner contacts the cloud provider (or on-premise storage system) and says: 'Create a new 10Gi EBS volume in us-east-1a with encryption enabled.' It then creates a corresponding PV object in Kubernetes that represents that newly created volume. The PVC then binds to this new PV. It all happens automatically – no human administrator needed.

Access modes are a detail you must know for the CKA. They are:

- ReadWriteOnce (RWO): the volume can be mounted as read-write by a single Pod (i.e., one node). This is the most common for block storage like EBS or GCEPersistentDisk.
- ReadOnlyMany (ROX): the volume can be mounted as read-only by many Pods (on many nodes). Common for shared file systems like NFS.
- ReadWriteMany (RWX): the volume can be mounted as read-write by many Pods. This is harder to achieve and usually requires a network-based file system like AWS EFS, GlusterFS, or a CSI driver that supports it.
- ReadWriteOncePod (RWOP): a newer mode that restricts the volume to a single Pod across the entire cluster (not just a single node). This helps with CSI migration.

To enable dynamic provisioning, you simply create a StorageClass with a provisioner, and then PVCs that reference that StorageClass (either explicitly by name or by setting it as the default) will automatically trigger volume creation. The CKA exam will ask you to create a StorageClass, create a PVC using that StorageClass, and then create a Pod that mounts that PVC as a volume. You do not need to interact with the cloud console in the exam – you just write YAML manifests and use kubectl commands.

Why does this matter? Before Kubernetes, developers would submit tickets to the storage team: 'Please provision a 100GB LUN on the SAN.' The storage team would carve the LUN, present it to the compute team, who would mount it on the server. That process could take days. With dynamic provisioning and StorageClasses, it takes seconds. The CKA tests your ability to configure this automation correctly – especially the YAML syntax for StorageClass, PVC, and Pod objects.

## Real-world context

A mid-sized company, 'FinLoop', runs its microservices on Kubernetes across three environments: development, staging, and production. The infrastructure team has two storage tiers: a fast, expensive, replicated storage backend (Portworx with encryption) for production databases, and a slower, cheaper, standard block storage (hostPath for dev, cloud volumes for prod) for application logs and temporary data.

The team defines two StorageClasses: 'fast-replicated' and 'slow-standard'. Each StorageClass has a different provisioner and different parameters (like replication factor, IOPS limits, or filesystem type). The 'fast-replicated' class might use a CSI driver for Portworx with parameters like 'repl: 3' and 'io_profile: db_remote'. The 'slow-standard' class might use the in-tree 'kubernetes.io/gce-pd' provisioner with 'type: pd-standard'.

When a developer deploys a PostgreSQL database, they write a PVC YAML that specifies 'storageClassName: fast-replicated' and 'resources.requests.storage: 50Gi'. The moment they apply that manifest, the Portworx provisioner receives the claim, creates a 50Gi replicated volume across three nodes, encrypts it, and creates a corresponding PV. The Pod then mounts that PVC and the database starts. The developer never had to open a ticket or talk to a storage admin.

For the application's log collector, another PVC uses 'storageClassName: slow-standard' with 'accessModes: ReadWriteOnce'. The cloud provisioner creates a standard persistent disk in a specific zone. The Pod mounts it, writes logs, and when the Pod is deleted, the PVC might be kept (depending on the reclaim policy) or deleted automatically.

The real task for an IT professional is: 
- Define the StorageClasses with correct provisioner names and parameters for their specific storage backend.
- Set a default StorageClass so that PVCs without an explicit storageClassName use the appropriate default.
- Configure reclaim policies (Retain, Delete, Recycle) – 'Delete' is common for dynamic PVs, but for databases you often want 'Retain' so you can recover the volume if the PVC is deleted accidentally.
- Ensure the PVC names, labels, and selectors match so that Pods can reference them.
- Troubleshoot 'Pending' PVCs by checking events, verifying the StorageClass exists, and confirming the provisioner is installed.
- Use 'kubectl describe pvc <name>' to see events like 'waiting for a volume to be created' or 'provisioning succeeded'.
- Know how to edit a StorageClass to change parameters (though many are immutable after creation for the backing volume).

In a real environment, you would also set up monitoring on StorageClass usage (how many volumes per class, how much capacity), define quotas per namespace, and manage volume expansion (allowing PVCs to request more storage after creation if the StorageClass allows it). These are all tasks that a CKA may need to perform.

## Exam focus

The CKA exam tests your ability to configure StorageClasses and use dynamic provisioning in a hands-on command-line environment. You will not be asked theoretical questions about cloud storage pricing. Instead, you will be given a Kubernetes cluster running on a sandbox (often hosted by the exam platform) and asked to perform specific tasks.

The core exam topics are:
- Create a StorageClass. You must write a YAML manifest or use 'kubectl create' with the correct API version (storage.k8s.io/v1), kind (StorageClass), provisioner name, and parameters. The provisioner name is often something like 'kubernetes.io/no-provisioner' for static provisioning, or a specific CSI driver like 'ebs.csi.aws.com'.
- Create a PersistentVolumeClaim referencing that StorageClass. You must set the 'storageClassName' field correctly. If you misspell the name, the PVC will never bind and stay in 'Pending'.
- Mount a PVC as a volume in a Pod. You must write a Pod manifest that has a 'volumes' section with a 'persistentVolumeClaim' entry, and a 'volumeMounts' section in the container spec.
- Set a default StorageClass. The exam may ask you to make a StorageClass the default. You add the annotation 'storageclass.kubernetes.io/is-default-class: "true"' to the StorageClass YAML. If there are multiple defaults, the PVC will go to 'Pending' because it is ambiguous.
- Understand reclaim policies. The default for dynamically provisioned PVs is 'Delete' – meaning when the PVC is deleted, the PV and the underlying storage are also deleted. If you want to keep the volume, you must set the 'persistentVolumeReclaimPolicy' to 'Retain'.
- Troubleshoot a PVC stuck in 'Pending'. This is a common exam trap. The candidate must check events with 'kubectl describe pvc', look for 'no persistent volumes available for this claim' or 'storageclass.storage.k8s.io "<name>" not found'. The fix might be creating the StorageClass, correcting the name, or ensuring the provisioner is running.

Traps the exam uses:
- They give you a StorageClass YAML with a typo in the provisioner field. The PVC will never bind. You must identify the error by describing the PVC.
- They ask you to create a PVC but do not explicitly tell you to use a StorageClass. If you omit the 'storageClassName', it will use the default StorageClass (if one exists). If no default is set, it will look for a pre-existing PV that matches, and if no PV exists, it stays 'Pending'. So always check if a default is present.
- They ask you to mount the same PVC in two different Pods. If the access mode is ReadWriteOnce, the second Pod will fail to start because the volume is already attached to another node. You must choose the correct access mode (ReadWriteMany) or use a different PVC.
- They give a Pod spec that references a PVC volume but the mount path is not specified correctly. The Pod will fail with 'Error: failed to generate container config'. You must ensure the mount path is an absolute path like '/var/lib/data'.

Key definitions to memorise:
- StorageClass API version: storage.k8s.io/v1
- Provisioner: plugin that creates the actual volume (e.g., ebs.csi.aws.com, kubernetes.io/gce-pd, kubernetes.io/azure-disk, csi-hostpath)
- Parameters: depends on provisioner (e.g., type: pd-ssd, encrypted: "true", fstype: ext4)
- ReclaimPolicy: Delete or Retain (Recycle is deprecated in newer versions)
- VolumeBindingMode: Immediate or WaitForFirstConsumer – 'Immediate' creates the volume as soon as the PVC is created; 'WaitForFirstConsumer' delays creation until a Pod using the PVC is scheduled, which can help with zone-aware scheduling.
- AllowVolumeExpansion: true/false – if true, the PVC can be edited to request more storage after creation.

## Step by step

1. **Create a StorageClass YAML definition** — Write a YAML file (e.g., storage-class.yaml) with apiVersion: storage.k8s.io/v1, kind: StorageClass, metadata.name, provisioner (e.g., kubernetes.io/aws-ebs), and parameters (like type: gp2, encrypted: 'true'). This defines the template for how new volumes will be created.
2. **Apply the StorageClass to the cluster** — Use 'kubectl apply -f storage-class.yaml' to register the StorageClass. After this, any PVC that references this StorageClass by name will trigger dynamic provisioning. Verify with 'kubectl get sc'.
3. **Create a PersistentVolumeClaim (PVC) that uses the StorageClass** — Write a PVC YAML with spec.accessModes, spec.resources.requests.storage (e.g., 10Gi), and spec.storageClassName (set to the name of the StorageClass you created). Apply it. If successful, a PV will be created automatically.
4. **Verify the PVC and the dynamically created PersistentVolume (PV)** — Use 'kubectl get pvc' to see the PVC's status (Bound) and 'kubectl get pv' to see the new PV. The PV's 'Claim' column shows the PVC it is bound to. Use 'kubectl describe pv <name>' to see details like the volume handle (cloud provider volume ID).
5. **Create a Pod that mounts the PVC as a volume** — Write a Pod YAML with a volumes section containing a persistentVolumeClaim entry (claimName: <your-pvc-name>) and a volumeMounts section in the container spec (mountPath: '/data'). Apply the Pod and confirm it runs with 'kubectl get pods'. The Pod now has persistent storage, even if it gets rescheduled to another node.
6. **Set a default StorageClass (if required)** — If you want the StorageClass to be used when no storageClassName is specified in a PVC, add the annotation: 'storageclass.kubernetes.io/is-default-class: "true"' to the StorageClass metadata. Only one default should exist across the cluster. Test by creating a PVC without storageClassName – it should bind using the default.

## Comparisons

### Static Provisioning vs Dynamic Provisioning

**Static Provisioning:**
- Administrator manually creates PersistentVolume objects ahead of time.
- PVCs must wait until a matching PV is found; may remain 'Pending' if none exists.
- No StorageClass with a provisioner is required for binding to a PV.

**Dynamic Provisioning:**
- PVC triggers automatic creation of a PersistentVolume by a provisioner.
- StorageClass defines the provisioner and parameters for volume creation.
- Requires a StorageClass with a provisioner field (e.g., kubernetes.io/aws-ebs).

### Immediate Volume Binding Mode vs WaitForFirstConsumer Volume Binding Mode

**Immediate Volume Binding Mode:**
- The PV is created as soon as the PVC is created, regardless of where a Pod will run.
- May create volumes in a zone that lacks resources, causing scheduling failures.
- Default for most StorageClasses if not specified.

**WaitForFirstConsumer Volume Binding Mode:**
- The PV is created only after a Pod that uses the PVC is scheduled to a node.
- Ensures the volume is created in the same zone as the Pod, reducing latency.
- Useful for multi-zone clusters to avoid orphaned volumes.

### Reclaim Policy: Delete vs Reclaim Policy: Retain

**Reclaim Policy: Delete:**
- When the PVC is deleted, the PV and the underlying storage volume are also deleted automatically.
- Default for dynamically provisioned PVs.
- No manual cleanup required after deleting a PVC.

**Reclaim Policy: Retain:**
- When the PVC is deleted, the PV still exists but is in 'Released' state and cannot be reused until manually reclaimed.
- The underlying storage volume (e.g., cloud disk) is preserved, allowing data recovery.
- Requires manual steps to delete or reattach the PV if needed.

## Diagram

_The flow from a user creating a PVC through the decision of using a StorageClass, triggering the provisioner to create a volume, and eventually binding the PV to the PVC so a Pod can mount it._

```mermaid
flowchart TD
    A[User creates PVC YAML] --> B[Kubernetes API Server receives PVC]
    B --> C{Does PVC have storageClassName?}
    C -- Yes --> D[Find StorageClass with that name]
    C -- No --> E[Find StorageClass with is-default-class annotation]
    D --> F{Does StorageClass have a provisioner?}
    F -- Yes (dynamic) --> G[Provisioner calls cloud/storage backend]
    G --> H[Backend creates actual volume (e.g., EBS disk)]
    H --> I[PV is created and bound to PVC]
    F -- No (static) --> J[PVC waits for existing PV match]
    E --> F
    I --> K[Pod can mount the PVC]
```

## Common misconceptions

- **Misconception:** You must create a PersistentVolume manually before you can create a PersistentVolumeClaim, even with dynamic provisioning. **Reality:** With dynamic provisioning, you do NOT create any PV manually. The PVC references a StorageClass, and the provisioner automatically creates the PV and the underlying storage volume. The PV is created by the system, not by you. (This confusion comes from the older static provisioning workflow where administrators had to pre-create PVs. Beginners often see blog posts about static provisioning and assume it is the only way.)
- **Misconception:** A StorageClass with the same name as the default class is always applied automatically to every PVC that does not specify a storageClassName. **Reality:** A StorageClass becomes the default only if it has the annotation 'storageclass.kubernetes.io/is-default-class: "true"'. Having the name 'default' or being the only StorageClass does not make it the default. The annotation is mandatory. (The annotation is not intuitive. Many beginners assume that the name 'default' is magical, but Kubernetes uses a specific annotation to mark the default.)
- **Misconception:** The provisioner field in a StorageClass is optional – if you leave it blank, Kubernetes will use a built-in provisioner. **Reality:** The provisioner field is required for dynamic provisioning. If it is left blank or set to 'kubernetes.io/no-provisioner', dynamic provisioning will NOT work – the StorageClass can only be used for static binding to pre-existing PVs. (This mistake arises because some YAML examples omit the provisioner when demonstrating StorageClasses for static binding. Beginners copy the YAML and wonder why volumes are not being created.)
- **Misconception:** ReadWriteMany (RWX) is supported by all StorageClasses and provisioners out of the box. **Reality:** Most block storage provisioners (like AWS EBS, GCE Persistent Disk, Azure Disk) do NOT support RWX. They only support ReadWriteOnce. RWX typically requires a network filesystem (NFS, GlusterFS) or a CSI driver specifically designed for it (like Amazon EFS CSI driver). (The naming 'PersistentVolume' sounds general, so beginners assume any volume can be mounted by many Pods. In practice, the underlying storage technology has hard limits.)
- **Misconception:** If I delete a PVC that is bound to a dynamically provisioned PV, the underlying storage volume (e.g., the cloud disk) is always deleted immediately. **Reality:** The deletion behaviour depends on the 'persistentVolumeReclaimPolicy' of the StorageClass or the PV. The default is 'Delete', but it can be set to 'Retain' to keep the volume. Even with 'Delete', if there are finalizers on the PV (e.g., from the CSI driver), the deletion might be delayed. (Beginners hear 'dynamic provisioning' and assume the reverse (dynamic de-provisioning) is always automatic. But reclaim policies can be modified, and some storage backends (like Portworx with snapshots) may hold volumes for a grace period.)

## Key takeaways

- A StorageClass defines a 'class' of storage with a specific provisioner, parameters, and reclaim policy, enabling dynamic provisioning of PersistentVolumes.\n\nPersistentVolumeClaim (PVC) is a request for storage that can bind to a dynamically created PV if it references a StorageClass with a valid provisioner.\n\nThe default StorageClass must be explicitly marked with the annotation 'storageclass.kubernetes.io/is-default-class: "true"' – the name 'default' alone does not make it the default.\n\nReadWriteOnce (RWO) is the only access mode supported by most block storage provisioners; ReadWriteMany (RWX) requires a network filesystem or a specialised CSI driver.\n\nA PVC stuck in 'Pending' state is often caused by a missing or misspelled StorageClass name, or a lack of a default StorageClass.\n\nThe 'VolumeBindingMode' setting can delay storage creation until a Pod is scheduled (WaitForFirstConsumer), which helps with topology constraints like zones.\n\nTo delete a dynamically provisioned volume permanently, you typically delete the PVC; the PV and the backing volume are then deleted if the reclaim policy is 'Delete'.

## FAQ

**What is the difference between a PersistentVolume (PV) and a PersistentVolumeClaim (PVC)?**

A PV is the actual storage resource (like a virtual hard disk) that exists in the cluster. A PVC is a request for storage – it lists the size and access mode needed. The PVC binds to a PV that matches its requirements.

**Do I need to create a PV if I use a StorageClass with dynamic provisioning?**

No. When you create a PVC that references a StorageClass with a provisioner, the Kubernetes control plane automatically creates a new PV and the underlying storage volume (e.g., a cloud disk). You do not create the PV manually.

**Can I change the reclaim policy of a StorageClass after it is created?**

Yes, you can edit the StorageClass object with 'kubectl edit sc <name>' and change the 'persistentVolumeReclaimPolicy' field. However, this only affects PVs created after the change; existing PVs retain their original reclaim policy unless you update them individually.

**What happens if I create a PVC with a storageClassName that does not exist?**

The PVC will remain in 'Pending' state indefinitely. You can see the error by running 'kubectl describe pvc <name>' and looking at the Events section for a message like 'storageclass.storage.k8s.io "<name>" not found'.

**Can I have multiple StorageClasses in the same cluster?**

Yes, you can have as many as you need. Each represents a different tier of storage (e.g., fast, slow, encrypted, unencrypted). Each PVC selects one by name.

**How do I make a StorageClass the default so that PVCs without a storageClassName use it?**

Add the annotation 'storageclass.kubernetes.io/is-default-class: "true"' to the StorageClass's metadata. If multiple StorageClasses have this annotation set to true, PVCs will remain in 'Pending' because the system cannot decide which to use.

## Check your understanding

1. **What must a StorageClass have in its spec to enable dynamic provisioning?**

   Answer: A 'provisioner' field that references a CSI driver or in-tree plugin (e.g., 'kubernetes.io/aws-ebs', 'ebs.csi.aws.com').

2. **A PVC is stuck in 'Pending' after you created it. Which command do you run first to diagnose the problem?**

   Answer: Run 'kubectl describe pvc <name>' and look at the Events section for error messages about missing StorageClass or no matching PV.

3. **How do you make a StorageClass the default for PVCs that do not specify a storageClassName?**

   Answer: Add the annotation 'storageclass.kubernetes.io/is-default-class: "true"' to the StorageClass metadata.

4. **What happens to the underlying storage volume when you delete a PVC that used a StorageClass with reclaimPolicy: Delete?**

   Answer: The PV and the underlying storage volume (e.g., cloud disk) are automatically deleted by the provisioner.

5. **Can you mount a single PVC with ReadWriteOnce access mode in two Pods that are scheduled on different nodes?**

   Answer: No. ReadWriteOnce allows only one node to mount the volume as read-write. The second Pod will remain in 'ContainerCreating' state or fail with a mount error.

---

Interactive version with quiz and diagrams: https://courseiva.com/learn/cncf-cka/configuring-storage-classes-and-dynamic-provisioning
