Kubernetes Scaling, Probes and Troubleshooting with kubectl
DevOpsLesson 4 of 46 min read
What you'll learn
- Scale a Deployment manually and with a HorizontalPodAutoscaler
- Choose between startup, readiness and liveness probes and set requests and limits
- Debug CrashLoopBackOff by reading status, events and previous logs
- Debug ImagePullBackOff by separating naming, registry and credential problems
On this page
Pager goes off: the ticket-gate API at a ferry terminal shows CrashLoopBackOff and the morning rush is twenty minutes away. The worst response is to start changing things. The best one is a fixed sequence of questions, each answered by one kubectl command. This lesson covers the settings that prevent most of these incidents (scaling, resource requests and probes) and then walks through the sequence for the two failures you will meet most often.
Scaling: manual first, then automatic
Manual scaling is a single field: kubectl scale deployment/ticket-gate --replicas=5. Automatic scaling uses a HorizontalPodAutoscaler (HPA), which watches a metric and adjusts replicas between a minimum and maximum. CPU utilisation is the usual starting metric. Two prerequisites trip people up. The cluster needs a metrics source such as metrics-server, and the Pods must declare CPU requests, because utilisation is calculated as a percentage of the request.
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: ticket-gate
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: ticket-gate
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70Read it as: keep average CPU around 70 percent of each Pod's request, never below two Pods or above ten. If you also set replicas in the Deployment manifest and re-apply it, you will fight the autoscaler, so omit that field once an HPA owns the count. Autoscaling Pods does not add nodes; node-level scaling is a separate component (a cluster autoscaler) and depends on your platform.
Requests, limits and probes
A request is what the scheduler reserves for a container. A limit is the ceiling the kernel enforces. Exceed a memory limit and the container is killed with OOMKilled. Exceed a CPU limit and it is throttled instead. Three probes tell the kubelet about health, and mixing them up is a classic source of outages.
| Probe | Question it answers | On failure |
|---|---|---|
| startupProbe | Has the app finished booting? | Keeps other probes paused until it passes; kills the container if it never does |
| readinessProbe | Can it take traffic right now? | Removes the Pod from Service endpoints; does not restart it |
| livenessProbe | Is it stuck beyond recovery? | Restarts the container |
containers:
- name: gate
image: ghcr.io/example-org/ticket-gate:3.1.2
ports:
- containerPort: 8080
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
memory: 512Mi
startupProbe:
httpGet:
path: /health
port: 8080
failureThreshold: 30
periodSeconds: 2
readinessProbe:
httpGet:
path: /ready
port: 8080
periodSeconds: 5
livenessProbe:
httpGet:
path: /health
port: 8080
periodSeconds: 10
failureThreshold: 3The debugging walkthrough: read, then ask why
Start the same way every time. kubectl get pods shows the status, and kubectl describe pod <name> shows the evidence. Everything below is about what to look for in describe and why.
Case 1: CrashLoopBackOff
This status means the container starts, exits, and Kubernetes keeps restarting it with growing delays. The image pulled fine, so the problem is what happens after start. Work through these questions in order.
- What exit code did it leave with? In
describe, under Containers, readLast State: Terminated,ReasonandExit Code. Exit code 1 or 2 usually means the application itself failed. 137 means it was killed with SIGKILL, and a reason ofOOMKilledconfirms it hit its memory limit. 126 or 127 means the command could not be executed or was not found, typically a wrongcommandor entrypoint. - What did it say before dying?
kubectl logs <pod> --previousprints the last terminated container's logs. Plainlogsoften shows the new, empty attempt. Missing environment variables, an unreachable database host or a bad config file path usually appear here. - Is the liveness probe killing a healthy app? Look for
Liveness probe failedin Events. If the app needs 40 seconds to boot and the probe starts at 10, the kubelet restarts it forever. Add a startup probe or lengthen the delay. - Do the config objects exist and have the right keys? A wrong key name in a
configMapKeyReffails differently (CreateContainerConfigError), but a present-but-wrong value fails inside the app and shows in the logs. - Reproduce it interactively. Run the image with a sleep command in a debug Pod, or use
kubectl debugto attach a debug container to the failing Pod, then run the real start command by hand and watch it fail.
Containers:
gate:
State: Waiting
Reason: CrashLoopBackOff
Last State: Terminated
Reason: OOMKilled
Exit Code: 137
Restart Count: 6
Limits:
memory: 512MiThat block says the process was killed for exceeding 512Mi, so reading logs will not help much. The fix is raising the limit, or finding the memory leak or oversized batch behind it.
Case 2: ImagePullBackOff (and its sibling ErrImagePull)
Here the container never started, because the node could not fetch the image. The Events section at the bottom of describe carries the reason, and three causes cover most cases.
- Name or tag is wrong. An event like
manifest unknownornot foundmeans the registry has no such tag. Compare the Pod's image string character by character against what was pushed (1.4.1xversus1.4.1, a missing registry hostname, upper case in a repository name). - Credentials are missing. An event like
pull access deniedorunauthorizedfor a private registry means the node had no valid login. Create a docker-registry Secret and reference it underimagePullSecretsin the Pod spec or on the ServiceAccount, in the same namespace as the Pod. - The node cannot reach the registry. Events mentioning timeouts, TLS errors or DNS lookup failures point to network, proxy or firewall issues rather than the image. Try pulling the same image from the node, or from a debug Pod, to confirm.
kubectl get pods -l app=ticket-gate
kubectl describe pod <pod-name>
kubectl logs <pod-name> --previous
kubectl get events --sort-by=.metadata.creationTimestamp
kubectl rollout undo deployment/ticket-gate
kubectl get endpoints ticket-gateThose six commands are the sequence in miniature: see the status, read the evidence, read the previous logs, scan recent cluster events, roll back if customers are affected while you investigate, and finally confirm the Service has endpoints again. Rolling back first and investigating second is a perfectly professional order when the incident is live.
| Status | Meaning | First place to look |
|---|---|---|
| Pending | No node can run it yet | describe events: insufficient CPU or memory, unbound PVC, taints |
| ImagePullBackOff | Cannot fetch the image | Events: tag, registry credentials, network |
| CrashLoopBackOff | Starts, then exits repeatedly | Exit code, logs --previous, probes |
| CreateContainerConfigError | A referenced ConfigMap or Secret or key is missing | Events naming the missing object |
| Running but not Ready | Readiness probe failing | Probe path and port, app logs |
Common mistakes and the symptoms they cause
- No CPU requests with an HPA. Symptom: the HPA shows
unknownfor its target and never scales. Fix: set requests and confirm metrics-server is installed. - Memory limit far below real use. Symptom: restarts with
OOMKilledand exit code 137 under load only. - Liveness probe pointing at a slow dependency. Symptom: all Pods restart together when the database is slow.
- Probe on the wrong port or path. Symptom:
Readiness probe failed: HTTP probe failed with statuscode: 404and a Pod that is Running but never Ready. - Fixing only the live Pod. Symptom: a hand-edit works until the next rollout. Fix: change the manifest in version control and apply it.
How this shows up in real projects and interviews
"A Pod is in CrashLoopBackOff, what do you do?" is perhaps the most common practical Kubernetes interview question, and the interviewer wants a method, not a guess: status, describe, exit code, previous logs, probes, config. Say aloud what each command tells you. On call, the discipline is the same, and teams that pair it with automated deployment checks (see CI/CD) catch many of these failures before they reach production.
Try it yourself
Deliberately break a test Deployment two ways: first set the image to a tag that does not exist, then fix that and set the container command to a binary that does not exist. For each failure, write down the Pod status, the exact event or exit code that explains it, and the one kubectl command that revealed it.
Show a hint
Use kubectl get pods, then kubectl describe pod <name> and read Events and Last State. A missing command usually exits with code 127 or a StartError in the container state.
Key takeaways
- Set CPU and memory requests so the scheduler and the HPA have something to calculate against.
- Readiness controls traffic, liveness controls restarts, and startup protects slow boots; keep liveness checks cheap and self-contained.
- For CrashLoopBackOff read the exit code and the previous logs; 137 or OOMKilled points to the memory limit.
- For ImagePullBackOff read the event message to separate a wrong tag, missing credentials and network problems.
- Roll back first during a live incident, then investigate from evidence rather than guesses.
Test yourself
A trick from this lesson's ride: The error text names the culprit: manifest unknown means that tag is not in the registry. Four more tricks like it wait along the route, each followed by a question.
Frequently asked questions
How do I fix CrashLoopBackOff in Kubernetes?
Run kubectl describe pod to read the last state, reason and exit code, then kubectl logs --previous to see what the crashed container printed. Common causes are application errors, missing configuration, memory limits (exit 137, OOMKilled) and liveness probes that kill a slow-starting app.
What causes ImagePullBackOff in Kubernetes?
The node cannot pull the container image. The usual causes are a wrong image name or tag, a private registry without valid imagePullSecrets, or network problems between the node and the registry. The Events section of kubectl describe pod shows which one applies.
What is the difference between a liveness and a readiness probe?
A readiness probe decides whether a Pod should receive traffic; failing it removes the Pod from Service endpoints without restarting it. A liveness probe decides whether the container is stuck; failing it makes the kubelet restart the container.