The PersistentVolumeClaim is stuck in a Pending state because the Kubernetes control plane cannot find a PersistentVolume that matches its requirements.

Common Causes and Fixes

  1. No Matching PersistentVolumes Exist:

    • Diagnosis: kubectl get pv
    • Problem: You have PersistentVolumeClaims requesting specific storageClassName, accessModes, or capacity, but no PersistentVolumes are available that satisfy these criteria.
    • Fix:
      • Dynamic Provisioning: If you intend to use dynamic provisioning, ensure your StorageClass is correctly configured and available. Check its status with kubectl get sc. If it’s missing or misconfigured, create/update it. For example, to create a StorageClass for AWS EBS:
        apiVersion: storage.k8s.io/v1
        kind: StorageClass
        metadata:
          name: gp2-dynamic
        provisioner: kubernetes.io/aws-ebs
        parameters:
          type: gp2
        reclaimPolicy: Retain
        volumeBindingMode: Immediate
        
        Then, ensure your PersistentVolumeClaim references this StorageClass:
        apiVersion: v1
        kind: PersistentVolumeClaim
        metadata:
          name: my-pvc
        spec:
          storageClassName: gp2-dynamic # Must match the StorageClass name
          accessModes:
            - ReadWriteOnce
          resources:
            requests:
              storage: 10Gi
        
        This works because the provisioner field tells Kubernetes which external storage system (like AWS EBS, GCE PD, Ceph) to contact to create a new volume on demand when a PersistentVolumeClaim requests it and no suitable static PersistentVolume is found.
      • Static Provisioning: If you are using static provisioning, you need to manually create PersistentVolumes that match the PersistentVolumeClaim’s storageClassName, accessModes, and capacity. For example, to create a PersistentVolume for NFS:
        apiVersion: v1
        kind: PersistentVolume
        metadata:
          name: my-static-pv
        spec:
          capacity:
            storage: 50Gi
          volumeMode: Filesystem
          accessModes:
            - ReadWriteMany
          persistentVolumeReclaimPolicy: Recycle
          storageClassName: nfs-storage
          mountPath: "/mnt/nfs/data" # This is specific to the PV definition, not typically used by K8s itself for mounting
          nfs:
            server: 192.168.1.100
            path: "/exports/data"
        
        And ensure your PersistentVolumeClaim matches:
        apiVersion: v1
        kind: PersistentVolumeClaim
        metadata:
          name: my-nfs-pvc
        spec:
          accessModes:
            - ReadWriteMany
          storageClassName: nfs-storage # Must match the PV's storageClassName
          resources:
            requests:
              storage: 50Gi
        
        This works because the storageClassName acts as a label that Kubernetes uses to bind a PersistentVolumeClaim to a suitable PersistentVolume. When they match, the claim can be satisfied.
  2. Incorrect storageClassName in PVC:

    • Diagnosis: kubectl get pvc <pvc-name> -o yaml and kubectl get sc
    • Problem: The storageClassName specified in your PersistentVolumeClaim does not exist in your cluster or has a typo.
    • Fix: Correct the storageClassName in your PersistentVolumeClaim YAML to match an existing StorageClass. If you want to use the default provisioner, you can omit the storageClassName field entirely if a default StorageClass is set in your cluster. To set a default StorageClass:
      apiVersion: storage.k8s.io/v1
      kind: StorageClass
      metadata:
        name: default-storage
        annotations:
          storageclass.kubernetes.io/is-default-class: "true"
      provisioner: kubernetes.io/no-provisioner # Or your actual provisioner
      
      This works because storageClassName is the primary mechanism for selecting a volume provisioner or a pre-provisioned PersistentVolume. If it doesn’t resolve, the claim cannot be bound.
  3. Volume Binding Mode (Delayed Binding):

    • Diagnosis: kubectl get pvc <pvc-name> -o yaml and check the volumeBindingMode in the associated StorageClass (kubectl get sc <storage-class-name> -o yaml).
    • Problem: If volumeBindingMode is set to WaitForFirstConsumer on the StorageClass, the PersistentVolume will not be provisioned or bound until a Pod that uses the PersistentVolumeClaim is scheduled. If no Pod is scheduled, or if the Pod cannot be scheduled for other reasons, the PVC will remain Pending.
    • Fix:
      • If you intend to use WaitForFirstConsumer, ensure that the Pod using the PVC can be scheduled. Check Pod scheduling issues with kubectl describe pod <pod-name>.
      • Alternatively, change volumeBindingMode to Immediate in your StorageClass definition.
        apiVersion: storage.k8s.io/v1
        kind: StorageClass
        metadata:
          name: my-storage-class
        provisioner: kubernetes.io/aws-ebs
        volumeBindingMode: Immediate # Change this from WaitForFirstConsumer
        
        This works because Immediate binding ensures that a PersistentVolume is provisioned and bound as soon as the PersistentVolumeClaim is created, regardless of whether a Pod is ready to consume it. This allows the PVC to be satisfied sooner.
  4. Insufficient Available PersistentVolumes (Static Provisioning):

    • Diagnosis: kubectl get pv and kubectl get pvc <pvc-name> -o yaml
    • Problem: You are using static PersistentVolumes, but all available PersistentVolumes are already bound to other PersistentVolumeClaims or are not available due to PersistentVolumeReclaimPolicy (e.g., Retain and the PV is not explicitly released).
    • Fix:
      • Create more PersistentVolumes that match the requirements of your PersistentVolumeClaims.
      • If a PersistentVolume is in a Released state and its persistentVolumeReclaimPolicy is Retain, you may need to manually delete the PersistentVolume object from Kubernetes and ensure the underlying storage is cleaned up, then re-create the PersistentVolume object if you want to reuse it. Be extremely cautious with this.
      • If a PersistentVolume is in a Failed state, investigate the underlying storage provisioner logs.
        # Example of manually releasing and cleaning up a PV (USE WITH EXTREME CAUTION)
        # 1. Ensure the pod using the PVC is deleted.
        # 2. If the PV's reclaim policy is Retain, you might need to manually delete the underlying storage.
        # 3. Delete the PV object:
        kubectl delete pv <pv-name>
        # 4. Re-create the PV object if you intend to reuse it and have cleaned up the storage.
        
        This works because Kubernetes relies on the status field of the PersistentVolume object to determine its availability. If all suitable PVs are in a Bound state or unavailable for other reasons, no match can be found.
  5. Incorrect accessModes:

    • Diagnosis: kubectl get pvc <pvc-name> -o yaml and kubectl get pv -o yaml
    • Problem: The accessModes requested by the PersistentVolumeClaim (e.g., ReadWriteOnce, ReadOnlyMany, ReadWriteMany) are not supported by any available PersistentVolumes that otherwise match. For example, requesting ReadWriteMany when all available PVs only support ReadWriteOnce.
    • Fix: Adjust the accessModes in your PersistentVolumeClaim to match those offered by an available PersistentVolume, or create/configure a PersistentVolume that supports the desired accessModes.
      # PVC requesting ReadWriteOnce
      apiVersion: v1
      kind: PersistentVolumeClaim
      metadata:
        name: my-pvc-rwo
      spec:
        accessModes:
          - ReadWriteOnce # This mode is widely supported
        resources:
          requests:
            storage: 10Gi
      
      # PV supporting ReadWriteOnce
      apiVersion: v1
      kind: PersistentVolume
      metadata:
        name: my-pv-rwo
      spec:
        capacity:
          storage: 10Gi
        accessModes:
          - ReadWriteOnce
        hostPath: # Example for local storage
          path: "/mnt/data/rwo"
      
      This works because accessModes define how a volume can be mounted by nodes. A PersistentVolumeClaim can only be bound to a PersistentVolume that offers at least one compatible accessMode.
  6. Storage Provisioner Issues:

    • Diagnosis: kubectl get pods -n kube-system (look for CSI driver pods or provisioner pods), kubectl logs <provisioner-pod-name> -n <namespace>
    • Problem: If you are using dynamic provisioning, the storage provisioner (e.g., a CSI driver or an in-tree provisioner) might be failing. This could be due to network issues, authentication problems with the cloud provider, misconfiguration of the provisioner, or underlying storage system errors.
    • Fix: Examine the logs of your storage provisioner pods for errors. Troubleshoot connectivity, credentials, and configuration as indicated by the logs. For example, if using AWS EBS CSI driver, check the aws-ebs-csi-driver pods.
      # Example: Check logs for AWS EBS CSI driver controller pod
      kubectl logs <aws-ebs-csi-driver-controller-pod-name> -n kube-system
      
      This works because the provisioner is the component responsible for actually creating the storage volume in your cloud provider or storage system when requested by Kubernetes. If it fails, no PersistentVolume is created, and the PersistentVolumeClaim remains Pending.

Once all these issues are resolved, your PersistentVolumeClaim should transition to a Bound state. The next common problem you might encounter is Pods failing to start due to issues with the underlying storage becoming available or mountable by the node.

Want structured learning?

Take the full Kubernetes course →