{"id":5912,"date":"2026-10-16T15:08:12","date_gmt":"2026-10-16T12:08:12","guid":{"rendered":"https:\/\/www.dchost.com\/blog\/?p=5912"},"modified":"2026-10-16T03:20:16","modified_gmt":"2026-10-16T00:20:16","slug":"kubernetes-pvc-stuck-pending","status":"publish","type":"post","link":"https:\/\/www.dchost.com\/blog\/en\/kubernetes-pvc-stuck-pending\/","title":{"rendered":"Kubernetes PVC Stuck Pending: Diagnose Storage Provisioning and Binding"},"content":{"rendered":"<div class=\"dchost-blog-content-wrapper\"><div class=\"aiw-summary\">\n<p class=\"aiw-box-title\">Quick summary<\/p>\n<p>A Kubernetes persistent volume claim (PVC) stuck Pending means your application&#8217;s persistent storage request has not yet been matched with a storage resource. Possible causes include an incorrect storage class, missing disk-creation permissions, capacity limits or a wait for node selection. It does not necessarily indicate a disk attachment failure.<\/p>\n<ul>\n<li>Start with the claim&#8217;s Events section to find out why it is waiting.<\/li>\n<li>StorageClass defines the storage driver and settings used to provide storage.<\/li>\n<li>WaitForFirstConsumer can delay provisioning-the creation or allocation of storage-until Kubernetes determines placement for the Pod, the unit running your application&#8217;s containers.<\/li>\n<li>Binding the claim to storage and attaching that storage to the node running your application are separate stages.<\/li>\n<\/ul>\n<\/div>\n<p>If your application is deployed but its Pod will not start and its storage request remains Pending, check events first. These messages help you distinguish storage-class, provisioning and node-selection problems so you can target the cause rather than change settings at random.<\/p>\n<p><strong>Example scenario:<\/strong> On a hypothetical existing Amazon EKS cluster, AWS&#8217;s managed Kubernetes environment, you&#8217;ll diagnose a claim with an incorrect storage-class name. You&#8217;ll confirm that it has no associated PV, disk or data before recreating it with the correct class, then check that the consumer Pod can write and read a file-not just that its status changes.<\/p>\n<div id=\"toc_container\" role=\"navigation\" aria-label=\"Table of Contents\" data-nosnippet class=\"toc_transparent no_bullets toc_numbered toc_title_center\"><p class=\"toc_title\">\u0130\u00e7indekiler<\/p><ul class=\"toc_list\"><li><a href=\"#Diagnose_PVC_Pending_through_events\"><span class=\"toc_number toc_depth_1\">1.<\/span> Diagnose PVC Pending through events<\/a><ul><li><a href=\"#Check_the_cluster_and_namespace_first\"><span class=\"toc_number toc_depth_2\">1.1.<\/span> Check the cluster and namespace first<\/a><\/li><li><a href=\"#Use_PVC_events_to_identify_the_failing_stage\"><span class=\"toc_number toc_depth_2\">1.2.<\/span> Use PVC events to identify the failing stage<\/a><\/li><\/ul><\/li><li><a href=\"#Verify_the_example_environment_and_fix_the_StorageClass_error\"><span class=\"toc_number toc_depth_1\">2.<\/span> Verify the example environment and fix the StorageClass error<\/a><ul><li><a href=\"#Check_EKS_the_driver_and_permissions\"><span class=\"toc_number toc_depth_2\">2.1.<\/span> Check EKS, the driver and permissions<\/a><\/li><li><a href=\"#Create_the_example_StorageClass\"><span class=\"toc_number toc_depth_2\">2.2.<\/span> Create the example StorageClass<\/a><\/li><li><a href=\"#Recreate_only_a_verified_empty_unbound_claim\"><span class=\"toc_number toc_depth_2\">2.3.<\/span> Recreate only a verified empty, unbound claim<\/a><\/li><\/ul><\/li><li><a href=\"#Create_the_consumer_Pod_and_verify_disk_access\"><span class=\"toc_number toc_depth_1\">3.<\/span> Create the consumer Pod and verify disk access<\/a><ul><li><a href=\"#Start_the_Pod_that_uses_the_claim\"><span class=\"toc_number toc_depth_2\">3.1.<\/span> Start the Pod that uses the claim<\/a><\/li><li><a href=\"#Verify_binding_and_file_access\"><span class=\"toc_number toc_depth_2\">3.2.<\/span> Verify binding and file access<\/a><\/li><\/ul><\/li><li><a href=\"#If_Pending_persists_check_CSI_capacity_and_access_modes\"><span class=\"toc_number toc_depth_1\">4.<\/span> If Pending persists, check CSI, capacity and access modes<\/a><ul><li><a href=\"#Identify_permission_errors_in_CSI_logs\"><span class=\"toc_number toc_depth_2\">4.1.<\/span> Identify permission errors in CSI logs<\/a><\/li><li><a href=\"#Distinguish_quotas_from_actual_capacity_shortages\"><span class=\"toc_number toc_depth_2\">4.2.<\/span> Distinguish quotas from actual capacity shortages<\/a><\/li><li><a href=\"#Check_access_modes_and_static_PV_matching\"><span class=\"toc_number toc_depth_2\">4.3.<\/span> Check access modes and static PV matching<\/a><\/li><li><a href=\"#Find_topology_constraints_through_Pod_events\"><span class=\"toc_number toc_depth_2\">4.4.<\/span> Find topology constraints through Pod events<\/a><\/li><\/ul><\/li><li><a href=\"#Frequently_Asked_Questions\"><span class=\"toc_number toc_depth_1\">5.<\/span> Frequently Asked Questions<\/a><ul><li><a href=\"#Can_a_Pod_use_a_PVC_from_another_namespace\"><span class=\"toc_number toc_depth_2\">5.1.<\/span> Can a Pod use a PVC from another namespace?<\/a><\/li><li><a href=\"#Does_changing_the_StorageClass_move_an_existing_disk\"><span class=\"toc_number toc_depth_2\">5.2.<\/span> Does changing the StorageClass move an existing disk?<\/a><\/li><li><a href=\"#Does_disk_data_survive_Pod_deletion\"><span class=\"toc_number toc_depth_2\">5.3.<\/span> Does disk data survive Pod deletion?<\/a><\/li><li><a href=\"#Is_a_disk_snapshot_enough_for_an_application_backup\"><span class=\"toc_number toc_depth_2\">5.4.<\/span> Is a disk snapshot enough for an application backup?<\/a><\/li><\/ul><\/li><li><a href=\"#Closing_checklist_Hand_storage_over_to_your_application\"><span class=\"toc_number toc_depth_1\">6.<\/span> Closing checklist: Hand storage over to your application<\/a><ul><li><a href=\"#Clean_up_the_test_resources_while_protecting_data\"><span class=\"toc_number toc_depth_2\">6.1.<\/span> Clean up the test resources while protecting data<\/a><\/li><\/ul><\/li><\/ul><\/div>\n<h2><span id=\"Diagnose_PVC_Pending_through_events\">Diagnose PVC Pending through events<\/span><\/h2>\n<p>A <strong>Kubernetes PVC stuck Pending<\/strong> means that your application&#8217;s persistent storage request has not yet been matched with a storage resource. A PVC (PersistentVolumeClaim) describes the storage you need, including capacity and access mode-how nodes may access it. A PV (PersistentVolume) represents the disk or other storage resource available to meet that request. When a PVC is <code>Bound<\/code>, it has been matched with a PV; that alone does not prove the application can use the disk.<\/p>\n<figure class=\"aiw-figure\" data-aiw-visual-id=\"5908\" data-aiw-visual-key=\"e140b6e77efda496157c50e5adadd5887d369e60f9f4bd8215c9218a1b4bed50\"><img decoding=\"async\" src=\"https:\/\/www.dchost.com\/blog\/wp-content\/uploads\/2026\/10\/dchost-flow-en-977481435d16e93deadb.png\" alt=\"A diagnostic flow starts with context and namespace, then branches by PVC status into provisioning checks or Pod attach and mount errors.\" width=\"840\" height=\"1197\" loading=\"lazy\" class=\"aiw-article-visual wp-image-5908\" srcset=\"https:\/\/www.dchost.com\/blog\/wp-content\/uploads\/2026\/10\/dchost-flow-en-977481435d16e93deadb.png 840w, https:\/\/www.dchost.com\/blog\/wp-content\/uploads\/2026\/10\/dchost-flow-en-977481435d16e93deadb-211x300.png 211w, https:\/\/www.dchost.com\/blog\/wp-content\/uploads\/2026\/10\/dchost-flow-en-977481435d16e93deadb-719x1024.png 719w, https:\/\/www.dchost.com\/blog\/wp-content\/uploads\/2026\/10\/dchost-flow-en-977481435d16e93deadb-768x1094.png 768w\" sizes=\"auto, (max-width: 840px) 100vw, 840px\" \/><figcaption>Workflow diagram: This flow separates a claim that has not bound to storage from a bound volume that its Pod cannot yet use.<\/figcaption><\/figure>\n<p>A Pod is the unit that runs your application containers. If it will not start, first check whether its storage claim is still waiting or has already bound to a volume that the Pod cannot use. These are different stages, and identifying the failing stage helps you avoid changing the wrong resource.<\/p>\n<h3><span id=\"Check_the_cluster_and_namespace_first\">Check the cluster and namespace first<\/span><\/h3>\n<p><strong>Example scenario:<\/strong> This hypothetical environment uses an existing Amazon EKS cluster with Linux worker nodes. EKS is AWS&#8217;s managed Kubernetes service; cluster setup is outside this walkthrough. Run the following commands in your administrator terminal with <code>kubectl<\/code> access to the cluster. The context determines which cluster and user identity your commands use; the namespace is the logical area containing your resources.<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">kubectl config current-context\nkubectl config view --minify\nkubectl get namespace pvc-demo\nkubectl get pvc -n pvc-demo<\/code><\/pre>\n<p>Check that the cluster and user details in the first two outputs belong to your intended EKS environment. The last two commands should show the namespace and its claims. The example claim is <code>app-data<\/code> in the <code>pvc-demo<\/code> namespace. When investigating your own issue, substitute your existing resource names. If you see <code>NotFound<\/code> or an empty list, check the namespace and confirm that the resource exists before investigating storage. <code>Forbidden<\/code> means the identity running the command lacks permission for that operation.<\/p>\n<h3><span id=\"Use_PVC_events_to_identify_the_failing_stage\">Use PVC events to identify the failing stage<\/span><\/h3>\n<p>Once you have found the correct claim, read its details and events. Events record what Kubernetes has attempted and why an operation is waiting or failing. The first command shows the claim&#8217;s current state; the second lists its events by creation time. Neither command changes resources:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">kubectl describe pvc app-data -n pvc-demo\nkubectl get events -n pvc-demo --field-selector involvedObject.kind=PersistentVolumeClaim,involvedObject.name=app-data --sort-by=.metadata.creationTimestamp<\/code><\/pre>\n<p>In the <code>describe<\/code> output, <code>Status<\/code> shows the claim&#8217;s state, <code>StorageClass<\/code> identifies its storage class, and <code>Volume<\/code> shows the bound PV&#8217;s name. A storage class specifies the driver and settings used to provision, or create, the disk. In the <code>Events<\/code> section, read each event&#8217;s reason together with its message:<\/p>\n<ul>\n<li><code>storageclass.storage.k8s.io \"gp3-typo\" not found<\/code>: The storage class named in the claim does not exist. Check whether the claim&#8217;s class name matches a class defined in the cluster.<\/li>\n<li><code>WaitForFirstConsumer<\/code>: Disk provisioning may be waiting until Kubernetes determines which node will run the Pod that uses the claim. This consumer Pod must exist and be eligible for placement on a node. Waiting at this stage is not necessarily a fault.<\/li>\n<li><code>ProvisioningFailed<\/code>: Disk creation or allocation failed. The permission, capacity or driver details in the message determine which setting or component to investigate next.<\/li>\n<\/ul>\n<p>Events are kept for a limited time, so an empty <code>Events<\/code> list does not rule out a problem. If the PVC is <code>Bound<\/code> but the application will not start, binding has already succeeded. Look for the next clue in the Pod&#8217;s events:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">kubectl describe pod disk-check -n pvc-demo<\/code><\/pre>\n<p>This command inspects the example&#8217;s consumer Pod-the Pod that uses the claim. In your environment, substitute the name of the Pod that uses your claim. If the Pod has not been created yet, a response saying it cannot be found is not a disk attachment error.<\/p>\n<p>If the Pod exists, <code>FailedAttachVolume<\/code> points to a problem attaching the disk to the node, while <code>FailedMount<\/code> points to a problem making the storage accessible to the Pod. Read the rest of the event message to identify the affected volume and the reason the operation failed. These are different from an unbound storage claim. Follow the cause reported in the Pod event rather than deleting the bound claim.<\/p>\n<h2><span id=\"Verify_the_example_environment_and_fix_the_StorageClass_error\">Verify the example environment and fix the StorageClass error<\/span><\/h2>\n<p>If the event reports <code>gp3-typo not found<\/code>, the claim names a storage class that does not exist. Before creating the correct class, check that its storage driver is ready. This hypothetical example uses standard Amazon EKS with Linux EC2 worker nodes; the same settings do not apply to EKS Auto Mode or Fargate-only clusters.<\/p>\n<h3><span id=\"Check_EKS_the_driver_and_permissions\">Check EKS, the driver and permissions<\/span><\/h3>\n<p>Run these commands in your administrator terminal with <code>kubectl<\/code> and the AWS CLI, AWS&#8217;s command-line tool, installed and authenticated. Your AWS identity needs permission to read the target cluster and its add-ons. Replace the example cluster name and region with your values. The commands show your AWS identity and let you compare the cluster endpoint-its API address-with the address used by kubectl:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">export CLUSTER=my-eks\nexport AWS_REGION=eu-central-1\naws sts get-caller-identity\naws eks describe-cluster --name &quot;$CLUSTER&quot; --region &quot;$AWS_REGION&quot; --query &#039;cluster.{Status:status,Endpoint:endpoint}&#039;\nkubectl config view --minify -o jsonpath=&#039;{.clusters[0].cluster.server}&#039;\nkubectl get nodes -L kubernetes.io\/os,topology.kubernetes.io\/zone\nkubectl auth can-i create storageclasses.storage.k8s.io\nkubectl auth can-i create persistentvolumeclaims -n pvc-demo\nkubectl auth can-i delete persistentvolumeclaims -n pvc-demo\nkubectl auth can-i list persistentvolumes<\/code><\/pre>\n<p>Confirm that the identity output shows the intended AWS account. The cluster status should be <code>ACTIVE<\/code>, the API endpoints in both outputs should match, and suitable Linux nodes should be <code>Ready<\/code>. The permission checks should return <code>yes<\/code>. Together, these checks help confirm that you are working in the intended cluster and have permission for the listed operations. If a permission is missing, ask your cluster administrator for access to that specific resource and operation rather than broad administrator access.<\/p>\n<p>CSI, the Container Storage Interface, connects Kubernetes to storage drivers. This example assumes that the AWS EBS CSI add-on and the IAM role needed to create disks already exist. EBS provides the AWS block storage disks used here; IAM controls access to AWS resources. Use the following commands to inspect the add-on&#8217;s status, its role association and the driver components in Kubernetes. Your AWS identity also needs permission to list Pod Identity associations:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">aws eks describe-addon --cluster-name &quot;$CLUSTER&quot; --addon-name aws-ebs-csi-driver --region &quot;$AWS_REGION&quot; --query &#039;addon.{Status:status,Role:serviceAccountRoleArn,PodIdentity:podIdentityAssociations,Health:health.issues}&#039;\naws eks list-pod-identity-associations --cluster-name &quot;$CLUSTER&quot; --namespace kube-system --service-account ebs-csi-controller-sa --region &quot;$AWS_REGION&quot;\nkubectl get csidriver ebs.csi.aws.com\nkubectl -n kube-system get deployment ebs-csi-controller\nkubectl -n kube-system get daemonset ebs-csi-node\nkubectl get csinode -o yaml<\/code><\/pre>\n<p>The add-on should be <code>ACTIVE<\/code>. For the controller Deployment, the ready replica count should match the desired count; for the node DaemonSet, the ready count should match the desired number of nodes. In each target node&#8217;s <code>CSINode<\/code> record, an <code>ebs.csi.aws.com<\/code> entry shows that the driver has registered on that node.<\/p>\n<p>The driver can receive its IAM role through a service-account role or a Pod Identity association. Pod Identity assigns AWS roles to Pods; you do not need both methods configured at once. A role association alone does not prove that the driver can create disks; the provisioning attempt will check those permissions in practice. If the add-on is missing or not ready, resolve that prerequisite before continuing. Creating a storage class does not install the driver.<\/p>\n<h3><span id=\"Create_the_example_StorageClass\">Create the example StorageClass<\/span><\/h3>\n<p>A <code>StorageClass<\/code> specifies the driver and settings used to create a disk. First check for an existing definition with <code>kubectl get storageclass gp3-demo -o yaml<\/code>. If the class is not found, create and apply the file below in your terminal. If it already exists, compare its fields with the example rather than overwriting different settings. Continue only when the class matches the intended settings. If you skip creation, make sure your working directory still contains a pvc-demo folder for the files saved later.<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">mkdir -p .\/pvc-demo\ncat &gt; .\/pvc-demo\/storageclass.yaml &lt;&lt;&#039;EOF&#039;\napiVersion: storage.k8s.io\/v1\nkind: StorageClass\nmetadata:\n  name: gp3-demo\nprovisioner: ebs.csi.aws.com\nparameters:\n  type: gp3\nvolumeBindingMode: WaitForFirstConsumer\nreclaimPolicy: Retain\nEOF\nkubectl apply -f .\/pvc-demo\/storageclass.yaml\nkubectl get storageclass gp3-demo -o yaml<\/code><\/pre>\n<p>The file names the class gp3-demo, selects the standard EBS CSI driver and requests the gp3 disk type. The driver and settings in the final output should match the file. Creating this class alone does not create a disk.<\/p>\n<p><code>WaitForFirstConsumer<\/code> delays disk creation until Kubernetes determines placement for the Pod that will use it. <code>Retain<\/code> prevents automatic disk deletion when the claim is deleted. This preserves the disk, but cloud charges may apply once it is created, and manual cleanup is required when you finish using it.<\/p>\n<h3><span id=\"Recreate_only_a_verified_empty_unbound_claim\">Recreate only a verified empty, unbound claim<\/span><\/h3>\n<p>You cannot directly change the incorrect class name on the existing PVC in this example. To correct it, you must recreate the claim. First, run these commands in your administrator terminal to save its current definition and check for an associated PV or disk. The AWS command also requires permission to list EBS volumes:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">kubectl get pvc app-data -n pvc-demo -o yaml &gt; .\/pvc-demo\/pvc-before.yaml\nkubectl get pvc app-data -n pvc-demo -o jsonpath=&#039;{.status.phase}{&quot;\\n&quot;}{.spec.volumeName}{&quot;\\n&quot;}{.metadata.uid}{&quot;\\n&quot;}&#039;\nkubectl get pv -o custom-columns=&#039;NAME:.metadata.name,NS:.spec.claimRef.namespace,CLAIM:.spec.claimRef.name,UID:.spec.claimRef.uid,DISK:.spec.csi.volumeHandle&#039;\nkubectl describe pvc app-data -n pvc-demo\nPVC_UID=$(kubectl get pvc app-data -n pvc-demo -o jsonpath=&#039;{.metadata.uid}&#039;)\naws ec2 describe-volumes --region &quot;$AWS_REGION&quot; --filters &quot;Name=tag:CSIVolumeName,Values=pvc-$PVC_UID&quot; --query &#039;Volumes[].VolumeId&#039;<\/code><\/pre>\n<p>Before proceeding, the status must be <code>Pending<\/code> and <code>volumeName<\/code> must be empty. Compare the claim&#8217;s unique identifier from the second command with the UID column in the PV list. There must be no PV associated with this claim, and <code>Used By<\/code> in the claim&#8217;s details must show no Pod using it. All these checks must pass; Pending status alone is not enough.<\/p>\n<p>The last command searches AWS for disks using the driver&#8217;s standard tags. If provisioning was previously attempted, an empty result alone does not prove that no disk was created. Also inspect the CSI controller logs, using the commands in the troubleshooting section below, and the EC2 console&#8217;s Volumes list in the same AWS account and region. A permission error is not an empty result. If you find an associated disk or cannot establish whether one exists, stop before deletion.<\/p>\n<div class=\"aiw-note aiw-note-warning\">\n<p class=\"aiw-box-title\">Caution<\/p>\n<p>This procedure applies only to an incorrect claim confirmed to have no PV, disk or data. The saved YAML file preserves the resource definition; it is not a data backup. Do not delete the claim if it has become Bound or its data status is uncertain. This procedure does not migrate data.<\/p>\n<\/div>\n<p>If all checks pass, create the replacement file below in the same terminal. It selects the storage class you just checked. <code>5Gi<\/code> requests five gibibytes of capacity, <code>ReadWriteOnce<\/code> allows read-write access from a single node, and <code>Filesystem<\/code> means the disk will be used as a filesystem. Run the deletion command separately and wait for it to complete before running the apply command:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">cat &gt; .\/pvc-demo\/pvc.yaml &lt;&lt;&#039;EOF&#039;\napiVersion: v1\nkind: PersistentVolumeClaim\nmetadata:\n  name: app-data\n  namespace: pvc-demo\nspec:\n  storageClassName: gp3-demo\n  accessModes:\n    - ReadWriteOnce\n  volumeMode: Filesystem\n  resources:\n    requests:\n      storage: 5Gi\nEOF\nkubectl delete pvc app-data -n pvc-demo\nkubectl apply -f .\/pvc-demo\/pvc.yaml\nkubectl describe pvc app-data -n pvc-demo<\/code><\/pre>\n<p>If deletion does not complete, do not force it or remove protection fields. Investigate why it is waiting before applying the replacement claim. After a successful apply, the new claim&#8217;s details should show <code>gp3-demo<\/code> as its class. With no consumer Pod yet, it is expected to remain Pending because of <code>WaitForFirstConsumer<\/code>. Its events should now indicate that it is waiting for a consumer, rather than reporting a missing class.<\/p>\n<p>If you need to roll back, repeat the checks above on the new claim: it must still be unbound, unused and have no associated disk or data. Edit a copy of your saved <code>pvc-before.yaml<\/code>: remove the top-level <code>status<\/code> field; under metadata, remove <code>uid<\/code>, <code>resourceVersion<\/code>, <code>creationTimestamp<\/code>, <code>managedFields<\/code>, and controller-added annotations and finalizers. Make these edits only in the file copy; do not remove protection fields from the live resource.<\/p>\n<p>Then delete the empty replacement claim, wait for deletion to finish, and apply the cleaned file. This restores only the previous claim definition; it does not resolve the <code>gp3-typo<\/code> error. If you are continuing with the corrected claim instead, leave it in place and create its consumer Pod in the next step.<\/p>\n<h2><span id=\"Create_the_consumer_Pod_and_verify_disk_access\">Create the consumer Pod and verify disk access<\/span><\/h2>\n<p>Once <code>app-data<\/code> uses the correct class, create the consumer Pod: the Pod that uses the claim. With <code>WaitForFirstConsumer<\/code>, the scheduler-the component that chooses which node runs a Pod-first determines suitable placement. The CSI driver then creates the disk to match that placement. This avoids creating a disk in an availability zone where the Pod cannot run.<\/p>\n<figure class=\"aiw-figure\" data-aiw-visual-id=\"5910\" data-aiw-visual-key=\"f52e544f04268efee6cac74611aeed6479e5a48a240f1c2c7ad9a1cb6c358263\"><img decoding=\"async\" src=\"https:\/\/www.dchost.com\/blog\/wp-content\/uploads\/2026\/10\/dchost-flow-en-125ffa954fb46e650173.png\" alt=\"A flow connects a waiting storage claim to consumer placement, availability-zone selection, disk provisioning and a file write\/read check.\" width=\"840\" height=\"1328\" loading=\"lazy\" class=\"aiw-article-visual wp-image-5910\" srcset=\"https:\/\/www.dchost.com\/blog\/wp-content\/uploads\/2026\/10\/dchost-flow-en-125ffa954fb46e650173.png 840w, https:\/\/www.dchost.com\/blog\/wp-content\/uploads\/2026\/10\/dchost-flow-en-125ffa954fb46e650173-190x300.png 190w, https:\/\/www.dchost.com\/blog\/wp-content\/uploads\/2026\/10\/dchost-flow-en-125ffa954fb46e650173-648x1024.png 648w, https:\/\/www.dchost.com\/blog\/wp-content\/uploads\/2026\/10\/dchost-flow-en-125ffa954fb46e650173-768x1214.png 768w\" sizes=\"auto, (max-width: 840px) 100vw, 840px\" \/><figcaption>Workflow diagram: The diagram shows how the consumer Pod triggers disk provisioning in the example and where file access is checked; it does not report a performed test.<\/figcaption><\/figure>\n<h3><span id=\"Start_the_Pod_that_uses_the_claim\">Start the Pod that uses the claim<\/span><\/h3>\n<p>Create and apply the following file in your administrator terminal. The Pod runs in the same <code>pvc-demo<\/code> namespace as the PVC. The field <code>claimName: app-data<\/code> selects the claim, and <code>mountPath: \/data<\/code> makes the disk&#8217;s filesystem accessible at that path inside the container.<\/p>\n<p>Your nodes must be able to reach the image registry to download the BusyBox image used here. After applying the file, the final command watches the claim&#8217;s status so you can see when storage binding completes.<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">cat &gt; .\/pvc-demo\/pod.yaml &lt;&lt;&#039;EOF&#039;\napiVersion: v1\nkind: Pod\nmetadata:\n  name: disk-check\n  namespace: pvc-demo\nspec:\n  nodeSelector:\n    kubernetes.io\/os: linux\n  containers:\n    - name: check\n      image: busybox:1.36\n      command: [&quot;sh&quot;, &quot;-c&quot;, &quot;sleep 86400&quot;]\n      volumeMounts:\n        - name: data\n          mountPath: \/data\n  volumes:\n    - name: data\n      persistentVolumeClaim:\n        claimName: app-data\nEOF\nkubectl apply -f .\/pvc-demo\/pod.yaml\nkubectl get pvc app-data -n pvc-demo -w<\/code><\/pre>\n<p>The expected transition is from <code>Pending<\/code> to <code>Bound<\/code>. Stop watching with <code>Ctrl+C<\/code>; this does not delete any resources. If the status does not change, stop the watch and inspect the events as shown below. Do not add <code>nodeName<\/code> to the Pod definition: assigning a node directly bypasses the scheduler and can leave the claim Pending in this binding mode.<\/p>\n<p>An AWS region contains multiple availability zones: <code>eu-central-1<\/code> is a region, while <code>eu-central-1a<\/code> is an availability zone within it. An EBS disk must be in the same availability zone as the EC2 node that uses it. This example does not select a zone manually. When choosing a suitable Linux node, the scheduler also considers where the storage can be used.<\/p>\n<h3><span id=\"Verify_binding_and_file_access\">Verify binding and file access<\/span><\/h3>\n<p>After stopping the watch, run these commands in the same terminal. The first waits for the Pod to become ready. The remaining commands show the claim&#8217;s status, the node assigned to the Pod and the nodes&#8217; availability zones. Here, <code>180s<\/code> is only the command&#8217;s timeout, not a guarantee of disk provisioning time.<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">kubectl wait -n pvc-demo --for=condition=Ready pod\/disk-check --timeout=180s\nkubectl get pvc app-data -n pvc-demo\nkubectl get pod disk-check -n pvc-demo -o wide\nkubectl get nodes -L topology.kubernetes.io\/zone<\/code><\/pre>\n<p>The PVC should be <code>Bound<\/code>, the Pod should be <code>Running<\/code>, and the ready container count should be <code>1\/1<\/code>. Find the node named in the Pod output&#8217;s <code>NODE<\/code> column in the node list. Its zone label tells you which availability zone the Pod is running in.<\/p>\n<p>If the wait times out, read the Events sections in the following outputs to identify where the process has stalled. Use those messages to guide the next check rather than recreating resources:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">kubectl describe pvc app-data -n pvc-demo\nkubectl describe pod disk-check -n pvc-demo<\/code><\/pre>\n<p>If the Pod is ready, run the following command from your terminal to test file access. It writes a file to the container&#8217;s disk-mounted <code>\/data<\/code> directory, then reads the contents only if the write succeeds. <code>set -C<\/code> prevents overwriting an existing file with the same name.<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">kubectl exec -n pvc-demo disk-check -- sh -c &#039;set -C; printf &quot;disk kontrolu tamam\\n&quot; &gt; \/data\/check.txt &amp;&amp; cat \/data\/check.txt&#039;<\/code><\/pre>\n<p>The expected output is <code>disk kontrolu tamam<\/code>. If the file already exists, read it with <code>kubectl exec -n pvc-demo disk-check -- cat \/data\/check.txt<\/code>; this checks reading but does not perform a new write.<\/p>\n<p>A successful write and read demonstrate that the mounted filesystem is usable, not just that the PVC is bound. This check does not test whether data persists after recreating the Pod or whether you can restore it from a backup.<\/p>\n<h2><span id=\"If_Pending_persists_check_CSI_capacity_and_access_modes\">If Pending persists, check CSI, capacity and access modes<\/span><\/h2>\n<p>If your PVC remains Pending, follow the cause reported in its events rather than changing several settings at once. Run the following commands in the same administrator terminal. After each correction, look for new events in <code>kubectl describe pvc app-data -n pvc-demo<\/code>; older errors may remain visible even after the problem is resolved.<\/p>\n<h3><span id=\"Identify_permission_errors_in_CSI_logs\">Identify permission errors in CSI logs<\/span><\/h3>\n<p><code>ExternalProvisioning<\/code> alone is not a permission error; it means Kubernetes is waiting for the component that provisions the disk. If you see <code>ProvisioningFailed<\/code>, read the accompanying message, then inspect the CSI controller logs. The first command shows disk-operation logs; the second shows logs from the component coordinating provisioning. Both retrieve entries from the last 15 minutes:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">kubectl logs -n kube-system deployment\/ebs-csi-controller -c ebs-plugin --since=15m\nkubectl logs -n kube-system deployment\/ebs-csi-controller -c csi-provisioner --since=15m<\/code><\/pre>\n<p>These commands select a controller Pod rather than collecting logs from every controller Pod. If the relevant error is missing, inspect the other controller Pods&#8217; logs too. Messages containing <code>UnauthorizedOperation<\/code> or <code>AccessDenied<\/code> can identify the denied action and resource, helping you locate the permission problem.<\/p>\n<p>In the AWS console, open <strong>IAM &#8211; Roles<\/strong> and select the CSI role you checked earlier. Save a copy of the permissions policy before editing it, then address the denied action and resource without granting unrelated access. If the message mentions an encryption key in KMS, AWS&#8217;s key management service, inspect that key&#8217;s policy too. Adding permissions to your administrator user does not change the CSI role&#8217;s permissions.<\/p>\n<p>After saving the correction, check the logs and PVC events during the next provisioning attempt. The same denial should no longer recur, and provisioning should progress. If you need to undo the policy change, restore the saved policy.<\/p>\n<h3><span id=\"Distinguish_quotas_from_actual_capacity_shortages\">Distinguish quotas from actual capacity shortages<\/span><\/h3>\n<p><code>exceeded quota<\/code> can point to a Kubernetes quota, while <code>VolumeLimitExceeded<\/code> can indicate an AWS-side limit. A quota caps your allowed usage; available capacity describes whether the provider can supply the disk at that moment. To check namespace quotas, compare their current usage with their limits:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">kubectl describe resourcequota -n pvc-demo<\/code><\/pre>\n<p>In the output, <strong>Used<\/strong> shows current usage and <strong>Hard<\/strong> shows the allowed limit. A Kubernetes quota usually rejects claim creation or expansion, so it does not, by itself, explain why an existing claim remains Pending.<\/p>\n<p>For an AWS limit, select the region used for the example, open <strong>Service Quotas<\/strong> and inspect the Amazon EBS quotas. Compare usage with the limit named in the error. If that limit has been reached, request an increase for the specific quota rather than deleting disks as your first response.<\/p>\n<p>For provider capacity errors such as <code>InsufficientCapacity<\/code>, a quota increase may not help. Check any availability zone identified in the message and monitor the driver&#8217;s subsequent provisioning attempts if the shortage is temporary. Consider another zone only if the Pod&#8217;s placement requirements and the data&#8217;s location allow it; changing placement does not move an existing disk. Check for new provisioning events and the PVC becoming <code>Bound<\/code> to see whether the correction worked.<\/p>\n<h3><span id=\"Check_access_modes_and_static_PV_matching\">Check access modes and static PV matching<\/span><\/h3>\n<p>If an event reports <code>unsupported access mode<\/code> or no suitable volume, run these commands in your administrator terminal. Compare the claim&#8217;s requested access mode and capacity with what the storage can provide:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">kubectl get pvc app-data -n pvc-demo -o yaml\nkubectl get pv -o yaml<\/code><\/pre>\n<p>The gp3 disk in this example cannot satisfy <code>ReadWriteMany<\/code>, which requests writable access from multiple nodes. This example requires <code>ReadWriteOnce<\/code>: writable access from one node, not necessarily just one Pod. Recreate an incorrect claim only if the earlier checks confirm that no PV, disk or data has been created. If your application needs shared access from multiple nodes, choose storage that supports it rather than reducing the claim&#8217;s requirements.<\/p>\n<p>If you use a static PV-a volume created in advance-its capacity must be at least <code>5Gi<\/code> for this example. Its storage class must match the claim&#8217;s class, and <code>volumeMode<\/code> must match the requested filesystem or block-device usage. The PV&#8217;s access modes must also satisfy the claim. Check any additional matching fields: the claim&#8217;s <code>selector<\/code> requires matching PV labels, its <code>volumeName<\/code> targets a particular PV, and the PV&#8217;s <code>claimRef<\/code> identifies the claim for which it is reserved.<\/p>\n<p>A PV reserved for another claim or in the <code>Released<\/code> state is not automatically available for reuse; it may contain old data. Do not remove <code>claimRef<\/code> until you have confirmed which application owns the data and that reuse is safe. If the data must be preserved, ensure you have a recoverable backup before changing its reservation. Once the claim matches a suitable, available PV, check the claim&#8217;s status to confirm that it transitions to <code>Bound<\/code>.<\/p>\n<h3><span id=\"Find_topology_constraints_through_Pod_events\">Find topology constraints through Pod events<\/span><\/h3>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">kubectl describe pod disk-check -n pvc-demo\nkubectl get storageclass gp3-demo -o yaml\nkubectl get nodes -L topology.kubernetes.io\/zone<\/code><\/pre>\n<p>Run these commands in your administrator terminal to inspect topology constraints: restrictions on where the Pod and its storage can run. The outputs show the Pod&#8217;s scheduling events, the storage class&#8217;s placement limits and the nodes&#8217; availability zones. <code>FailedScheduling<\/code> is a general scheduling error. If its message includes <code>volume node affinity conflict<\/code>, the nodes suitable for the Pod may not match those where the disk can be used.<\/p>\n<p>Compare the Pod&#8217;s <code>nodeSelector<\/code>, which selects nodes by label, and its <code>affinity<\/code> placement requirements with the storage locations permitted by the class&#8217;s <code>allowedTopologies<\/code>. If a PV is already bound, use the PV output from the preceding check to inspect its <code>nodeAffinity<\/code> field as well. These comparisons help you identify whether the restriction comes from the Pod, the storage class or the existing disk&#8217;s location.<\/p>\n<p>Change only a Pod constraint that you have confirmed is wrong. For the example Pod, first save a copy of <code>pod.yaml<\/code>, then edit the incorrect placement requirement in that file. If writes are in progress, stop them safely and flush pending writes before deleting only the <code>disk-check<\/code> Pod and reapplying the edited file. Do not delete the PVC; keeping it preserves the connection to the existing storage. To roll back the placement change, repeat the Pod replacement using the saved definition.<\/p>\n<p>If no suitable node exists in the disk&#8217;s availability zone, provide node capacity in that zone. Changing a node&#8217;s zone label does not move either the disk or the node and will not solve the problem. Run the Pod description command again after the correction: the Pod should have a node assigned, and no new scheduling errors should appear. Once it is ready, return to the earlier file-access check to confirm that it can use the disk.<\/p>\n<h2><span id=\"Frequently_Asked_Questions\">Frequently Asked Questions<\/span><\/h2>\n<h3><span id=\"Can_a_Pod_use_a_PVC_from_another_namespace\">Can a Pod use a PVC from another namespace?<\/span><\/h3>\n<p>No. A Pod can directly use only PVCs in its own namespace. That is why the <code>disk-check<\/code> Pod and the <code>app-data<\/code> claim both belong to <code>pvc-demo<\/code> in the example. Creating a PVC with the same name in another namespace creates a separate storage request; it does not give you access to the existing disk. A PV belongs to the cluster rather than a namespace, but it binds to only one PVC at a time.<\/p>\n<p>If you are moving your application to another namespace, plan to create separate storage at the destination and transfer the data in a controlled way. Trying to share the same EBS disk through two separate PV records is not a safe shortcut.<\/p>\n<h3><span id=\"Does_changing_the_StorageClass_move_an_existing_disk\">Does changing the StorageClass move an existing disk?<\/span><\/h3>\n<p>No. A StorageClass defines how storage is provisioned; it does not move existing data to another disk or availability zone. You cannot change a bound PVC&#8217;s <code>storageClassName<\/code> field through a normal update. Changing the default class does not convert existing claims that already have a class assigned, either.<\/p>\n<p>To move to a different class, create a new PVC that uses the target class. Transfer the data using a backup-and-restore or data-copying method appropriate for your application. During the final transfer, stop application writes or use a synchronization method your application supports. Check that the destination data is complete and readable before switching the application to the new claim. Keep the old disk so you can roll back until you have confirmed that the application works with the new storage.<\/p>\n<h3><span id=\"Does_disk_data_survive_Pod_deletion\">Does disk data survive Pod deletion?<\/span><\/h3>\n<p>In this example, yes: deleting only the <code>disk-check<\/code> Pod does not delete the separately created <code>app-data<\/code> PVC or the EBS disk. A recreated Pod can access the saved files at the same path if it mounts the same PVC at <code>\/data<\/code>. Temporary files in the container&#8217;s own filesystem do not have this protection. Pending writes held in memory but not yet saved to disk can also be lost.<\/p>\n<p>Deleting the PVC is a different operation. The example&#8217;s <code>Retain<\/code> policy preserves the disk but does not automatically bind its existing data to a new claim. With a <code>Delete<\/code> policy, the disk may also be deleted after the PVC is removed. Before testing persistence by deleting the Pod, stop application writes safely and flush pending writes from memory to disk.<\/p>\n<h3><span id=\"Is_a_disk_snapshot_enough_for_an_application_backup\">Is a disk snapshot enough for an application backup?<\/span><\/h3>\n<p>Not always. A disk snapshot captures storage at a particular point in time. It does not include data waiting in a running database&#8217;s memory, and it does not by itself guarantee consistency for operations spanning multiple disks. The database may need to perform recovery when restored.<\/p>\n<p>Use a backup method supported by your database, or pause writes using an application-supported procedure and flush pending data to disk before taking the snapshot. Also securely back up your Kubernetes manifests-the resource definition files-along with the required settings and secrets. Restore the backup in a separate environment, then check that the application starts and can read the data you expect.<\/p>\n<h2><span id=\"Closing_checklist_Hand_storage_over_to_your_application\">Closing checklist: Hand storage over to your application<\/span><\/h2>\n<p>Before handing storage over to your application, check not only that the PVC is bound but also that the Pod can use the disk. Complete this checklist in the same administrator terminal:<\/p>\n<ul>\n<li><strong>PVC\/PV binding:<\/strong> In the output of <code>kubectl get pvc app-data -n pvc-demo -o wide<\/code>, the status should be <code>Bound<\/code>, the capacity <code>5Gi<\/code> and the class <code>gp3-demo<\/code>. Find the PV shown in the <code>VOLUME<\/code> column in the PV list; that record&#8217;s <code>CLAIM<\/code> field should point to <code>pvc-demo\/app-data<\/code>.<\/li>\n<li><strong>Pod placement:<\/strong> In the output of <code>kubectl get pod disk-check -n pvc-demo -o wide<\/code>, check that the status is <code>Running<\/code> and a node has been assigned. If you previously saw a topology error, compare the node&#8217;s availability zone with the PV&#8217;s placement requirements; they must be compatible.<\/li>\n<li><strong>Writing and reading:<\/strong> Read the value you wrote to <code>\/data\/check.txt<\/code> during the earlier file check again using <code>kubectl exec -n pvc-demo disk-check -- cat \/data\/check.txt<\/code>. You should see the expected contents; a running Pod alone does not prove it has write access.<\/li>\n<li><strong>New events:<\/strong> Check the event timestamps in the output of <code>kubectl describe pvc app-data -n pvc-demo<\/code> and <code>kubectl describe pod disk-check -n pvc-demo<\/code>. Focus on whether the same error recurs after the fix, rather than whether old warnings remain in the list.<\/li>\n<\/ul>\n<p>If the claim is still <code>Pending<\/code>, return to the checks for the storage class, consumer waiting or the CSI driver&#8217;s disk provisioning process. If the claim is <code>Bound<\/code> but the Pod cannot start, follow the scheduling, disk attachment and filesystem mount errors in the Pod events.<\/p>\n<p>If the Pod runs but cannot write, first inspect the mount path, read-only setting and file permissions; do not recreate the PVC. To track problems once storage is working, continue with <a href=\"https:\/\/www.dchost.com\/blog\/en\/how-to-set-up-kubernetes-monitoring\/\">setting up Kubernetes monitoring<\/a>.<\/p>\n<h3><span id=\"Clean_up_the_test_resources_while_protecting_data\">Clean up the test resources while protecting data<\/span><\/h3>\n<p>If you are removing the test resources, first save the disk identity and the reclaim policy currently set on the PV. Run the following commands in your administrator terminal only after the PVC is <code>Bound<\/code>; if the PV name is empty, do not proceed with deletion:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">PV=$(kubectl get pvc app-data -n pvc-demo -o jsonpath=&#039;{.spec.volumeName}&#039;)\nkubectl get pv &quot;$PV&quot; -o yaml &gt; .\/pvc-demo\/pv-before-cleanup.yaml\nkubectl get pv &quot;$PV&quot; -o jsonpath=&#039;{.spec.persistentVolumeReclaimPolicy}{&quot;\\n&quot;}{.spec.csi.volumeHandle}{&quot;\\n&quot;}&#039;<\/code><\/pre>\n<p>The first output line should show the expected policy, <code>Retain<\/code>, and the second should show the EBS disk ID. The saved YAML contains only the resource definition; it is not a data backup. If you need to keep any data, take an application-appropriate backup and verify that you can restore it before deleting anything. If the policy shows <code>Delete<\/code>, do not follow the deletion steps below: deleting the PVC could also delete the disk, so the disk-preserving workflow described here would not apply.<\/p>\n<p>After confirming that the policy preserves the disk and that only disposable test data remains, remove the consumer Pod first, then the claim:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">kubectl delete pod disk-check -n pvc-demo\nkubectl delete pvc app-data -n pvc-demo\nkubectl get pv &quot;$PV&quot;<\/code><\/pre>\n<p>With <code>Retain<\/code>, the PV is expected to enter the <code>Released<\/code> state; the EBS disk remains and may continue to incur charges. This step does not delete the disk, but it does not automatically bind the old data to a new claim either.<\/p>\n<p>For permanent cleanup, open <strong>EC2 &#8211; Volumes<\/strong> in the AWS console and find the saved disk ID in the region used for the example. After confirming that the disk is detached and no longer needed, delete it using <strong>Actions &#8211; Delete volume<\/strong>. This removes the data on the disk; if you need to keep any of it, do not proceed until you have a verified recovery path from backup.<\/p>\n<p>Once you have confirmed that the disk is deleted, remove its Kubernetes record with <code>kubectl delete pv \"$PV\"<\/code>. If no other claim uses the example-specific class, you can also remove it with <code>kubectl delete storageclass gp3-demo<\/code>.<\/p>\n<\/div>","protected":false},"excerpt":{"rendered":"<p>Trace a Pending storage claim through Kubernetes events, StorageClass settings, CSI permissions and scheduling constraints. Follow a hypothetical EKS example to replace a verified empty claim safely and check that its consumer Pod can write and read a file.<\/p>\n","protected":false},"author":4,"featured_media":5901,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[176],"tags":[658,659,52,660,657,612],"class_list":["post-5912","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-cloud-computing","tag-amazon-eks","tag-csi","tag-kubernetes","tag-persistent-storage","tag-storageclass","tag-troubleshooting"],"_links":{"self":[{"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/posts\/5912","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/comments?post=5912"}],"version-history":[{"count":1,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/posts\/5912\/revisions"}],"predecessor-version":[{"id":5914,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/posts\/5912\/revisions\/5914"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/media\/5901"}],"wp:attachment":[{"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/media?parent=5912"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/categories?post=5912"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/tags?post=5912"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}