Kubernetes PVC Stuck in Pending on WSL2 Ubuntu: Comprehensive Troubleshooting Guide
Resolve Kubernetes Persistent Volume Claim (PVC) issues stuck in Pending status on Windows WSL2 Ubuntu. Diagnose and fix common storage class, provisioner, and PV configuration problems.
Resolve Kubernetes Persistent Volume Claim (PVC) issues stuck in Pending status on Windows WSL2 Ubuntu. Diagnose and fix common storage class, provisioner, and PV configuration problems.
Introduction
As an experienced Systems Administrator and DevOps engineer, I've seen countless Kubernetes deployments, and one of the most common hurdles for stateful applications is persistent storage. When running Kubernetes on Windows Subsystem for Linux 2 (WSL2) with an Ubuntu distribution, a PersistentVolumeClaim (PVC) getting stuck in a Pending state is a frequent roadblock for developers and administrators alike. This issue typically means that Kubernetes cannot find or provision a suitable PersistentVolume (PV) to fulfill the storage request made by your PVC.
This guide provides a highly technical, accurate, and step-by-step approach to diagnose and resolve PVCs stuck in Pending status specifically within the WSL2 Ubuntu environment. We'll cover common root causes ranging from misconfigured StorageClass definitions and absent provisioners to fundamental WSL2 filesystem considerations.
Symptom & Error Signature
When your PersistentVolumeClaim is stuck in Pending, your stateful applications (like databases, message queues, or persistent web services) will fail to start or remain in a ContainerCreating state because their required storage cannot be provisioned.
You will typically observe this status when listing your PVCs:
kubectl get pvc
Expected Output (Symptom):
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
my-app-data Pending local-path 5m
another-pvc Pending nfs-storage 2m
To get more detailed information about why it's pending, you'll need to inspect the PVC's events:
kubectl describe pvc my-app-data
Expected Output (Error Signature – Look for "Events" section):
Name: my-app-data
Namespace: default
StorageClass: local-path
Status: Pending
Volume:
Labels: <none>
Annotations: <none>
Finalizers: [kubernetes.io/pvc-protection]
Capacity:
Access Modes:
VolumeMode: Filesystem
DataSource: <none>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning ProvisioningFailed 15s (x3 over 2m) local-path-provisioner failed to provision volume with StorageClass "local-path": persistentvolumeclaims "my-app-data" not found
Warning ProvisioningFailed 10s (x5 over 2m) persistentvolume-controller Failed to provision volume with StorageClass "local-path": storageclass.storage.k8s.io "local-path" not found
Warning ProvisioningFailed 5s (x10 over 2m) persistentvolume-controller no persistent volume available for this claim and no storage class is set
The Events section is crucial. Messages like "no persistent volume available for this claim and no storage class is set", "Failed to provision volume with StorageClass … not found", or "failed to provision volume with StorageClass …: persistentvolumeclaims … not found" directly point to the underlying issues.
Root Cause Analysis
A PVC gets stuck in Pending primarily because Kubernetes cannot fulfill its storage request. This can be due to several reasons, especially in a WSL2 environment:
No Suitable Persistent Volume (PV) Available:
- Static Provisioning: There are no pre-created
PersistentVolumeobjects that match the PVC's requirements (e.g.,accessModes,storageClassName,capacity,volumeMode). - Dynamic Provisioning Failure: The
StorageClassspecified by the PVC is either:- Not found: The
StorageClassresource itself does not exist in the cluster. - Misconfigured: The
StorageClassexists, but its provisioner is incorrect or its parameters are malformed. - Provisioner not running/installed: The external controller responsible for dynamically creating PVs (e.g.,
local-path-provisioner,nfs-subdir-external-provisioner) is not deployed, is crashed, or has insufficient permissions.
- Not found: The
- Static Provisioning: There are no pre-created
StorageClass Issues:
- The
StorageClassreferenced in the PVC does not exist or has a typo. - If the PVC omits
storageClassName, there might be no defaultStorageClassdefined in the cluster.
- The
Capacity and Access Mode Mismatches:
- The PVC requests more storage than available in any existing static PVs, or more than the dynamic provisioner is configured to handle (e.g., disk quota limits).
- The
accessModesrequested by the PVC (e.g.,ReadWriteMany) cannot be satisfied by available PVs or the chosen provisioner (e.g.,HostPathtypically only supportsReadWriteOnce).
Provisioner Specific Issues (WSL2 Context):
local-path-provisioner(Common with K3s/MicroK8s):- The host path configured for the provisioner (e.g.,
/var/lib/rancher/k3s/storagefor K3s) does not exist within the WSL2 Ubuntu filesystem or has incorrect permissions. - The provisioner pod itself is failing due to internal errors or misconfiguration.
- The host path configured for the provisioner (e.g.,
- NFS Provisioner:
- The NFS server is not running, not accessible from WSL2, or the configured export path is incorrect.
- NFS client tools are not installed in the WSL2 Ubuntu distro, or required kernel modules are missing.
- Network connectivity issues between the Kubernetes node (WSL2 Ubuntu) and the NFS server.
- General WSL2 Filesystem Limitations: While
HostPathcan technically point to/mnt/c/(Windows drives), many production-grade provisioners or evenlocal-path-provisionermight have issues with permissions, symlinks, or performance when directly operating on Windows filesystems mounted into WSL2. It's generally best practice to keep persistent storage paths within the WSL2 Ubuntu filesystem.
Step-by-Step Resolution
Follow these steps meticulously to diagnose and resolve your PVC Pending issue on WSL2.
1. Initial Diagnosis: Check PVC, PV, StorageClass, and Events
The first step is always to gather all relevant information.
# Get details of the problematic PVC
kubectl describe pvc my-app-data
# Check if any Persistent Volumes (PVs) exist
kubectl get pv
# Check if StorageClasses are defined
kubectl get sc
# Get all events related to the PVC
kubectl get events --field-selector involvedObject.name=my-app-data --sort-by=".metadata.creationTimestamp"
Carefully examine the Events section from kubectl describe pvc. This often gives the most direct hint. Look for messages indicating missing StorageClass, failed provisioning, or no matching PVs.
2. Verify StorageClass Configuration
A missing or misconfigured StorageClass is a prime suspect.
Scenario A: PVC specifies a storageClassName (e.g., local-path, nfs-storage)
Ensure the StorageClass actually exists and its configuration is correct.
kubectl get sc <your-storage-class-name> -o yaml
Example (for local-path used by K3s/MicroK8s):
# kubectl get sc local-path -o yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
annotations:
storageclass.kubernetes.io/is-default-class: "true" # May or may not be default
name: local-path
provisioner: rancher.io/local-path
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
- Action: If the
StorageClassdoes not exist, you must create it. If it exists, verify theprovisionerfield matches the expected provisioner. If theprovisioneris incorrect or misspelled, you'll need to correct it (often by deleting and recreating theStorageClass).
Scenario B: PVC does NOT specify a storageClassName
In this case, Kubernetes attempts to use the default StorageClass.
kubectl get sc -o json | jq -r '.items[] | select(.metadata.annotations."storageclass.kubernetes.io/is-default-class" == "true") .metadata.name'
- Action: If this command returns nothing, there is no default
StorageClass. You must either:- Define a default
StorageClassby adding the annotationstorageclass.kubernetes.io/is-default-class: "true"to an existingStorageClass(and ensure only one is marked as default). - Modify your PVC to explicitly specify an existing
storageClassName.
- Define a default
3. Ensure Storage Provisioner is Running (for Dynamic Provisioning)
If your StorageClass uses dynamic provisioning, the associated provisioner controller must be running and healthy.
A. For local-path-provisioner (Common with K3s/MicroK8s on WSL2)
This provisioner relies on a path within the WSL2 Ubuntu filesystem to store PVs.
Check the provisioner pod status:
kubectl get pods -n kube-system -l app=local-path-provisionerEnsure the pod is
Runningand healthy. If not, inspect its logs:kubectl logs -n kube-system -l app=local-path-provisioner kubectl describe pod -n kube-system -l app=local-path-provisionerVerify the host path: The
local-path-provisionertypically uses/var/lib/rancher/k3s/storage(for K3s) or similar. Ensure this directory exists and has correct permissions inside your WSL2 Ubuntu distribution.# From inside your WSL2 Ubuntu terminal sudo ls -ld /var/lib/rancher/k3s/storage # Or relevant path for MicroK8sThe directory should typically be owned by
root:rootwith permissions likedrwxr-xr-x. If it doesn't exist, create it:sudo mkdir -p /var/lib/rancher/k3s/storage sudo chown root:root /var/lib/rancher/k3s/storage sudo chmod 755 /var/lib/rancher/k3s/storageDo NOT attempt to use paths directly on your Windows host filesystem (e.g.,
/mnt/c/some/path) forlocal-path-provisioner. While technically possible withHostPathfor a static PV, it's generally problematic for dynamic provisioners and can lead to permission issues and performance degradation within WSL2. Stick to paths within the WSL2 filesystem.
B. For nfs-subdir-external-provisioner (or other NFS-based storage)
If you're using an NFS server, ensure it's accessible and correctly configured.
Check NFS provisioner pod status:
kubectl get pods -n <your-nfs-namespace> -l app=nfs-subdir-external-provisionerEnsure the pod is
Running. Check its logs if it's not:kubectl logs -n <your-nfs-namespace> -l app=nfs-subdir-external-provisioner kubectl describe pod -n <your-nfs-namespace> -l app=nfs-subdir-external-provisionerVerify NFS server accessibility from WSL2: From your WSL2 Ubuntu terminal, try to manually mount the NFS share:
sudo apt update && sudo apt install -y nfs-common # Install NFS client if not present sudo mount -t nfs <NFS_SERVER_IP>:<EXPORT_PATH> /mnt/test_nfs ls /mnt/test_nfs # Check contents sudo umount /mnt/test_nfsIf this fails, your NFS server configuration or network connectivity is the issue.
Check the
StorageClassand provisioner deployment for correct NFS server details: TheStorageClassfor NFS provisioner typically points to the NFS server and path. Thenfs-subdir-external-provisionerdeployment itself will also contain these details.# Example NFS StorageClass apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: nfs-storage provisioner: nfs-subdir-external-provisioner parameters: archiveOnDelete: "false" # or "true"The actual NFS server and path are usually configured in the
nfs-subdir-external-provisionerdeployment/statefulset via environment variables or volume mounts.
4. Create a Static Persistent Volume (If Dynamic Provisioning is Not Used/Desired)
If dynamic provisioning is not set up, or you prefer static provisioning for a specific scenario (e.g., local development with a HostPath), you need to manually create a PersistentVolume that matches your PVC's requirements.
HostPathPVs are not suitable for production environments. They bind storage directly to the Kubernetes node's filesystem, making pods non-portable and introducing single points of failure. They are acceptable for single-node development environments like K3s/MicroK8s on WSL2.
Define the PV YAML: Create a file, e.g.,
my-app-pv.yaml. Ensurecapacity,accessModes,storageClassName, andpathmatch your PVC's expectations. ThepathMUST exist inside your WSL2 Ubuntu distro.apiVersion: v1 kind: PersistentVolume metadata: name: my-app-pv labels: type: local spec: storageClassName: local-path # Must match your PVC's storageClassName or be omitted if PVC omits it capacity: storage: 1Gi accessModes: - ReadWriteOnce hostPath: path: "/var/data/my-app" # This path must exist inside your WSL2 Ubuntu filesystem! type: DirectoryOrCreate # Ensures the directory exists if not already present persistentVolumeReclaimPolicy: Retain # Or Delete, RecycleCreate the directory in WSL2:
# From inside your WSL2 Ubuntu terminal sudo mkdir -p /var/data/my-app sudo chown 1000:1000 /var/data/my-app # Adjust UID:GID to match your application's user sudo chmod 777 /var/data/my-app # Or more restrictive permissionsCarefully consider the
chownandchmodpermissions. Your application inside the container will run as a specific user/group ID. The directory on the host (WSL2) must have permissions that allow this user/group to write to it. Often,777or matching container UID/GID is required for simplicity in development.Apply the PV:
kubectl apply -f my-app-pv.yamlAfter creating the PV, Kubernetes should automatically bind it to your
PendingPVC if all criteria (capacity, access modes,storageClassName) match. Verify withkubectl get pvc.
5. Address Capacity and Access Mode Mismatches
If PVs exist but your PVC is still Pending, check if the requested resources or access modes can be fulfilled.
Check PVC requirements:
kubectl get pvc my-app-data -o yaml | grep -E "storage|accessModes" -A 1Check available PVs:
kubectl get pv -o yaml- Capacity: Ensure there's a PV with
capacity.storageequal to or greater than your PVC'sresources.requests.storage. - Access Modes: The PV's
accessModesmust be compatible with (contain) the PVC's requestedaccessModes. For example, a PVC requestingReadWriteManycannot be satisfied by a PV offering onlyReadWriteOnce.
If a static PV requires
ReadWriteManyand you're usingHostPath, it will only work if multiple pods on the same node require access.HostPathfundamentally doesn't support trueReadWriteManyacross multiple nodes in a multi-node cluster. For WSL2 single-node K3s/MicroK8s,ReadWriteOnceis usually sufficient, orReadWriteManyif all accessing pods are on the same (and only) node.- Capacity: Ensure there's a PV with
- Action: If there's a mismatch, either modify your PVC to request less storage or compatible access modes (requires deleting and recreating the PVC), or create/configure a PV/StorageClass that can meet the PVC's demands.
6. Examine Provisioner Logs (If Dynamic Provisioning is Failing)
If your dynamic provisioner pod is running but PVCs are still pending, dive into its logs for specific errors.
# Get the provisioner pod name (adjust namespace and label selector as needed)
PROVISIONER_POD=$(kubectl get pods -n kube-system -l app=local-path-provisioner -o jsonpath='{.items[0].metadata.name}')
# Stream its logs
kubectl logs -n kube-system $PROVISIONER_POD --follow
The logs might reveal file system permissions errors, invalid arguments, or connectivity issues that kubectl describe doesn't expose.
7. WSL2 Specific Considerations
While most Kubernetes troubleshooting applies universally, WSL2 has unique characteristics.
Filesystem Permissions: Ensure that any paths used by
HostPathPVs orlocal-path-provisionerwithin the WSL2 filesystem have the correct user/group ownership and permissions for the processes running inside your containers. Kubernetes typically uses abstract UIDs/GIDs (e.g., 1000, 65534), which must map correctly to the WSL2 host.Resource Allocation: WSL2 by default allocates a portion of your Windows machine's resources. Ensure your WSL2 VM has sufficient memory and CPU allocated to run Kubernetes and your workloads, especially if using a resource-intensive storage provisioner. You can configure this in your
~/.wslconfigfile in Windows.Networking: If your storage solution involves external services (like an NFS server on your local network or another VM), ensure Windows Firewall is not blocking communication between your WSL2 Ubuntu distro and the storage server.
Reboot WSL2: Sometimes, a fresh start for the WSL2 environment can resolve transient issues.
# From Windows PowerShell/CMD wsl --shutdown wsl # To restart your default distroThen, recheck your Kubernetes cluster and PVCs.
Our Production Verification Guarantee
Encountering a bug not covered here or running a non-standard kernel configuration? Our solutions are continually refined against real production incidents. Submit an environment trace for our editorial team to replicate.