KubeTW All articles
Architecture & Strategy

Storage Is the Skeleton in Your Cluster's Closet: How PVs and PVCs Are Quietly Wrecking Production

KubeTW
Storage Is the Skeleton in Your Cluster's Closet: How PVs and PVCs Are Quietly Wrecking Production

You've spent weeks tuning your node pools, dialing in your resource requests, and getting your GitOps pipeline humming. Everything looks clean. Then a StatefulSet pod restarts, a PersistentVolumeClaim lands in a Pending state it never escapes from, and suddenly your on-call rotation is having a very bad night.

Storage in Kubernetes is one of those topics that gets a chapter in every onboarding doc and about 10 minutes of real attention. That gap between "we know how it works" and "we actually know how it works" is where production incidents are born.

Let's talk about what's really happening.

The PV vs. PVC Confusion That Never Fully Goes Away

Here's the thing: even experienced engineers occasionally blur the line between PersistentVolumes and PersistentVolumeClaims in ways that cause real problems. The conceptual split is clean on paper — PVs are the actual storage resources, PVCs are requests for that storage — but the operational implications of that split get messy fast.

A PVC isn't just a pointer to a PV. It's a contract. And like any contract, the terms matter enormously. Access modes, storage class, capacity requests — all of these determine whether your claim binds to the right volume or sits orphaned in Pending while your application crashes in a loop.

The mistake teams make most often? Treating PVCs like they're interchangeable with the underlying storage. They're not. A PVC bound to a volume on a specific node in a zone-aware cluster can pin your pod to infrastructure you didn't intend. That's not a Kubernetes bug — it's the system working exactly as designed, just not the way you assumed.

Reclaim Policies: The Setting Nobody Changes Until It's Too Late

If there's one storage configuration that causes more quiet data loss than anything else, it's the reclaimPolicy field, and the culprit is almost always the Delete policy sitting on a StorageClass that somebody provisioned and forgot about.

Here's the scenario that plays out more often than anyone wants to admit: a developer deletes a PVC during a cleanup operation, fully expecting the data to stick around because "Kubernetes doesn't just delete things." Except with Delete policy, it does. The PV gets released, the underlying cloud disk gets deleted, and the data is gone. No warning. No grace period. Just gone.

The three reclaim policies — Retain, Recycle, and Delete — have very different operational personalities:

The pragmatic default for most production workloads is Retain on anything stateful, combined with a documented cleanup process. Yes, it creates operational overhead. That overhead is intentional. Data loss is not a recoverable situation.

Volume Binding Modes Are Doing More Than You Think

Binding modes quietly determine when a PVC gets bound to a PV, and the difference between Immediate and WaitForFirstConsumer is a bigger deal in multi-zone clusters than most teams realize.

With Immediate binding, a PVC gets bound to an available PV as soon as it's created — before any pod that uses it has been scheduled. In a cluster spread across multiple availability zones, this can mean your PV ends up in us-east-1a while your pod scheduler wants to place the workload in us-east-1b. The result is a pod that either can't schedule or runs in a zone you didn't intend, potentially creating cross-zone data transfer costs that show up quietly on your AWS or GCP bill.

WaitForFirstConsumer delays binding until a pod actually needs the volume, letting the scheduler factor in topology constraints first. For zone-aware clusters — which is most production clusters at any meaningful scale — this is almost always the right default.

If your StorageClass doesn't explicitly set this and you're running multi-zone, you're rolling dice on placement every time a new PVC gets created.

Dynamic Provisioning Isn't a Free Pass

Dynamic provisioning through StorageClasses is one of Kubernetes' genuinely great features. It eliminates the manual PV pre-provisioning workflow and lets workloads request storage on demand. Teams adopt it, see it work, and then stop thinking about it.

That's where things go sideways.

Dynamic provisioning means every PVC that doesn't specify a StorageClass will use the cluster's default — and if your default StorageClass has Delete reclaim policy and Immediate binding mode (which is common in out-of-the-box cloud provider configurations), you've got a footgun that's one misplaced kubectl delete pvc away from firing.

A few things worth auditing right now:

Designing Storage That Doesn't Surprise You

A storage strategy that holds up at scale isn't complicated, but it does require being intentional about a handful of decisions upfront.

First, create multiple StorageClasses that map to actual operational needs. A fast-retain class for stateful production workloads, a standard-delete class for ephemeral workloads and dev environments, and whatever zone-specific variants your cloud provider requires. Don't let one default class try to serve every use case.

Second, document the reclaim policy for every StorageClass in a place where your team actually looks. A comment in a Helm values file counts. A Confluence page nobody reads doesn't.

Third, treat volume snapshots as a first-class part of your storage design, not an afterthought. The VolumeSnapshot API has been stable since Kubernetes 1.20, and if you're running stateful workloads without a snapshot strategy, you're operating without a safety net.

Finally, test your failure modes. Delete a PVC in staging and watch what happens. Simulate a node failure and verify your pods reschedule with their volumes intact. Storage failure scenarios are rarely tested until they happen in production, and by then the learning is expensive.

The Bottom Line

Kubernetes storage isn't magic, and it isn't simple. The abstractions are genuinely useful, but they paper over a lot of decisions that your team still has to make — reclaim behavior, binding topology, provisioner defaults. When those decisions get made implicitly, through inaction or inherited defaults, the results tend to surface at the worst possible time.

The clusters that handle storage gracefully aren't the ones running the most sophisticated setup. They're the ones where somebody sat down, understood the options, made deliberate choices, and wrote them down. That's the whole game.

All Articles

Keep Reading

The Runtime Underneath Everything: How containerd vs CRI-O Shapes Your Cluster's Fate

The Runtime Underneath Everything: How containerd vs CRI-O Shapes Your Cluster's Fate

The Gatekeeper Nobody Configured: Getting Admission Controllers to Actually Do Their Job

The Gatekeeper Nobody Configured: Getting Admission Controllers to Actually Do Their Job

RBAC Looks Great on Paper—Your Cluster Tells a Different Story

RBAC Looks Great on Paper—Your Cluster Tells a Different Story