Triage — Workloads & Storage
Most broken things on a performance-based platform exam are broken at the workload layer: a pod that will not schedule, an image that will not pull, a container that dies and dies and dies, a probe that shoots a perfectly healthy app, or a volume that nobody will hand over. These failures are worth learning as a vocabulary, because the status column narrows the cause to a handful of possibilities before you have run a single extra command. This page is the pod-state matrix and the storage-binding chain in full — symptom, likely cause, the first command to run, and the typical fix — sitting on top of the same fixed diagnostic order you should be running on everything. It is one branch of the triage hub; the other branches cover networking, RBAC and admission and GitOps, observability and platform APIs.
Imagine the light in your bedroom won’t turn on. A panicky person immediately starts unscrewing the ceiling. A calm person always does the same four checks first: is the switch on? is the bulb dead? do the other lights work? did someone flip the big switch in the hallway box? The checks take twenty seconds and they usually find the problem straight away. Kubernetes has exactly the same kind of checks — and one of them, called Events, is a little note the cluster leaves you saying literally what went wrong. Most people forget to read the note. This page is mostly about reading the note, and then about the ten or so ways a pod can be sad and the seven ways a disk can go missing.
The universal triage order — a 90-second recap
☺ Like you’re 10: Before you guess what’s wrong, always look in the same six places, in the same order. Guessing first is how you waste ten minutes.
The single biggest time sink under exam pressure is forming a hypothesis before gathering evidence. You see CrashLoopBackOff, you decide it must be the config map, you spend six minutes on the config map, and the answer was in the Events the whole time. So: do not think. Run the sequence. The sequence takes ninety seconds and it very frequently hands you the literal answer in plain English. Everything later on this page assumes you have already run it.
The six steps, in order
Steps 1–4 are the general Kubernetes ladder; step 5 is the platform-engineering addition that matters enormously on this exam, because so much of the platform is expressed as custom resources whose status.conditions block is the real error message. Only at step 6 do you get to have an opinion.
| # | Step | Command | What you are looking for |
|---|---|---|---|
| 1 | Wide list | kubectl get <kind> -o wide | Phase, restarts, age, node, IP. Restarts > 0 and a recent age tell you a lot before you read anything else. |
| 2 | Describe | kubectl describe <kind> <name> | The Events block at the bottom. Scroll straight there first. Then Conditions, then the container spec. |
| 3 | Namespace events | kubectl get events --sort-by=.lastTimestamp | Failures on objects you did not think to describe — the ReplicaSet, the PVC, the webhook, the quota. |
| 4 | Logs | kubectl logs <pod> --previous | Why the app itself died. --previous is essential in a crash loop — the current container has no logs yet. |
| 5 | Controller / CR status | kubectl get <cr> -o yaml → status.conditions | What the operator thinks. Argo CD, Flux, Crossplane and cert-manager all write the true error here. |
| 6 | Hypothesis | — | Only now. And prefer the cause that explains all the evidence, not just the loudest bit. |
The 90-second opener
Keep this block in muscle memory. It is the same six commands whether the thing in front of you is a pod, a StatefulSet, or a volume that never appeared.
# The 90-second opener. Run this on every broken thing, every time. kubectl config current-context # am I even on the right cluster? kubectl get pods -n "$NS" -o wide # 1. wide list kubectl describe pod -n "$NS" "$POD" | tail -30 # 2. Events live at the BOTTOM kubectl get events -n "$NS" --sort-by=.lastTimestamp | tail -25 # 3. everything else that failed kubectl logs -n "$NS" "$POD" --previous --tail=50 # 4. why the process exited kubectl logs -n "$NS" "$POD" -c "$INIT" --previous # 4b. init containers need -c # Cluster-wide sweep when you do not yet know where the damage is: kubectl get pods -A --field-selector=status.phase!=Running kubectl get events -A --sort-by=.lastTimestamp | grep -i -E 'warn|fail|error' | tail -30
Events are the answer key. Kubernetes tells you, in near-English, things like 0/3 nodes are available: 3 Insufficient cpu, couldn't find key DB_HOST in ConfigMap app/config, or admission webhook "validate.kyverno.svc" denied the request: require-labels. Every one of those is the whole diagnosis. Train yourself to read the Events block before you read the spec — it is the difference between a two-minute task and a twelve-minute one. The full six-step rationale and the decision flow behind it live on the triage hub; the raw commands are drilled in the Command Reference.
“The thing that finally made me fast wasn’t learning more Kubernetes — it was deleting a habit. I used to open the Deployment YAML first, because that felt like ‘real’ debugging. Now I describe the pod and read the last ten lines of Events before I look at anything else. Roughly two thirds of the time the fix is right there in a sentence and I never open the YAML at all.”
Pod-level failures
☺ Like you’re 10: Pods break in about ten recognisable ways. Learn the ten names and each one tells you where to look.
Pod states are a diagnostic vocabulary. The status column is not just a label — it narrows the cause down to a handful of possibilities before you have run a single extra command. This is the highest-value table on the page; if you internalise nothing else, internalise this.
The pod-state matrix
| Symptom | Likely causes (in priority order) | First command | Typical fix |
|---|---|---|---|
Pending / Unschedulable | Insufficient CPU/memory · nodeSelector or affinity mismatch · node taints · unbound PVC · ResourceQuota | kubectl describe pod → Events | Lower requests, fix selector, add toleration, fix PVC/quota |
ImagePullBackOff / ErrImagePull | Image name or tag typo · private registry, no imagePullSecrets · registry unreachable | kubectl describe pod → Events | Correct the reference; create and attach a pull secret |
CrashLoopBackOff | App exits non-zero · wrong command/args · missing config at runtime · dependency unreachable | kubectl logs --previous | Fix app config, command, or the dependency it needs |
OOMKilled (exit 137) | Memory limit too low · genuine leak · JVM/runtime heap larger than the limit | kubectl describe pod → Last State | Raise resources.limits.memory or fix the app |
CreateContainerConfigError | Referenced ConfigMap/Secret missing · the key inside it missing | kubectl describe pod → Events | Create the object or correct the key name |
Init:Error / Init:CrashLoopBackOff | Init container failed — usually a migration, a wait-for-dependency, or a permissions step | kubectl logs <pod> -c <init> --previous | Fix the init container or the thing it waits on |
Running but 0/1 READY | Readiness probe failing — wrong path, wrong port, app slow to start | kubectl describe pod → Readiness | Correct probe path/port, add startupProbe |
| Restarts climbing, app looks fine | Liveness probe too aggressive — probe kills a healthy-but-slow app | kubectl describe pod → Liveness | Raise initialDelaySeconds/failureThreshold |
Evicted | Node memory/disk pressure · exceeded ephemeral-storage · low-priority pod squeezed out | kubectl describe node → Conditions | Free the node, set requests, add ephemeral-storage limits |
Stuck Terminating | Finalizer never removed · long terminationGracePeriodSeconds · node gone | kubectl get pod -o yaml → finalizers | Fix the controller; last resort, clear the finalizer |
Pending and Unschedulable
A Pending pod has been accepted by the API server but the scheduler cannot place it. The scheduler is unusually chatty about why — its message enumerates every node and the reason each one was rejected, and that message alone almost always closes the case.
kubectl describe pod -n app web-5d8f | tail -20
# Events:
# Warning FailedScheduling ... 0/3 nodes are available:
# 1 node(s) had untolerated taint {node-role.kubernetes.io/control-plane: },
# 2 Insufficient cpu. preemption: 0/3 nodes are available: 3 No preemption victims found.
# Read the arithmetic yourself:
kubectl describe node worker-1 | grep -A 8 'Allocated resources'
kubectl get nodes -o custom-columns=NAME:.metadata.name,CPU:.status.allocatable.cpu,MEM:.status.allocatable.memory
kubectl get node worker-1 -o jsonpath='{.spec.taints}{"\n"}'
kubectl get node --show-labels | head # does the nodeSelector label exist at all?
kubectl get resourcequota -n app # quota blocks admission, not scheduling
kubectl get pvc -n app # an unbound PVC pins the pod in PendingWhen a ResourceQuota blocks a workload, there is often no pod at all to describe — the ReplicaSet controller was rejected before it could create one. Your Deployment simply shows 0/3 ready with nothing underneath. The error lives on the ReplicaSet: kubectl describe rs -n app, or kubectl get events -n app --sort-by=.lastTimestamp, which is exactly why step 3 of the triage order exists. Look for exceeded quota: compute-quota, requested: requests.cpu=2, used: ..., limited: .... The same “no pod to describe” shape appears when a LimitRange or an admission webhook rejects the pod template — admission failures are covered in Triage — Networking, RBAC & Admission.
ImagePullBackOff and ErrImagePull
ErrImagePull is the first failure; ImagePullBackOff is kubelet backing off after repeated failures. The Events line distinguishes the three causes precisely: not found or manifest unknown means a bad name or tag, unauthorized or authentication required means credentials, and a timeout or DNS error means the registry is unreachable from the node.
kubectl describe pod -n app api-7c9 | grep -A 5 Events
# Failed to pull image "ghcr.io/acme/api:v1.4.3": ... denied: denied
# -> unauthorized: the node has no credentials for a private registry
# Create the pull secret and attach it (either to the pod spec or the ServiceAccount):
kubectl create secret docker-registry ghcr-creds -n app \
--docker-server=ghcr.io --docker-username="$USER" --docker-password="$TOKEN"
kubectl patch serviceaccount default -n app \
-p '{"imagePullSecrets":[{"name":"ghcr-creds"}]}' # applies to all pods using this SA
# Confirm what the pod is actually asking for (typos hide well in long refs):
kubectl get pod -n app api-7c9 -o jsonpath='{.spec.containers[*].image}{"\n"}'Note that adding imagePullSecrets to a ServiceAccount only affects newly created pods, so delete the existing ones (or let the Deployment roll) afterwards. Secret handling more broadly is covered in Secrets Management.
CrashLoopBackOff
The container starts, exits, and kubelet restarts it with growing backoff (10s, 20s, 40s, up to five minutes). The crucial move is --previous: by the time you look, the current container may not have produced any output yet, and without -p you will see an empty log and conclude, wrongly, that there is nothing to find.
kubectl logs -n app worker-abc --previous --tail=60
kubectl logs -n app worker-abc --previous --all-containers=true
# The exit code narrows it further:
kubectl get pod -n app worker-abc -o jsonpath='{.status.containerStatuses[0].lastState.terminated}{"\n"}'
# exit 1 generic application error — read the logs
# exit 137 SIGKILL — usually OOMKilled, check reason field
# exit 143 SIGTERM — killed during shutdown/eviction
# exit 126 command found but not executable
# exit 127 command not found — bad `command:` or wrong image entrypoint
# Reproduce interactively without the broken entrypoint getting in the way:
kubectl debug -n app worker-abc -it --image=busybox:1.36 --target=worker -- sh
kubectl run tmp-shell --rm -it --image=busybox:1.36 --restart=Never -- sh☺ Like you’re 10: --previous means “show me the logs of the one that just died,” not “the one that’s about to die.” In a crash loop, the dead one is the only one that has anything to say.
OOMKilled and exit code 137
Exit 137 is 128 + 9, i.e. the process received SIGKILL. When the kernel cgroup OOM killer did it, the pod status carries reason: OOMKilled explicitly. There are only two real fixes: give it more memory, or make it use less.
kubectl describe pod -n app cache-0 | grep -A 4 'Last State'
# Last State: Terminated
# Reason: OOMKilled
# Exit Code: 137
kubectl top pod -n app --containers # needs metrics-server
kubectl get pod -n app cache-0 -o jsonpath='{.spec.containers[0].resources}{"\n"}'
# Fix: raise the limit (and keep requests honest so the scheduler places it correctly)
kubectl set resources deployment/cache -n app --limits=memory=1Gi --requests=memory=512MiA limit that is too low and a genuine leak look identical from the outside. Distinguish them with time: a container that OOMs within seconds of every start has a limit below its baseline footprint; one that runs happily for an hour and then dies, repeatedly, is leaking. Under exam time pressure the first is the intended answer far more often. Sizing and scheduling in depth live in Scaling & Scheduling.
CreateContainerConfigError
This one is a gift: it means the pod spec references a ConfigMap or Secret that does not exist, or a key inside it that does not exist, and kubelet says so verbatim.
kubectl describe pod -n app api-1 | grep -A 3 Events
# Error: couldn't find key DATABASE_URL in ConfigMap app/api-config
kubectl get configmap api-config -n app -o jsonpath='{.data}{"\n"}' | tr ',' '\n'
kubectl describe secret api-creds -n app # lists the keys and byte sizes, never the values
# Fix the key (or make the reference tolerant if it genuinely is optional):
kubectl create configmap api-config -n app \
--from-literal=DATABASE_URL=postgres://db:5432/api \
--dry-run=client -o yaml | kubectl apply -f -A related state, CreateContainerError, usually means the container runtime could not start the process at all — a bad command, a missing binary, or a read-only root filesystem where the app expects to write. See Configuration Management for how to keep these references from drifting in the first place.
Init container failures
Init containers run to completion, in order, before any app container starts. A stuck Init:0/2 is not an app problem — it is a dependency problem, and the pod will sit there indefinitely, which is often exactly the “app never comes up” mystery you were sent to solve.
kubectl get pod -n app api-1 # STATUS: Init:0/2 or Init:CrashLoopBackOff
kubectl get pod -n app api-1 -o jsonpath='{range .spec.initContainers[*]}{.name}{"\n"}{end}'
kubectl logs -n app api-1 -c wait-for-db --previous
kubectl describe pod -n app api-1 | grep -A 12 'Init Containers'Readiness vs liveness — two very different symptoms
Confusing these costs people marks. The symptom tells you which probe is at fault, and therefore which knob to turn.
| Probe | On failure | Symptom you see | Knobs |
|---|---|---|---|
| readinessProbe | Pod removed from Service endpoints | Running but 0/1 READY; Service has no endpoints; traffic 503s. No restarts. | path, port, periodSeconds, failureThreshold |
| livenessProbe | Container killed and restarted | RESTARTS climbing steadily; logs cut off mid-work | initialDelaySeconds, failureThreshold |
| startupProbe | Container killed before it ever started | Slow-booting app never gets past startup; restart loop at boot | failureThreshold × periodSeconds = boot budget |
kubectl describe pod -n app web-1 | grep -E 'Liveness|Readiness|Startup'
# Liveness: http-get http://:8080/healthz delay=0s timeout=1s period=10s #success=1 #failure=3
# Warning Unhealthy ... Liveness probe failed: Get "http://10.1.2.3:8080/healthz": dial tcp 10.1.2.3:8080: connect: connection refused
# Test the endpoint from inside the cluster, exactly as kubelet would:
kubectl exec -n app web-1 -- wget -qO- --timeout=2 http://127.0.0.1:8080/healthz
kubectl port-forward -n app pod/web-1 8080:8080 & # backgrounded: port-forward blocks
curl -sv localhost:8080/healthz
# Classic fix: the app takes 40s to boot, liveness starts probing at 0s.
kubectl patch deployment web -n app --type=json -p \
'[{"op":"replace","path":"/spec/template/spec/containers/0/livenessProbe/initialDelaySeconds","value":45}]'☺ Like you’re 10: Readiness means “don’t send me customers yet.” Liveness means “if I stop answering, please shoot me and start me again.” If you mix them up, a slow-but-healthy app gets shot forever.
A failing readiness probe removes the pod from its Service’s endpoint list, so the reported symptom is almost always “the Service is broken” or “the Ingress returns 503.” If you find an empty endpoint list and kubectl get pods -l <the service’s selector> does return pods, you have not found a labelling bug — you have found a readiness bug, and you should stay on this page rather than chasing selectors over in Triage — Networking, RBAC & Admission. Same symptom, completely different fix.
Evicted pods and stuck Terminating
An Evicted pod was removed by kubelet under node pressure — memory, disk, or PID exhaustion. The pod object stays around in a Failed phase as a tombstone. Stuck Terminating, by contrast, is nearly always a finalizer: some controller registered a cleanup hook and either the controller is gone or its cleanup keeps failing.
kubectl get pods -A --field-selector=status.phase=Failed
kubectl describe node worker-2 | grep -E 'MemoryPressure|DiskPressure|PIDPressure'
kubectl describe pod -n app evicted-1 | grep -i 'Message'
# Message: The node was low on resource: ephemeral-storage.
# Stuck Terminating — find out who is holding it:
kubectl get pod -n app stuck-1 -o jsonpath='{.metadata.finalizers}{"\n"}'
kubectl get pod -n app stuck-1 -o jsonpath='{.metadata.deletionTimestamp}{"\n"}'
# Prefer fixing the controller. Clearing a finalizer skips real cleanup (leaked cloud resources!):
kubectl patch pod stuck-1 -n app -p '{"metadata":{"finalizers":null}}' --type=mergeStorage failures
☺ Like you’re 10: A pod that wants a disk waits politely forever until it gets one. Your job is to find out why nobody is handing one over.
Storage failures nearly all present the same way — a pod stuck in Pending or ContainerCreating — so you must go one level down to the PVC and its events to tell them apart. That is the crucial habit: the pod is only the messenger, and describing it forever will not tell you which of the seven storage faults you are looking at. Background on the model is in Storage & State.
The storage failure matrix
| Symptom | Likely causes | First command | Fix |
|---|---|---|---|
PVC Pending, no events | volumeBindingMode: WaitForFirstConsumer — this is normal until a pod uses it | kubectl get sc | Nothing; create the consuming pod |
PVC Pending, no persistent volumes available | Static provisioning with no matching PV; wrong size or access mode | kubectl describe pvc | Create a matching PV or use a dynamic StorageClass |
PVC Pending, storageclass not found | Typo in storageClassName; no default StorageClass | kubectl get sc | Fix the name or mark a default class |
Multi-Attach error | An RWO volume is still attached to another node (old pod not fully gone) | kubectl describe pod → Events | Delete the old pod; use RWX for genuine sharing |
volume node affinity conflict | Pod scheduled to a node/zone the PV cannot attach to | kubectl get pv -o yaml | Use WaitForFirstConsumer; align zones |
App errors No space left on device | Volume full; or ephemeral storage full on the node | kubectl exec … -- df -h | Expand the PVC (if the class allows) or clean up |
StatefulSet stuck at pod -0 | Its PVC will not bind — ordered startup blocks every later replica | kubectl get pvc -n <ns> | Fix the first PVC; the rest follow |
PVC Pending — walking the binding chain
Start at the claim and read its events, then look at the class, then at the pool of volumes. The three Pending rows above are told apart entirely by the message on the PVC: a WaitForFirstConsumer notice is benign and means the class is deliberately deferring binding until a pod is scheduled; no persistent volumes available means nothing in the pool matches your size and access mode; and storageclass ... not found means the name is wrong or there is no default class marked.
kubectl get pvc -n app kubectl describe pvc data-db-0 -n app | tail -15 # Warning ProvisioningFailed ... storageclass.storage.k8s.io "fast-ssd" not found # Normal WaitForFirstConsumer ... waiting for first consumer to be created before binding kubectl get sc # which class is (default)? kubectl get pv -o custom-columns=NAME:.metadata.name,CAP:.spec.capacity.storage,MODES:.spec.accessModes,CLASS:.spec.storageClassName,STATUS:.status.phase,CLAIM:.spec.claimRef.name
Multi-Attach, full volumes and expansion
A Multi-Attach error means an RWO (ReadWriteOnce) volume is still attached to another node because the old pod has not fully gone — common after a node failure or during a rollout that overlaps. The VolumeAttachment objects tell you who is still holding it. A volume node affinity conflict is the mirror image: the pod landed on a node or zone the PV simply cannot attach to, which is exactly what WaitForFirstConsumer exists to prevent. And when the app itself reports No space left on device, the volume (or the node’s ephemeral storage) is genuinely full — expansion only works if the StorageClass allows it.
# Multi-Attach: find who still holds it
kubectl describe pod -n app db-0 | grep -i multi-attach
kubectl get volumeattachment | grep <pv-name>
# Is the disk simply full?
kubectl exec -n app db-0 -- df -h /var/lib/data
# Expand (only if the StorageClass has allowVolumeExpansion: true)
kubectl patch pvc data-db-0 -n app -p '{"spec":{"resources":{"requests":{"storage":"20Gi"}}}}'A StatefulSet blocked on its own PVC
This is the storage failure that looks the most alarming and is the least alarming, because three broken pods are almost always one broken volume.
StatefulSets are ordered: pod -1 is not created until -0 is Running and Ready. So a StatefulSet showing 0/3 is nearly always one broken thing at index zero, not three broken things. Always debug -0 and ignore the rest — and note that the PVCs of a StatefulSet deliberately survive scale-down and deletion, so a “clean redeploy” can silently reattach old, incompatible data.
Break your own cluster on purpose, then fix it using only the six-step order. On a kind or minikube cluster, deploy any small app plus a one-replica StatefulSet with a PVC, then introduce these five faults one at a time and time yourself: (1) set resources.limits.memory: 16Mi on a JVM or Node app; (2) reference a ConfigMap key that does not exist; (3) set livenessProbe.initialDelaySeconds: 0 on an app that takes 30 seconds to boot; (4) set storageClassName: fast-ssd on the PVC when no such class exists; (5) request more CPU than any node can allocate. For each one, write down the first command that revealed the cause. If that command was not describe or get events in at least three of the five cases, you are still guessing before gathering — run the whole set again. Then take it to the Lab Track and time yourself against Practice Tasks.
Dot: My deployment’s been rolling for ten minutes and the pods just say Pending. I think the cluster’s broken.
Ellie: Maybe! But did you describe the pod and read the bottom?
Dot: …it says 0/3 nodes are available: 3 Insufficient cpu. Oh. It literally just told me.
Ellie: Events almost always do. Ninety seconds of looking beats ten minutes of theorising, every time.
Gizmo: Easy fix — strip the resource requests off entirely! No requests, no “insufficient”, no problem. 😈
Timmy: And then the scheduler packs six noisy pods onto one node and evicts your database at 3am. Right the first time, Gizmo, or you’ll be right in the middle of the night.
Dot: Speaking of the database — its StatefulSet says 0/3 and I’ve been describing all three pods.
Ellie: Only describe -0. The other two were never created. One stuck PVC, three sad-looking pods.
Where to go next
Workloads and storage are the first branch of the tree, and the one that pays back fastest — the pod-state matrix alone resolves a large share of broken-on-purpose tasks. When the pods are healthy and traffic still fails, or when a forbidden or a webhook denial is in the way, cross to Triage — Networking, RBAC & Admission. When the workload never got deployed in the first place, or the dashboards are empty, or a self-service claim went nowhere, cross to Triage — Delivery, Observability & Platform APIs. The shared diagnostic order, the decision flow and the “when you are stuck” checks all live on the triage hub. For the concepts underneath these symptoms, read the Kubernetes substrate, Scaling & Scheduling and Storage & State; for the muscle memory, drill the Command Reference and work through Practice Tasks before ticking off the final checklist.
1. Name the first four steps of the universal triage order, in order. 2. A pod is Running with 0/1 READY and zero restarts — which probe is failing, and why do you know it isn’t the other one? 3. Why is --previous essential when debugging CrashLoopBackOff, and what do exit codes 137, 126 and 127 each mean? 4. A Deployment shows 0/3 ready and there is no pod to describe. What are the two likeliest causes, and which command finds them? 5. Your PVC is Pending with a WaitForFirstConsumer event and nothing else. What is wrong? 6. A StatefulSet shows 0/3. Which pod do you debug, and why? 7. What does a Multi-Attach error tell you about the volume’s access mode?
Check your answers
kubectl get -o wide→kubectl describe(read the Events at the bottom) →kubectl get events --sort-by=.lastTimestamp→kubectl logs --previous. Then the controller/CRstatus.conditions, and only then a hypothesis.- The readiness probe. A failing liveness probe restarts the container, so the restart count would be climbing; zero restarts rules it out. The pod is running fine but has been removed from the Service’s endpoints.
- In a crash loop the current container may not have logged anything yet, so without
--previousyou see an empty log and wrongly conclude there is nothing to find;--previousshows the container that just died. 137 = SIGKILL, usually OOMKilled (check thereasonfield); 126 = command found but not executable; 127 = command not found, i.e. a badcommand:or the wrong image entrypoint. (143 = SIGTERM during shutdown or eviction; 1 = a generic application error — read the logs.) - A ResourceQuota or LimitRange (or an admission webhook) rejected the pod template, so the ReplicaSet controller never created a pod. The error lives on the ReplicaSet —
kubectl describe rs -n <ns>orkubectl get events -n <ns> --sort-by=.lastTimestamp. - Nothing.
volumeBindingMode: WaitForFirstConsumerdeliberately defers binding until a pod that uses the claim is scheduled — create the consuming pod. - Pod
-0. StatefulSets start ordered:-1is not created until-0is Running and Ready, so0/3is one broken thing at index zero, not three. Also remember StatefulSet PVCs survive deletion, so a “clean redeploy” can reattach old data. - It is a ReadWriteOnce (RWO) volume that is still attached to another node, because the previous pod has not fully gone. Delete the old pod (or check
kubectl get volumeattachment); use RWX only if you genuinely need shared access.