Skip to main content

Kubernetes fundamentals

Kubernetes is a control loop with an API in front of it. You write down the state you want; a set of controllers continuously compares that against the state of the cluster and takes action to close the gap. Almost every behaviour that seems surprising at first becomes obvious once you think in terms of desired state versus observed state.

Cluster anatomy

Control plane

ComponentResponsibility
kube-apiserverThe only component that talks to etcd. Validates, authenticates, authorises, and persists every object. Everything else is a client of this API.
etcdConsistent key-value store holding all cluster state. The one component you must back up.
kube-schedulerWatches for Pods with no nodeName and picks a node based on resource requests, affinity rules, taints, and topology constraints.
kube-controller-managerRuns the built-in controllers — Deployment, ReplicaSet, Node, Job, EndpointSlice, and dozens more.
cloud-controller-managerCloud-specific glue: provisioning load balancers, attaching disks, labelling nodes with zone information.

Every node

ComponentResponsibility
kubeletThe node agent. Watches the API server for Pods assigned to its node and drives the container runtime to make them exist. Reports status back.
Container runtimecontainerd or CRI-O. Actually pulls images and starts containers, via the CRI (Container Runtime Interface).
kube-proxyPrograms iptables or IPVS rules so that Service virtual IPs are routed to healthy backing Pods.

The Pod

A Pod is the smallest object you can schedule. It is one or more containers that share a network namespace and can share volumes — they reach each other on localhost, and they are always scheduled together onto the same node.

pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: web
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:1.27-alpine
ports:
- containerPort: 80
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
memory: 256Mi
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 3
periodSeconds: 5

You rarely create Pods directly. A bare Pod is not rescheduled if its node dies — nothing owns it, so nothing recreates it. That job belongs to a controller.

Requests vs limits

This distinction decides whether your workload is stable.

  • requests are what the scheduler reserves. A node is considered "full" based on the sum of requests, not on actual usage.
  • limits are enforced at runtime by cgroups. Exceeding a memory limit gets the container OOM-killed (exit code 137). Exceeding a CPU limit does not kill anything — the container is throttled.

Workload controllers

Deployment → ReplicaSet → Pod

A Deployment does not manage Pods. It manages ReplicaSets, and a ReplicaSet manages Pods. That indirection is exactly what makes rollouts and rollbacks work: each new Pod template produces a new ReplicaSet, and the Deployment scales the new one up while scaling the old one down.

deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 3
revisionHistoryLimit: 5
selector:
matchLabels:
app: web
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # one extra Pod may exist during the rollout
maxUnavailable: 0 # never drop below 3 ready Pods
template:
metadata:
labels:
app: web # must match spec.selector.matchLabels
spec:
containers:
- name: nginx
image: nginx:1.27-alpine
ports:
- containerPort: 80

Choosing a controller

ControllerUse it whenKey property
DeploymentStateless servicesInterchangeable Pods, rolling updates, easy rollback
StatefulSetDatabases, queues, anything with identityStable ordinal names (db-0, db-1), stable per-Pod storage, ordered rollout
DaemonSetNode-level agents: log shippers, CNI, node exportersExactly one Pod per matching node
JobRun-to-completion batch workRetries until completions succeed
CronJobScheduled batch workCreates Jobs on a cron schedule

The reconciliation loop, concretely

kubectl apply -f deployment.yaml
kubectl rollout status deployment/web --timeout=120s

Delete a Pod managed by a Deployment and a replacement appears within seconds. The ReplicaSet controller observed replicas: 3 but counted 2, so it created one. Nothing "noticed the failure" in a special-cased way — the loop simply ran again.

Namespaces and labels

Namespaces partition names, not networks. Two Pods in different namespaces can talk to each other freely unless a NetworkPolicy says otherwise; namespaces exist for RBAC boundaries, quotas, and avoiding name collisions.

Labels are the actual wiring of the cluster. A Service finds its Pods with a label selector; a ReplicaSet finds the Pods it owns the same way. Annotations, by contrast, carry data for tools and are never used for selection.

kubectl get pods -l 'app=web,tier notin (canary)' --all-namespaces
kubectl label pod web-abc123 tier=canary --overwrite

Health probes

ProbeFailure behaviourUse for
startupProbeSuspends the other probes until it passes onceSlow-booting apps (JVM, large migrations)
readinessProbePod removed from Service endpoints; not restarted"Can I serve traffic right now?"
livenessProbeContainer is restarted"Am I wedged and unrecoverable?"

A common outage pattern: pointing a liveness probe at an endpoint that depends on a database. The database blips, every replica fails liveness at once, and the whole Deployment restart-loops. Readiness should depend on dependencies; liveness should only test the process itself.