Kubernetes operations
Skill Goodsmileduck/claude-registry/plugins/kubernetes-skills/skills/kubernetes-operations
Community marketplace of Claude Code plugins: DevOps skill packs (Terraform, Kubernetes, CI/CD, cloud platforms, DigitalOcean), CLAUDE.md optimization, and diagramming — gated by a best-practices lint + security-audit CI.
npx -y skills add Goodsmileduck/claude-registry --skill kubernetes-operationsAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Debugs Kubernetes pods and controllers — FailedCreate, ImagePullBackOff, init-container failures, probe flapping, missing service endpoints, GKE NEG readiness. Use when a pod is not Running, a Deployment/StatefulSet shows FailedCreate, image pulls fail, or services lack endpoints.
SKILL.md
11.5 KB, as published. Nobody here has run it
Kubernetes — pod debug decision tree
For ArgoCD-managed resources, also check the argocd-operations skill: direct mutations are reverted by selfHeal within ~3 minutes.
When to invoke
The pod's STATUS column tells you which branch to take. Always start with:
kubectl config current-context # confirm cluster/env BEFORE anything
kubectl describe pod <pod> -n <ns> | tail -40 # events at the bottom
kubectl logs <pod> -n <ns> [-c <container>] [--previous]
kubectl get events -n <ns> --sort-by=.lastTimestamp | tail -20
The Events: section at the end of describe is the single highest-signal source. Read it before anything else.
Pre-flight: is this resource Argo-managed?
Before any kubectl edit/patch/apply -f fix, check whether the resource is GitOps-owned:
kubectl get <kind> <name> -n <ns> -o jsonpath='{.metadata.labels}{"\n"}{.metadata.annotations}{"\n"}'
# managed-by indicators: argocd.argoproj.io/tracking-id, meta.helm.sh/release-name, app.kubernetes.io/managed-by
If managed: fix the source (chart/values/kustomization), not the cluster. See the argocd-operations skill.
Branch 1 — Pod never created (FailedCreate on the controller)
The pod doesn't exist yet; the ReplicaSet/StatefulSet/Job can't create it.
# Look at the controller's events, not the pod's (the pod isn't there)
kubectl describe rs <rs-name> -n <ns> | tail -30
kubectl describe statefulset <ss> -n <ns> | tail -30
| Event message contains | Cause | Fix |
|---|---|---|
forbidden: violates PodSecurity | Pod Security Admission rejecting the spec at the namespace's PSA level (restricted, baseline) | Either relax the namespace label pod-security.kubernetes.io/enforce or fix the pod spec (drop capabilities, runAsNonRoot, etc.) |
exceeded quota / forbidden: exceeded quota | ResourceQuota in the namespace | kubectl describe resourcequota -n <ns> to see what's exhausted |
admission webhook "..." denied the request | A ValidatingWebhookConfiguration rejected the pod | kubectl get validatingwebhookconfigurations, inspect the named webhook's policy; the webhook's controller logs explain why |
serviceaccount "X" not found | SA referenced in pod spec doesn't exist in this namespace | Create the SA, or fix the spec; common with Helm chart values mismatch |
persistentvolumeclaim "X" not found (StatefulSet) | The PVC template name doesn't match what was provisioned, or volumeClaimTemplate changed | StatefulSet PVCs are immutable; delete and recreate, or revert the template |
error looking up service account ... no token | ServiceAccount exists but no token Secret (IRSA / GKE WI setups) | Check Workload Identity bindings (GCP) or service-account token projection |
Branch 2 — Pod is Pending
Pod was created but never scheduled. Look at scheduler events:
kubectl describe pod <pod> -n <ns> | grep -A 10 Events
| Event reason | Cause | Diagnostic |
|---|---|---|
FailedScheduling: 0/N nodes are available: insufficient cpu/memory | No node has free capacity | kubectl describe nodes | grep -E "Name:|Allocatable|Allocated"; consider cluster autoscaler logs |
FailedScheduling: ... node(s) didn't match Pod's node affinity/selector | Affinity / nodeSelector doesn't match any node | Compare pod's affinity/nodeSelector against kubectl get nodes --show-labels |
FailedScheduling: ... node(s) had untolerated taint | Tainted nodes (e.g. GKE Autopilot system pools, spot-only pools) | Add matching tolerations to the pod, or schedule to a different pool |
FailedScheduling: ... volume node affinity conflict | The PV is in zone A, no node in zone A has capacity | Common with regional GKE + zonal PD; need a node in the PV's zone |
FailedScheduling: ... topology spread constraint(s) not satisfied | topologySpreadConstraints can't be honored | Inspect the constraint's maxSkew/whenUnsatisfiable; consider ScheduleAnyway |
Branch 3 — Image pull failures (ImagePullBackOff, ErrImagePull)
Container status waiting.reason reveals the specific failure:
kubectl get pod <pod> -n <ns> -o jsonpath='{.status.containerStatuses[*].state.waiting}{"\n"}'
| Reason | Meaning | Fix |
|---|---|---|
ErrImagePull | First pull failed (registry auth, image absent, network) | Read the next event for the underlying message |
ImagePullBackOff | Repeated ErrImagePull — kubelet is backing off | Same as ErrImagePull; the backoff just means it's been failing a while |
ImageInspectError | Image manifest fetched but inspection failed (often signature/policy verification) | Check sigstore / image policy controllers (Kyverno, Gatekeeper, Connaisseur) |
Event: manifest unknown / not found | Tag or digest doesn't exist in the registry | Verify the tag with docker manifest inspect <ref> |
Event: unauthorized / denied | imagePullSecret missing, expired, or wrong | Inspect: kubectl get sa <sa> -n <ns> -o yaml | grep imagePullSecrets; verify secret with kubectl get secret <s> -n <ns> -o jsonpath='{.data.\.dockerconfigjson}' | base64 -d |
Event: toomanyrequests (Docker Hub) | Anonymous rate limit hit (100/6h) | Use authenticated pulls or a registry mirror |
Event: dial tcp: i/o timeout | Network egress to registry blocked | Check NetworkPolicies, NAT, firewall, private cluster master-auth |
For private GCR/Artifact Registry on GKE: the node's SA needs roles/artifactregistry.reader on the AR repo, or use Workload Identity bound to a GSA that has it. Anonymous pulls from gcr.io/google-containers/* style public mirrors don't need auth.
Branch 4 — Init container failing
The pod status shows Init:CrashLoopBackOff, Init:Error, or Init:0/N. Containers run sequentially; one failure blocks the rest.
# Identify which init container by index
kubectl get pod <pod> -n <ns> -o jsonpath='{.status.initContainerStatuses[*].name}{"\n"}'
# Then read its logs
kubectl logs <pod> -n <ns> -c <init-container-name>
kubectl logs <pod> -n <ns> -c <init-container-name> --previous # if it crashed
| State | Meaning |
|---|---|
Init:0/3 Pending for long time | Init container hasn't started — image pull failure (see branch 3) or volume mount failure |
Init:CrashLoopBackOff | Init container ran and exited non-zero, repeatedly — read its logs |
Init:Error | Most recent run exited non-zero, no backoff yet — read logs with --previous after the next attempt |
Common init-container roles and their failure modes:
- Wait-for-DB / wait-for-service scripts → DNS or network failure; check
kubectl get svc -n <ns>, runnslookup <svc>from a debug pod - Volume permission fixers (
chown) → SecurityContext.fsGroup mismatch or readOnly volume - Secret materializers (Vault Agent, External Secrets job-style) → the upstream secret source isn't ready or unauthorized
Branch 5 — Main container CrashLoopBackOff
The pod started but the container exits repeatedly.
kubectl logs <pod> -n <ns> --previous # logs from the crashed instance
kubectl get pod <pod> -n <ns> -o jsonpath='{.status.containerStatuses[*].lastState.terminated}{"\n"}' | jq .
The terminated block shows exitCode, reason, and message:
exitCode / reason | Likely cause |
|---|---|
137 + reason OOMKilled | Hit container memory limit — raise resources.limits.memory or fix leak |
139 (SIGSEGV) | Segfault — application bug |
143 (SIGTERM) | Graceful termination, but exiting fast enough to look like a crash — check probe behavior (branch 6) |
1 + app-level log | Read the logs; application config or startup error |
reason Error exit 255 | Often crashloop right at PID 1 — entrypoint script bug |
Branch 6 — Probe-induced flapping (Running but restarting)
Pod restarts but logs look fine. Suspect liveness probe killing a healthy container.
kubectl describe pod <pod> -n <ns> | grep -A 5 "Liveness\|Readiness\|Startup"
kubectl get events -n <ns> --field-selector involvedObject.name=<pod> | grep -i probe
Probe rules of thumb (the trap that keeps showing up):
- Startup probe exists to protect slow-starting containers from liveness — set this for anything that takes >10s to be ready. Without it, slow startups get killed before they can answer the liveness probe.
- Liveness probe kills the container on failure. Use it only for "deadlocked process" cases. Default to no liveness probe if you're not sure — readiness alone is safer.
- Readiness probe controls service traffic only (the container stays alive, just leaves the endpoint set). Use it freely.
- A single HTTP endpoint serving as both liveness and readiness is fine, but make sure it doesn't depend on downstream services — otherwise a temporary DB blip kills your pods.
Common misconfigurations:
initialDelaySecondstoo small + no startup probe → liveness kills during startup → crashloop that looks application-sideperiodSecondssmaller than the endpoint's typical latency → false negatives- Probe scheme
HTTPSagainst a container serving plain HTTP (or vice versa)
Branch 7 — Pod Running and Ready, but service has no endpoints (GKE NEG case)
The pod is healthy in every way, but kubectl get endpoints <svc> is empty, OR the GKE container-native LB shows the backend as unhealthy.
kubectl get pod <pod> -n <ns> -o yaml | grep -A 3 "readinessGates\|conditions:"
kubectl get svc <svc> -n <ns> -o yaml | grep -A 5 "annotations:\|selector:"
kubectl get pod <pod> -n <ns> -o jsonpath='{.metadata.annotations.cloud\.google\.com/neg-status}{"\n"}'
| Check | What it should show |
|---|---|
Service selector matches pod labels | kubectl get pod <pod> -n <ns> --show-labels; compare to svc selector |
Pod has cloud.google.com/neg-status annotation | Indicates GKE picked it up for a Network Endpoint Group; absence means standalone NEG isn't configured |
Pod has readinessGates: cloud.google.com/load-balancer-neg-ready | The pod won't be Ready until the LB health check passes — required for proper rolling updates with container-native LB |
BackendConfig referenced by service annotation cloud.google.com/backend-config | kubectl get backendconfig -n <ns> — the health-check path/port must reach the pod |
The most common GKE NEG failure mode: BackendConfig health check path is / but the app serves on /healthz, so the LB marks the backend unhealthy even though Kubernetes thinks the pod is Ready. Add a readinessGate so the pod's Ready status reflects the LB's view, not just Kubernetes'.
When you've exhausted the tree
If describe pod, logs, --previous, events, and the controller's events all look clean and the pod still misbehaves:
- Check NetworkPolicies in the namespace (
kubectl get netpol -n <ns>) — egress to DNS, kube-api, or other services may be silently blocked. - Check resource pressure on the node (
kubectl describe node <node> | grep -E "Conditions:\|MemoryPressure\|DiskPressure\|PIDPressure"). - Check container runtime logs on the node (last resort; requires node access).
- Pull a debug copy of the pod:
kubectl debug <pod> -n <ns> --image=busybox --target=<container>to inspect the namespace from inside the same network/PID context.