kubernetes/kubernetes · warning

PVC has empty pvc.Spec.VolumeName

Error message

PVC has empty pvc.Spec.VolumeName

What it means

Returned by getPVCExtractPV when a PVC is marked Bound but its Spec.VolumeName is empty. A bound claim must reference a PV name; an empty one is corrupt/inconsistent state that the volume manager cannot resolve to a PV.

Source

Thrown at pkg/kubelet/volumemanager/populator/desired_state_of_world_populator.go:553

	// that can happen is that such pod is scheduled. This was the default
	// behavior in 1.8 and earlier and users should not be that surprised.
	// It should happen only in very rare case when scheduler schedules
	// a pod and user deletes a PVC that's used by it at the same time.
	//
	// 2) Adding a check for kubernetes.io/pvc-protection here to prevent
	// the existing running pods from being affected during the rebuild of
	// the desired state of the world cache when the kubelet is restarted.
	// It is safe for kubelet to add this check here because the PVC will
	// be stuck in Terminating state until the pod is deleted.
	if pvc.ObjectMeta.DeletionTimestamp != nil && !slices.Contains(pvc.Finalizers, util.PVCProtectionFinalizer) {
		return nil, errors.New("PVC is being deleted")
	}

	if pvc.Status.Phase != v1.ClaimBound {
		return nil, errors.New("PVC is not bound")
	}
	if pvc.Spec.VolumeName == "" {
		return nil, errors.New("PVC has empty pvc.Spec.VolumeName")
	}

	return pvc, nil
}

// getPVSpec fetches the PV object with the given name from the API server
// and returns a volume.Spec representing it.
// An error is returned if the call to fetch the PV object fails.
func (dswp *desiredStateOfWorldPopulator) getPVSpec(
	ctx context.Context,
	name string,
	pvcReadOnly bool,
	expectedClaimUID types.UID) (*volume.Spec, string, error) {
	pv, err := dswp.kubeClient.CoreV1().PersistentVolumes().Get(ctx, name, metav1.GetOptions{})
	if err != nil || pv == nil {
		return nil, "", fmt.Errorf(
			"failed to fetch PV %s from API server: %v", name, err)
	}

View on GitHub (pinned to b882c60b40)

Solutions

  1. Inspect the PVC: kubectl get pvc <name> -o yaml; confirm spec.volumeName is truly empty.
  2. Delete and recreate the PVC so the binder/provisioner reconciles it cleanly.
  3. Check the persistent-volume-controller and provisioner logs for the binding failure.
  4. If a PV exists, manually set the claim's spec.volumeName (advanced) or recreate both.
Defensive patterns

Strategy: validation

Validate before calling

// Validate a bound PVC actually references a PV name.
func pvcHasVolume(pvc *corev1.PersistentVolumeClaim) bool {
    return pvc.Status.Phase == corev1.ClaimBound && pvc.Spec.VolumeName != ""
}

Type guard

func isPVCEmptyVolumeName(err error) bool {
    return err != nil && err.Error() == "PVC has empty pvc.Spec.VolumeName"
}

Try / catch

// This is corrupt state, not transient. Recreate the PVC rather than retrying blindly.
if _, err := getPVCExtractPV(...); err != nil {
    if isPVCEmptyVolumeName(err) { /* recreate PVC; investigate binder */ }
}

Prevention

When it happens

Trigger: Returned at desired_state_of_world_populator.go:553 when pvc.Spec.VolumeName == "". Hit after the Bound-phase check passes.

Common situations: Corrupted PVC object (controller bug, failed binding mid-write), an external provisioner that set phase=Bound without setting VolumeName, or recovery from a bad etcd state.

Related errors


AI-assisted analysis of kubernetes/kubernetes@b882c60b40 (2026-08-07). Data as JSON: /api/errors/863b0c2452774add. Report an issue: GitHub.