## Intro

Kubernetes Persistent Volume (PV) networking issues can surface as slow application responses, failed mounts, or inaccessible services. Troubleshooting requires methodical observation, a clear understanding of the component interactions, and safe, reversible changes. This guide provides practical, example-driven steps to diagnose and resolve networking problems related to Persistent Volumes in Kubernetes, focusing on DNS resolution, port connectivity, and end-to-end network troubleshooting.

We will walk through a realistic scenario: an application pod that cannot access a Persistent Volume served over the network, leading to intermittent write failures. You will learn how to gather version and environment details, verify configuration, perform diagnostics, identify failure modes, and follow a recovery checklist. Every step includes concrete commands, expected outputs, and decision points. The goal is operational confidence: observe before changing, limit the blast radius, use placeholders for sensitive data, and verify outcomes.

This guide targets developers, DevOps consultants, and technical startup teams who manage stateful workloads in Kubernetes. While the focus is on PV networking, we touch on Persistent Volume Claims (PVC), StatefulSets, and Storage Classes only when they affect connectivity or recovery.

## Version and Environment Inventory

Before diving into troubleshooting, establish the exact version and topology of your Kubernetes cluster and the storage backend. This prevents mismatched assumptions and helps you compare against known issues. Run these commands and record the output alongside a timestamp.

### Cluster and Node Information

kubectl version --short
kubectl get nodes -o wide 
 Expected output is similar to:

Client Version: v1.28.2
Server Version: v1.28.2

NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
node-1 Ready control-plane 30d v1.28.2 10.0.0.11 <none> Ubuntu 22.04.3 LTS 5.15.0-86-generic containerd://1.7.11
node-2 Ready <none> 30d v1.28.2 10.0.0.12 <none> Ubuntu 22.04.3 LTS 5.15.0-86-generic containerd://1.7.11 
 Note the Kubernetes version (e.g., 1.28) and container runtime (containerd). Network plugin details matter; for example, with Calico:

kubectl get pods -n kube-system | grep -E 'calico|kube-proxy' 
 Expected output:

calico-node-abcde 1/1 Running 0 30d
calico-kube-controllers-12345 1/1 Running 0 30d
kube-proxy-xyz1 1/1 Running 0 30d 

### Storage Backend Information

 Identify the StorageClass and provisioner used by your PV. Suppose you use NFS for a shared storage network. List storage classes:

kubectl get storageclass 
 Expected output:

NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
nfs-client (default) nfs.csi.k8s.io Delete Immediate true 60d 
 Then inspect the PV and PVC details:

kubectl get pv
kubectl get pvc -n application-namespace 
 Example output:

NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-38b7f2a3-4c5d-4e6f-8a1b-9c0d1e2f3a4b 20Gi RWO Delete Bound app/data-pvc nfs-client 15d

NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
data-pvc Bound pvc-38b7f2a3-4c5d-4e6f-8a1b-9c0d1e2f3a4b 20Gi RWO nfs-client 15d 
 Record the NFS server endpoint if using NFS:

kubectl describe pv pvc-38b7f2a3-4c5d-4e6f-8a1b-9c0d1e2f3a4b | grep -A5 'Source:' 
 Expected output snippet:

Source:
 Type: NFS (an NFS mount that lasts the lifetime of a pod)
 Server: 192.168.1.100
 Path: /exports/data
 ReadOnly: false 
 This tells you the network endpoint your pods must reach. If the NFS server IP is wrong or unreachable, all PV networking fails.

### Pod and Workload Overview

Check the pods using the PVC:

kubectl get pods -n application-namespace -o wide 
 Example:

NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
app-db-0 1/1 Running 4 15d 10.244.2.55 node-2 <none> <none>
app-web-7b9c8d5f6-abcde 1/1 Running 0 15d 10.244.1.89 node-1 <none> <none> 
 Notice the restarts on app-db-0 ; this may indicate intermittent storage connectivity.

### Observing Without Intervening

At this stage, only run read-only commands. Avoid edits or deletions until you understand the current state. Capture the output of the following for later comparison:

