You created a PersistentVolumeClaim for your application, but it's stuck in Pending state with no bound PersistentVolume. Pods that need the PVC cannot start, and your stateful application is blocked. PVC pending issues are common when setting up storage in Kubernetes and require understanding storage classes, provisioning, and access modes.

Understanding PVC Pending State

A PVC stays pending when Kubernetes cannot find or create a matching PersistentVolume. This happens when no PV matches the PVC's requirements (capacity, access mode, storage class), or dynamic provisioning fails. Understanding the storage subsystem is essential for debugging.

Diagnosis Commands

Check PVC status:

```bash # List PVCs kubectl get pvc -n namespace

# Describe the pending PVC kubectl describe pvc pvc-name -n namespace

# Check events kubectl get events -n namespace --field-selector involvedObject.name=pvc-name ```

Check storage class:

```bash # List storage classes kubectl get storageclass

# Check specific storage class kubectl describe storageclass storage-class-name

# Check default storage class kubectl get storageclass -o jsonpath='{.items[?(@.metadata.annotations.storageclass\.kubernetes\.io/is-default-class=="true")].metadata.name}' ```

Check available PVs:

```bash # List available PVs kubectl get pv

# Check PV capacity and access modes kubectl get pv -o custom-columns='NAME:.metadata.name,CAPACITY:.spec.capacity.storage,ACCESS:.spec.accessModes[0],STATUS:.status.phase,CLASS:.spec.storageClassName'

# Describe specific PV kubectl describe pv pv-name ```

Common Solutions

Solution 1: Fix Missing Storage Class

If PVC references non-existent storage class:

bash
# Check PVC storage class
kubectl get pvc pvc-name -n namespace -o jsonpath='{.spec.storageClassName}'

If storage class doesn't exist:

```bash # List available classes kubectl get storageclass

# Create missing storage class kubectl apply -f - <<EOF apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: fast-storage provisioner: kubernetes.io/aws-ebs parameters: type: gp3 reclaimPolicy: Delete allowVolumeExpansion: true EOF ```

Or update PVC to use existing class:

```bash # Delete pending PVC kubectl delete pvc pvc-name -n namespace

# Recreate with correct storage class kubectl apply -f - <<EOF apiVersion: v1 kind: PersistentVolumeClaim metadata: name: pvc-name namespace: namespace spec: storageClassName: existing-class-name accessModes: - ReadWriteOnce resources: requests: storage: 10Gi EOF ```

Solution 2: Fix Provisioner Issues

For dynamic provisioning, check provisioner:

bash
# Check storage class provisioner
kubectl get storageclass storage-class-name -o jsonpath='{.provisioner}'

Common provisioner issues:

```bash # AWS EBS provisioner # Check IAM permissions for AWS kubectl describe pods -n kube-system | grep -A 5 ebs

# For CSI drivers, check driver pods kubectl get pods -n kube-system -l app=csi-driver kubectl logs -n kube-system -l app=csi-driver ```

Fix CSI driver issues:

```bash # Check CSI driver status kubectl get csidrivers kubectl describe csidriver driver-name

# Restart CSI controller if needed kubectl rollout restart deployment/csi-controller -n kube-system ```

Solution 3: Fix Capacity Issues

Requested capacity may exceed available storage:

```bash # Check requested capacity kubectl get pvc pvc-name -n namespace -o jsonpath='{.spec.resources.requests.storage}'

# Check available PVs kubectl get pv | grep Available ```

Adjust PVC capacity:

yaml
# Reduce request to match available PV
spec:
  resources:
    requests:
      storage: 5Gi  # Was 100Gi - too large

Or create larger PV manually:

yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: large-pv
spec:
  capacity:
    storage: 100Gi
  accessModes:
  - ReadWriteOnce
  storageClassName: standard
  hostPath:
    path: /mnt/data
    type: DirectoryOrCreate

Solution 4: Fix Access Mode Mismatch

PVC access mode must match PV:

```bash # Check PVC access mode kubectl get pvc pvc-name -n namespace -o jsonpath='{.spec.accessModes}'

# Check available PVs access modes kubectl get pv -o custom-columns='NAME:.metadata.name,ACCESS:.spec.accessModes' ```

