Kubernetes
Kubernetes is a control loop with an API in front of it. You do not tell it to start a container; you record a desired state, and controllers keep comparing that record to what is actually running and act on the difference — forever, not once. Every object it offers and every surprising behaviour it has comes from that. This page covers the loop, the handful of objects that matter, why resource limits and health probes cause most production incidents, and when you genuinely need any of it.
- You declare
- A state, not a command
- Controllers
- Reconcile continuously
- Most incidents
- Limits and probes
A control loop, not a command runner
The mental model that makes everything else fall into place is a thermostat. You do not tell a thermostat to turn on the heating; you tell it the temperature you want, and it keeps measuring and acting until the room matches — and then keeps measuring. Kubernetes works the same way. You submit an object describing what should be true, the APIApplication Programming Interface — contract between programsThe rules one program follows to call another: which requests it may send, in what shape, and what comes back. To the caller it is a contract — everything behind it stays a black box, which is exactly what lets the other side be rewritten without breaking you.Full article →All terms server stores it, and a controller loops forever: read the desired state, read the actual state, act on the difference.
This is why deleting a Pod by hand accomplishes nothing lasting: the Deployment still says three replicas, so a controller notices two and makes a third. It is why applying the same manifest twice is harmless — you restated a fact rather than issued a second instruction. And it is why the interesting question during an incident is never "did the command run" but "what does the cluster currently believe should be true, and what is stopping it". The answer is in the object's status and its events, which is the first place to look and the last place people look.
The objects you actually need
The API surface is enormous and the working set is small. Six objects cover most of what a service needs, and each exists because a controller reconciles it against something.
Pod
One or more containers that share a network address and can share a volume. The smallest thing scheduled — and never created directly.
Deployment
Says how many identical Pods should exist and from which image. Handles rolling updates and rollbacks. This is what you write.
Service
A stable name and address in front of a changing set of Pods. Pods come and go with new addresses; the Service does not.
Ingress
Routes outside HTTPHyperText Transfer Protocol — the protocol of the webThe protocol browsers and servers talk in: the client sends a method and an address, the server answers with a status code and a body. It keeps no memory between requests — every request stands alone, which is why anything that must be remembered is carried explicitly.All terms traffic to Services by host and path, and terminates TLS. One entry point instead of one per service.
ConfigMap & Secret
Configuration and credentials supplied to Pods as env vars or files. A Secret is base64-encoded, not encrypted — enable encryption at rest.
StatefulSet
For Pods that need stable identity and their own persistent storage. Necessary for databases, and a reason to ask whether you want one here at all.
A Service is worth one extra sentence because it is where the naming confusion lives. Inside the cluster, a Service gives you a DNS name that resolves to a stable virtual address, and traffic to it is spread across whichever Pods currently match its selector. That indirection is the whole reason a Pod can be replaced at any moment without anything else being reconfigured — and it is also why "the Pod is running but nothing can reach it" is almost always a selector that does not match the Pod's labels.
Where the incidents come from
Two settings cause more production trouble than everything else combined, and both look like boilerplate. The first is resources. A request is what the scheduler reserves when deciding which node a Pod fits on; a limit is the ceiling enforced while it runs. Set requests too low and the node is oversubscribed, so Pods land where there is no room to grow. Set them too high and you pay for capacity nobody uses. Omit them entirely and the scheduler is guessing, which is how one service ends up starving its neighbours on the same node.
The two limits behave differently when reached, and confusing them wastes hours. Exceeding the memory limit gets the process killed by the kernel: the Pod shows OOMKilled, restarts, and there is no stack tracethe calls in progress at an errorThe list of function calls that were in progress when an error was raised, innermost first, printed with the error. It shows where a failure surfaced, which is not always where it was caused — and a process killed from outside, by the kernel or an orchestrator, leaves none at all.All terms because no exception was ever raised. Exceeding the CPUCentral Processing Unit — the processorThe processor that executes a program’s instructions. Calling a task “CPU-bound” means it is limited by computation rather than by waiting on disk or network — and the two need opposite fixes, so the distinction is worth making early.All terms limit kills nothing — the process is throttled, so the symptom is latency that grows under load with no error anywhere, which people spend a day blaming on the database. Memory kills, CPU slows: that sentence saves the day it takes to learn it.
| Probe | What it answers | What happens when it fails |
|---|---|---|
| Readiness | Can this Pod take traffic right now? | It is removed from the Service and left running. The safe one, and the one to reach for first. |
| Liveness | Is this process wedged and beyond recovery? | The container is killed and restarted. Powerful, and the usual cause of a self-inflicted outage. |
| Startup | Has a slow-booting application finished starting? | The other probes are held off until it passes, which is how a slow start stops being a restart loop. |
How this shows up in real delivery
The honest question is whether you need it at all, and the answer is less often yes than the industry conversation suggests. Kubernetes buys you scheduling across many machines, self-healing, rolling updates and one declarative interface for teams that each run several services. It charges for that in a permanent operational surface: upgrades, networking, storage classes, access control, and a class of failure whose cause is the platform rather than your code. Three services on two machines get most of the benefit from a container runtime and a load balancerspreads requests across serversA server or managed service that receives incoming requests and spreads them across several copies of an application, skipping any copy that fails its health check. It is also one more network hop with its own timeouts, so its settings and the application’s have to agree.All terms, at a fraction of the cost — and a managed platform that hides the control plane is the middle option worth considering before either extreme.
If you do run it, the practice that pays for itself fastest is keeping manifests in a repository and having something apply them, rather than people running commands against the cluster. The cluster then has a reviewable history, a rollback is a revert, and the difference between what should be running and what is running becomes visible instead of anecdotal. Templating — Helm, Kustomize or either of their descendants — earns its place the moment the same manifests exist per environment, and stops earning it the moment nobody can tell what the templates produce.
On stateful workloads: the platform can run a database, and the question is whether it should. A StatefulSet plus persistent volumes gives you identity and storage that survive rescheduling, and it also gives you responsibility for backups, failover, version upgrades and the storage layer's own failure modes — on top of a scheduler that is designed to move things. Teams that run their own databases this way generally have someone whose job that is. Teams that do not are usually better served by a managed database and a cluster that holds only stateless work.
Where it degrades
- Adopting it for three services on two machines, and paying the operational cost for none of the benefit.
- A liveness probe with a dependency in its path, which turns a slow database into a cluster-wide restart storm.
- No resource requests, so the scheduler guesses and one service starves its neighbours.
- Chasing an
OOMKilledrestart for a stack trace that was never produced, because the kernel did the killing. - Changes made with
kubectl editon a live cluster, which the next apply silently discards. - Secrets committed as manifests, base64-encoded and mistaken for encrypted.
- A Service whose selector does not match the Pod labels, so a healthy Pod receives nothing.
- Running your own database in-cluster with nobody owning backups, failover or storage.
- Templating layered on templating, until no one can say what is actually applied.
When to use it
Use it when
- Many services across many machines, where scheduling and self-healing are work you would otherwise do by hand.
- Several teams needing one declarative interface and one set of guarantees to deploy against.
- Workloads whose demand varies enough that scaling replicas up and down is a routine event.
- Organisations already committed to it, where a managed control plane removes most of the operational cost.
Avoid it when
- A small number of services on a small number of machines, where a runtime and a load balancer suffice.
- Teams with no one to own the platform, where the cluster becomes the least understood thing in production.
- Stateful databases, unless somebody genuinely owns backups, failover and the storage layer.
- As a way to get portability between clouds, which the managed add-ons around the cluster quietly remove.
Found this useful?
Share it with someone who is working on the same problem.