kubectl describe pod app-db-0 -n application-namespace
kubectl logs app-db-0 -n application-namespace --tail=50 
 If the pod is crash-looping, use --previous :

kubectl logs app-db-0 -n application-namespace --previous 
 This inventory gives you a baseline. Now, move to safe configuration checks.

<style>
.eno-quiz-widget{margin:2rem 0;padding:1.5rem;border-radius:12px;background:var(--eno-surface-lowest,#f7f7f8);border:1px solid var(--eno-border-soft,#e2e2e6);font-family:var(--eno-font-body,Inter,sans-serif)}
.eno-quiz-widget .eno-quiz-kicker{font-family:var(--eno-font-label,"JetBrains Mono",monospace);font-size:.75rem;letter-spacing:.05em;text-transform:uppercase;color:var(--eno-text-muted,#5f5f68);margin:0 0 .5rem}
.eno-quiz-widget .eno-quiz-question{font-family:var(--eno-font-heading,"Hanken Grotesk",sans-serif);font-size:1.0625rem;font-weight:600;margin:0 0 1rem;color:var(--eno-text-strong,#1a1a1f)}
.eno-quiz-widget .eno-quiz-options{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:.5rem}
.eno-quiz-widget .eno-quiz-option{display:block;width:100%;min-height:44px;text-align:left;padding:.625rem .875rem;border-radius:8px;border:1.5px solid var(--eno-border-soft,#e2e2e6);background:#fff;font-size:.9375rem;cursor:pointer;transition:border-color var(--eno-motion-base,180ms ease-out),background var(--eno-motion-base,180ms ease-out)}
.eno-quiz-widget .eno-quiz-option:hover{border-color:var(--eno-primary,#0059bb)}
.eno-quiz-widget .eno-quiz-option:focus-visible{outline:none;box-shadow:0 0 0 3px rgb(0 89 187 / 0.15)}
.eno-quiz-widget .eno-quiz-option[aria-pressed="true"]{border-color:var(--eno-primary,#0059bb);background:rgb(0 89 187 / 0.06)}
.eno-quiz-widget .eno-quiz-option[data-correct="true"].eno-quiz-revealed{border-color:var(--eno-success,#17803a);background:rgb(23 128 58 / 0.08)}
.eno-quiz-widget .eno-quiz-option[data-correct="false"].eno-quiz-revealed.eno-quiz-was-selected{border-color:var(--eno-error,#ba1a1a);background:var(--eno-error-soft,#ffdad6)}
.eno-quiz-widget .eno-quiz-option-icon{display:inline-block;width:1.1em;margin-right:.4em;font-weight:700}
.eno-quiz-widget .eno-quiz-submit{margin-top:1rem;min-height:44px;padding:.5rem 1.25rem;border-radius:8px;border:none;background:var(--eno-primary,#0059bb);color:#fff;font-weight:600;font-size:.9375rem;cursor:pointer;transition:opacity var(--eno-motion-base,180ms ease-out)}
.eno-quiz-widget .eno-quiz-submit:disabled{opacity:.5;cursor:not-allowed}
.eno-quiz-widget .eno-quiz-submit:focus-visible{outline:none;box-shadow:0 0 0 3px rgb(0 89 187 / 0.15)}
.eno-quiz-widget .eno-quiz-explanation{margin-top:1rem;padding:.875rem 1rem;border-radius:8px;font-size:.9375rem;line-height:1.5;display:none}
.eno-quiz-widget .eno-quiz-explanation.eno-quiz-visible{display:block}
.eno-quiz-widget .eno-quiz-explanation.eno-quiz-correct{background:rgb(23 128 58 / 0.08);color:var(--eno-success,#17803a)}
.eno-quiz-widget .eno-quiz-explanation.eno-quiz-incorrect{background:var(--eno-error-soft,#ffdad6);color:var(--eno-error,#ba1a1a)}
@media (prefers-reduced-motion: reduce){.eno-quiz-widget *{transition:none!important}}
</style><div class="eno-quiz-widget" role="group" aria-label="Quick check question">
Quick check 1 of 2

What command output shows the NFS server endpoint for a PersistentVolume? (Assume the PV is using NFS type)

- kubectl get pv
- kubectl describe pv | grep -A5 'Source:'
- kubectl get storageclass
- kubectl get pvc

 Submit 
 The 'kubectl describe pv' command with a grep for 'Source:' shows the NFS server and path details for the PersistentVolume, as indicated in the expected output snippet in the reference. 

## Safe Configuration Path

 The safe configuration path ensures you make minimal, well-understood changes. For PV networking, focus on the StorageClass, PVC, and pod mount configurations.

### Inspect the StorageClass

For NFS, you might have a custom StorageClass with mount options that affect network behavior. View the YAML:

kubectl get storageclass nfs-client -o yaml 
 Key fields:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
 name: nfs-client
provisioner: nfs.csi.k8s.io
parameters:
 server: 192.168.1.100
 share: /exports
mountOptions:
 - nfsvers=4.1
 - hard
 - timeo=600
 - retrans=2
reclaimPolicy: Delete
volumeBindingMode: Immediate 
 Check that nfsvers matches the NFS server capability. Mismatched version (e.g., server supports NFSv3 only, but client requests 4.1) causes mount failures or poor performance. Also, hard mount option means the pod will block indefinitely on network partitions; soft may be preferable for faster failure detection, but it can cause data corruption. Decide based on workload tolerance.

### PVC and Pod Mount Verification

Verify the PVC has correct access modes and is bound:

kubectl get pvc data-pvc -n application-namespace -o yaml 
 Look for accessModes :

accessModes:
- ReadWriteOnce 
 If your application needs shared storage (e.g., multiple pods reading/writing), you need ReadWriteMany . Using ReadWriteOnce with multiple replicas can cause only one node to mount successfully, leading to networking errors for others.

Inspect the pod's volume mount and read-only flags:

kubectl get pod app-db-0 -n application-namespace -o yaml | grep -A10 'volumeMounts:' 
 Expected snippet:

volumeMounts:
- mountPath: /var/lib/data
 name: data
 readOnly: false 
 Ensure mountPath matches the application's expected path and that it is not read-only when writes are required.

### Smallest Justified Change

If you need to alter a configuration (e.g., add soft mount option or change NFS version), update the StorageClass with a patch, but first test in a non-production environment.

Example patch to change nfsvers from 4.1 to 4.0:

kubectl patch storageclass nfs-client --type='json' -p='[{"op": "replace", "path": "/mountOptions/0", "value": "nfsvers=4.0"}]' 
 Verify the change:

kubectl get storageclass nfs-client -o yaml | grep -A5 'mountOptions' 
 New pods will use the updated option. Existing pods must be restarted to pick up changes; consider a rolling restart of the StatefulSet.

### Local Testing with port-forward

Before involving external network services, test connectivity locally. For a pod that serves a network file protocol or API, use kubectl port-forward :

kubectl port-forward pod/app-db-0 -n application-namespace 2049:2049 
 Then from another terminal, test the NFS mount using local tools:

sudo mount -t nfs -o nfsvers=4.1 localhost:/exports/data /mnt/test 
 If this fails, the issue is inside the cluster, not with external networking.

### Verifying Without Load Balancers

Avoid creating a LoadBalancer service just to test. Instead, use a temporary NodePort service or port-forward. This limits exposure and cost.

## Verification and Diagnostics

Now that the configuration is understood, perform active diagnostics to isolate the network issue.

### DNS Resolution Checks

Many PV networking issues stem from DNS failures, especially when the NFS server is referenced by hostname. First, check the CoreDNS pods:

kubectl get pods -n kube-system -l k8s-app=kube-dns 
 Expected:

NAME READY STATUS RESTARTS AGE
coredns-787d4945fb-abcde 1/1 Running 0 30d
coredns-787d4945fb-fghij 1/1 Running 0 30d 
 Test DNS resolution from inside a pod. Exec into the app pod (or a debug pod) and run nslookup :

kubectl exec -it app-db-0 -n application-namespace -- nslookup nfs-server.example.com 
 Expected output:

Server: 10.96.0.10
Address: 10.96.0.10#53

Name: nfs-server.example.com
Address: 192.168.1.100 
 If you get server can't find nfs-server.example.com: NXDOMAIN , the hostname is not in DNS. Check if you need to add an entry to /etc/hosts via hostAliases in the pod spec or fix external DNS.

### Port Connectivity Tests

NFS typically uses TCP/UDP port 2049. Test connectivity from the pod to the NFS server:

kubectl exec -it app-db-0 -n application-namespace -- nc -zvw3 192.168.1.100 2049 
 If nc is not installed, use bash with /dev/tcp :

kubectl exec -it app-db-0 -n application-namespace -- bash -c 'echo > /dev/tcp/192.168.1.100/2049 && echo open || echo closed' 
 Expected success output: open . If closed, check NetworkPolicies, firewall rules, or the NFS server service status.

Check for NetworkPolicies that might block egress to the NFS server:

kubectl get networkpolicies -n application-namespace 
 If a policy exists, inspect it:

kubectl describe networkpolicy allow-nfs -n application-namespace 
 Ensure the policy allows egress to 192.168.1.100/32 on port 2049.

### Endpoint and Service Checks

If the storage is exposed via a Kubernetes Service (e.g., an NFS server running as a pod), verify endpoints:

kubectl get endpoints nfs-service -n storage-namespace 
 Expected:

NAME ENDPOINTS AGE
nfs-service 10.244.2.10:2049 20d 
 If endpoints are empty, check the service selector matches the pod labels.

### Logs and Events

Check pod events for mount errors:

kubectl describe pod app-db-0 -n application-namespace | tail -30 
 Look for messages like:

Warning FailedMount 2m (x12 over 30m) kubelet MountVolume.SetUp failed for volume "pvc-38b7f2a3..." : mount failed: exit status 32
Mounting command: mount
Mounting arguments: -t nfs 192.168.1.100:/exports/data /var/lib/kubelet/pods/.../volumes/kubernetes.io~nfs/pvc-38b7f2a3...
Output: mount.nfs: access denied by server while mounting 192.168.1.100:/exports/data 
 This indicates an NFS export permission issue or incorrect client IP in the export configuration.

Check kubelet logs on the node where the pod runs (node-2):

journalctl -u kubelet -n 50 --no-pager | grep -i 'nfs\|mount' 
 This may reveal network timeouts or RPC errors.

### Network Plugin Debugging

If using Calico, check calico-node logs:

kubectl logs -n kube-system calico-node-abcde --tail=50 | grep -i 'nfs\|drop\|deny' 
 Look for dropped packets due to policy. You may also use calicoctl to check network policy evaluation.

### Temporary Debug Pod

Run a temporary pod with network tools to isolate whether the issue is pod-specific or cluster-wide:

apiVersion: v1
kind: Pod
metadata:
 name: debug-network
 namespace: application-namespace
spec:
 containers:
 - name: debug
 image: nicolaka/netshoot
 command: ["sleep", "3600"]
 restartPolicy: Never 
 Create it:

kubectl apply -f debug-pod.yaml 
 Then test NFS mount manually inside debug pod:

kubectl exec -it debug-network -n application-namespace -- mount -t nfs 192.168.1.100:/exports/data /mnt 
 If this works but the app pod fails, the issue may be with the app pod's security context or volume mount configuration.

<style>
.eno-quiz-widget{margin:2rem 0;padding:1.5rem;border-radius:12px;background:var(--eno-surface-lowest,#f7f7f8);border:1px solid var(--eno-border-soft,#e2e2e6);font-family:var(--eno-font-body,Inter,sans-serif)}
.eno-quiz-widget .eno-quiz-kicker{font-family:var(--eno-font-label,"JetBrains Mono",monospace);font-size:.75rem;letter-spacing:.05em;text-transform:uppercase;color:var(--eno-text-muted,#5f5f68);margin:0 0 .5rem}
.eno-quiz-widget .eno-quiz-question{font-family:var(--eno-font-heading,"Hanken Grotesk",sans-serif);font-size:1.0625rem;font-weight:600;margin:0 0 1rem;color:var(--eno-text-strong,#1a1a1f)}
.eno-quiz-widget .eno-quiz-options{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:.5rem}
.eno-quiz-widget .eno-quiz-option{display:block;width:100%;min-height:44px;text-align:left;padding:.625rem .875rem;border-radius:8px;border:1.5px solid var(--eno-border-soft,#e2e2e6);background:#fff;font-size:.9375rem;cursor:pointer;transition:border-color var(--eno-motion-base,180ms ease-out),background var(--eno-motion-base,180ms ease-out)}
.eno-quiz-widget .eno-quiz-option:hover{border-color:var(--eno-primary,#0059bb)}
.eno-quiz-widget .eno-quiz-option:focus-visible{outline:none;box-shadow:0 0 0 3px rgb(0 89 187 / 0.15)}
.eno-quiz-widget .eno-quiz-option[aria-pressed="true"]{border-color:var(--eno-primary,#0059bb);background:rgb(0 89 187 / 0.06)}
.eno-quiz-widget .eno-quiz-option[data-correct="true"].eno-quiz-revealed{border-color:var(--eno-success,#17803a);background:rgb(23 128 58 / 0.08)}
.eno-quiz-widget .eno-quiz-option[data-correct="false"].eno-quiz-revealed.eno-quiz-was-selected{border-color:var(--eno-error,#ba1a1a);background:var(--eno-error-soft,#ffdad6)}
.eno-quiz-widget .eno-quiz-option-icon{display:inline-block;width:1.1em;margin-right:.4em;font-weight:700}
.eno-quiz-widget .eno-quiz-submit{margin-top:1rem;min-height:44px;padding:.5rem 1.25rem;border-radius:8px;border:none;background:var(--eno-primary,#0059bb);color:#fff;font-weight:600;font-size:.9375rem;cursor:pointer;transition:opacity var(--eno-motion-base,180ms ease-out)}
.eno-quiz-widget .eno-quiz-submit:disabled{opacity:.5;cursor:not-allowed}
.eno-quiz-widget .eno-quiz-submit:focus-visible{outline:none;box-shadow:0 0 0 3px rgb(0 89 187 / 0.15)}
.eno-quiz-widget .eno-quiz-explanation{margin-top:1rem;padding:.875rem 1rem;border-radius:8px;font-size:.9375rem;line-height:1.5;display:none}
.eno-quiz-widget .eno-quiz-explanation.eno-quiz-visible{display:block}
.eno-quiz-widget .eno-quiz-explanation.eno-quiz-correct{background:rgb(23 128 58 / 0.08);color:var(--eno-success,#17803a)}
.eno-quiz-widget .eno-quiz-explanation.eno-quiz-incorrect{background:var(--eno-error-soft,#ffdad6);color:var(--eno-error,#ba1a1a)}
@media (prefers-reduced-motion: reduce){.eno-quiz-widget *{transition:none!important}}
</style><div class="eno-quiz-widget" role="group" aria-label="Quick check question">
Quick check 2 of 2

According to the article, what is a recommended practice before making changes during troubleshooting?

- Immediately restart all pods
- Make multiple changes at once to speed up the process
- Observe and capture the current state before intervening
- Delete and recreate the PersistentVolumeClaim

<button type="button" class="eno-quiz-submit" disabled>Submit</button>
<div class="eno-quiz-explanation">The article emphasizes &#39;observe before changing&#39; and capturing the current state with read-only commands before making any changes, as stated in the &#39;Observing Without Intervening&#39; section.</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function () {
  document.querySelectorAll('.eno-quiz-widget:not([data-eno-quiz-bound])').forEach(function (widget) {
    widget.setAttribute('data-eno-quiz-bound', '1');
    var options = Array.prototype.slice.call(widget.querySelectorAll('.eno-quiz-option'));
    var submitBtn = widget.querySelector('.eno-quiz-submit');
    var explanation = widget.querySelector('.eno-quiz-explanation');
    var selected = null;
    options.forEach(function (opt) {
      opt.addEventListener('click', function () {
        if (widget.hasAttribute('data-eno-quiz-answered')) return;
        options.forEach(function (o) { o.setAttribute('aria-pressed', 'false'); });
        opt.setAttribute('aria-pressed', 'true');
        selected = opt;
        submitBtn.disabled = false;
      });
    });
    submitBtn.addEventListener('click', function () {
      if (!selected || widget.hasAttribute('data-eno-quiz-answered')) return;
      widget.setAttribute('data-eno-quiz-answered', '1');
      submitBtn.disabled = true;
      var correct = selected.getAttribute('data-correct') === 'true';
      options.forEach(function (o) {
        o.classList.add('eno-quiz-revealed');
        if (o === selected) o.classList.add('eno-quiz-was-selected');
        var icon = o.querySelector('.eno-quiz-option-icon');
        if (o.getAttribute('data-correct') === 'true') icon.textContent = '\u2713';
        else if (o === selected) icon.textContent = '\u2717';
      });
      explanation.classList.add('eno-quiz-visible', correct ? 'eno-quiz-correct' : 'eno-quiz-incorrect');
    });
  });
});
</script>

## Failure Modes and Recovery

Understanding common failure modes accelerates recovery. Here are specific scenarios and how to address them.

### Failure Mode 1: NFS Server Unreachable

Symptoms: Pod logs show I/O error , mount hangs, or FailedMount events with connection timed out .

Diagnosis:

kubectl exec -it app-db-0 -n application-namespace -- ping -c 3 192.168.1.100 
 If ping fails, check network routing: are the cluster nodes able to reach the NFS server subnet? If the NFS server is on a different network, ensure routing tables and firewall rules allow traffic.

Recovery: Fix network connectivity (e.g., add route or firewall rule), then restart pod:

kubectl delete pod app-db-0 -n application-namespace 
 Wait for StatefulSet to recreate it.

### Failure Mode 2: DNS Resolution Failure for NFS Server Hostname

Symptoms: Mount errors with Unable to resolve host or server not found . If the NFS server IP changed but DNS not updated.

Diagnosis: Confirm with nslookup (see earlier).

Recovery: Update DNS record or, as an immediate workaround, use IP address in the PV spec. To change NFS server in PV:

kubectl patch pv pvc-38b7f2a3-4c5d-4e6f-8a1b-9c0d1e2f3a4b --type='json' -p='[{"op": "replace", "path": "/spec/nfs/server", "value": "192.168.1.101"}]' 
 Then delete the pod to remount:

kubectl delete pod app-db-0 -n application-namespace 

### Failure Mode 3: Export Permissions Denied

 Symptoms: access denied by server in mount output (see earlier).

Diagnosis: Compare the NFS export configuration on the server. The export should allow the pod node IPs or the cluster network CIDR (e.g., 10.244.0.0/16 for Calico).

Example export on NFS server /etc/exports :

/exports/data 192.168.1.0/24(rw,sync,no_subtree_check,no_root_squash) 
 If your cluster pods use a different CIDR (e.g., 10.244.0.0/16), the NFS server might reject because client IP is from that range. Update export to include that CIDR:

/exports/data 10.244.0.0/16(rw,sync,no_subtree_check,no_root_squash) 
 Then run exportfs -ra on the NFS server. No pod restart needed if mount succeeds on next attempt.

### Failure Mode 4: Network Policy Blocking

Symptoms: Pod can reach NFS server from other pods but not from its own namespace, or connectivity fails after applying a NetworkPolicy.

Diagnosis: Review NetworkPolicies as described. Use kubectl describe networkpolicy to see rules. If a deny-all policy is present, ensure allow rules for NFS.

Recovery: Apply a new NetworkPolicy allowing egress to NFS server:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
 name: allow-nfs-egress
 namespace: application-namespace
spec:
 podSelector:
 matchLabels:
 app: db
 policyTypes:
 - Egress
 egress:
 - to:
 - ipBlock:
 cidr: 192.168.1.100/32
 ports:
 - protocol: TCP
 port: 2049
 - protocol: UDP
 port: 2049 
 Apply and test connectivity.

### Failure Mode 5: Mount Option Mismatch

Symptoms: Mount succeeds but performance is poor, or frequent timeouts. nfsvers mismatch causes fallback to older version or failure.

Diagnosis: Check StorageClass mount options and NFS server supported versions. Use rpcinfo -p 192.168.1.100 | grep nfs (from a node with access) to list versions.

Recovery: Update StorageClass with correct nfsvers and restart pods.

## Operations Checklist

Use this checklist to ensure you cover all bases during PV networking troubleshooting.

<div class="my-stack-md overflow-x-auto">
<table class="min-w-[42rem] border-collapse text-left">
<thead><tr><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Step</th><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Action</th><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Command or Check</th><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Expected Result</th></tr></thead>
<tbody><tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">1</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Record cluster version and nodes</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant"><code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">kubectl version --short</code>, <code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">kubectl get nodes -o wide</code></td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">All nodes Ready, version consistent</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">2</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Identify storage provisioner and backend</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant"><code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">kubectl get storageclass</code>, <code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">kubectl get pv</code></td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Provisioner matches intended (e.g., nfs.csi.k8s.io), PV Bound</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">3</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Check NFS server reachability from node</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant"><code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">ping -c 3 192.168.1.100</code> from node</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">0% packet loss</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">4</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Check NFS port open</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant"><code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">telnet 192.168.1.100 2049</code> or <code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">nc -zv</code></td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Connection successful</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">5</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Verify DNS resolution inside pod</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant"><code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">kubectl exec &lt;pod&gt; -- nslookup nfs-server.example.com</code></td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Returns correct IP</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">6</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Test NFS mount manually in debug pod</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant"><code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">mount -t nfs 192.168.1.100:/exports/data /mnt</code> inside debug pod</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Mount succeeds without errors</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">7</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Review pod events</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant"><code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">kubectl describe pod &lt;pod-name&gt;</code></td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">No <code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">FailedMount</code> warnings</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">8</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Check kubelet logs</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant"><code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">journalctl -u kubelet -n 50 --no-pager</code> on node</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">No mount errors</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">9</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Inspect NetworkPolicies</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant"><code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">kubectl get networkpolicies -n &lt;ns&gt;</code></td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Rules allow NFS traffic if needed</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">10</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Confirm PV/PVC status</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant"><code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">kubectl get pv,pvc -n &lt;ns&gt;</code></td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Bound and available</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">11</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Validate mount options</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant"><code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">kubectl get storageclass &lt;name&gt; -o yaml</code></td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Options match server capabilities</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">12</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Perform a controlled test change</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Patch StorageClass or PV, restart pod, verify mount</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">No adverse effects</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">13</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Document findings and recovery steps</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Update runbook</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Clear notes</td></tr></tbody>
</table>
</div>

### Runbook Example Entry

After resolving an issue (e.g., NFS export permission denied), add an entry to your runbook:

## Incident: PVC mount failed with "access denied by server"
Date: 2025-04-02
Cluster: prod-shared
Namespace: application-namespace
PV: pvc-38b7f2a3-4c5d-4e6f-8a1b-9c0d1e2f3a4b
Symptoms: app-db-0 pod restarts, event shows mount.nfs access denied.
Root cause: NFS server export only allowed 192.168.1.0/24, but pod network CIDR is 10.244.0.0/16.
Resolution: Updated /etc/exports to include 10.244.0.0/16, ran exportfs -ra, restarted pod.
Verification: pod restarted successfully, application writes resumed. 

## Conclusion

 Troubleshooting Kubernetes Persistent Volume networking requires a structured approach: inventory the environment, verify configuration, run targeted diagnostics, understand failure modes, and follow a recovery checklist. By using the concrete commands and examples in this guide, you can quickly identify whether the issue lies in DNS, port connectivity, mount options, network policies, or backend permissions.

Always observe before intervening. Capture current state, make one small change at a time, and verify the result. Keep sensitive data out of logs and commands by using placeholders and redacting when sharing. With these practices, you will reduce downtime and improve the reliability of your stateful applications.

As a next step, pick one low-risk verification from the Operations Checklist, run it in your environment, and document the outcome. Over time, you will build a robust troubleshooting playbook for Kubernetes Persistent Volume networking.