Access mode types: - ReadWriteOnce (RWO) - Single node read/write - ReadOnlyMany (ROX) - Multiple nodes read only - ReadWriteMany (RWX) - Multiple nodes read/write - ReadWriteOncePod (RWOP) - Single pod read/write

Fix access mode:

yaml
# Use supported access mode
spec:
  accessModes:
  - ReadWriteOnce  # Most common, most storage supports this

For ReadWriteMany, use NFS:

yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: nfs-storage
provisioner: nfs.csi.k8s.io
parameters:
  server: nfs-server.example.com
  share: /export/data

Solution 5: Fix Volume Binding Mode

WaitForFirstConsumer binding mode delays provisioning:

bash
# Check binding mode
kubectl get storageclass storage-class-name -o jsonpath='{.volumeBindingMode}'

If WaitForFirstConsumer, PVC won't bind until pod uses it:

yaml
# Immediate binding mode
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast-immediate
provisioner: kubernetes.io/aws-ebs
volumeBindingMode: Immediate

For WaitForFirstConsumer, create pod that uses the PVC:

yaml
apiVersion: v1
kind: Pod
metadata:
  name: storage-test
spec:
  containers:
  - name: test
    image: busybox
    command: ["sleep", "3600"]
    volumeMounts:
    - name: data
      mountPath: /data
  volumes:
  - name: data
    persistentVolumeClaim:
      claimName: pvc-name

Solution 6: Fix Selector and Label Mismatch

PVC selector must match PV labels:

```bash # Check PVC selector kubectl get pvc pvc-name -n namespace -o jsonpath='{.spec.selector}'

# Check PV labels kubectl get pv --show-labels ```

Fix label matching:

```yaml # PVC with selector spec: selector: matchLabels: environment: production type: fast

# Create PV with matching labels apiVersion: v1 kind: PersistentVolume metadata: name: production-fast-pv labels: environment: production type: fast spec: capacity: storage: 50Gi accessModes: - ReadWriteOnce storageClassName: standard nfs: server: nfs.example.com path: /data/production ```

Solution 7: Fix Cloud Provider Issues

For cloud storage, check provider configuration:

```bash # AWS EBS issues # Check node IAM roles kubectl describe node node-name | grep -A 5 "ProviderID"

# Verify region/zone matching kubectl get pv -o yaml | grep -A 5 zone

# GKE PD issues # Check GCP project and zone kubectl get pv -o yaml | grep -i zone ```

Fix zone/region issues:

yaml
# Storage class with allowed topologies
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: regional-storage
provisioner: kubernetes.io/aws-ebs
allowedTopologies:
- matchLabelExpressions:
  - key: topology.kubernetes.io/zone
    values:
    - us-east-1a
    - us-east-1b

Solution 8: Fix Reclaim Policy Issues

PV reclaim policy affects availability:

bash
# Check PV reclaim policy
kubectl get pv pv-name -o jsonpath='{.spec.persistentVolumeReclaimPolicy}'

Released PVs with Retain policy need manual intervention:

```bash # PV is Released after PVC deletion # Need to reclaim it manually

# Remove claimRef to make PV Available again kubectl patch pv pv-name -p '{"spec":{"claimRef": null}}'

# Or delete and recreate PV kubectl delete pv pv-name kubectl apply -f pv-definition.yaml ```

Verification

After fixing PVC issues:

```bash # Check PVC status kubectl get pvc -n namespace

# Should show Bound status kubectl describe pvc pvc-name -n namespace

# Verify PV is bound kubectl get pv

# Create pod using PVC kubectl apply -f pod-using-pvc.yaml

# Verify pod starts kubectl get pods -n namespace ```

Common PVC Pending Causes

CauseSymptomsSolution
Missing StorageClass"storageclass.storage.k8s.io not found"Create storage class
Provisioner failureEvents show provisioning failedCheck CSI driver/provisioner
Capacity mismatch"no PV found" with capacityCreate matching PV or reduce request
Access mode mismatch"access mode not supported"Use supported access mode
Binding mode delayWaitForFirstConsumer pendingCreate pod using PVC
Zone mismatch"zone not available"Fix topology/topologySpread

PVC pending issues require checking the entire storage chain: storage class exists, provisioner works, access modes match, and capacity is available. The describe command reveals the specific blocker.