Section 4 · Kubernetes Core
Pods, ReplicaSets and Deployments
20 min3 min readSpot the bug
Lesson at a glance
Write a Deployment manifest for notes-api, understand how it owns ReplicaSets and Pods through labels and selectors, and apply, inspect and scale it from files.
- 1Pods: the unit Kubernetes runs
- 2Deployments, ReplicaSets, Pods
- 3Labels and selectors
- 4Apply, inspect, scale
No video here — just the real thing: a written lesson, runnable examples, a quiz and practice.
The imperative kubectl create deployment from the last lesson was fine for a demo, and useless for a team. Nobody can review it, nobody knows what flags were used, and next week nobody can reproduce it. From here on, notes-api’s Kubernetes setup lives in YAML files under k8s/, next to the Dockerfile, reviewed like code.
Pods: the unit Kubernetes runs
A Pod is one or more containers that are scheduled together onto one node and share a network namespace (one IP, one localhost) and any volumes you declare. Most Pods have one main container. A second container is for something tightly coupled to the first, like a log shipper or a proxy that must live and die with it.
A minimal Pod manifest looks like this:
apiVersion: v1
kind: Pod
metadata:
name: notes-api-test
spec:
containers:
- name: api
image: ghcr.io/skylane/notes-api:1.4.0
ports:
- containerPort: 3000You won’t create Pods like this in practice. A bare Pod isn’t replaced if it crashes on a dead node, can’t be scaled, and can’t be updated to a new image gradually. Pods are cattle: every one is disposable, gets a new name and IP when replaced, and is managed by something above it.
Deployments, ReplicaSets, Pods
A Deployment is that something. It owns a ReplicaSet, whose only job is to keep N identical Pods running. The Deployment’s job is to manage ReplicaSets over time: when you change the Pod template, it creates a new ReplicaSet and shifts Pods over, which is the rolling update you’ll study in section 5.
apiVersion: apps/v1
kind: Deployment
metadata:
name: notes-api
labels:
app.kubernetes.io/name: notes-api
spec:
replicas: 3
selector:
matchLabels:
app.kubernetes.io/name: notes-api
template:
metadata:
labels:
app.kubernetes.io/name: notes-api
spec:
containers:
- name: api
image: ghcr.io/skylane/notes-api:1.4.0
ports:
- name: http
containerPort: 3000
env:
- name: PORT
value: "3000"Read it in two halves. The top half is the Deployment itself: its name, how many replicas, and which Pods it owns (selector). Everything under template is a Pod spec, the blueprint each replica is stamped from. Note that apiVersion differs by kind: Pods are core v1, Deployments are apps/v1.
Labels and selectors
Kubernetes objects don’t point at each other by name. They find each other by labels, key-value pairs in metadata.labels, and selectors, queries over those labels. The ReplicaSet counts its Pods by running its selector; next lesson, a Service will find Pods to send traffic to the same way.
That’s why the Deployment’s selector.matchLabels must match template.metadata.labels: the Deployment must be able to find the Pods its own template creates. The API server checks this and rejects a mismatch. The selector is also immutable once the Deployment exists, so choose it carefully. The app.kubernetes.io/name key is one of Kubernetes’ recommended labels; tools and dashboards understand it, and using it consistently pays off.
Apply, inspect, scale
kind load docker-image ghcr.io/skylane/notes-api:1.4.0 --name notes
kubectl apply -f k8s/deployment.yaml
kubectl get deploy,rs,pods -l app.kubernetes.io/name=notes-api
kubectl describe deployment notes-apikind load copies the image into the cluster under its full name, so the manifest can use the same reference production will. -l filters by label, the same mechanism the controllers use. Look at the Pod names: notes-api-6c8f9d7b5-x2k4q is the Deployment name, then the ReplicaSet’s pod-template-hash, then a random suffix. Change anything in the template and you’ll get a new ReplicaSet with a new hash.
To scale, change replicas: 3 to 5 in the file and apply again. kubectl apply compares your file with what’s stored and sends only the difference. kubectl diff -f k8s/ shows that difference before you commit to it, and you should make a habit of running it.
kubectl scale deployment/notes-api --replicas=5 also works, and has its place in an emergency. But the file still says 3, so the next routine apply quietly scales you back down. Whatever you change by hand, change in Git straight after.
Your Pods are running, but each has its own IP that changes every time it’s replaced. Nothing can reliably reach them yet. That’s what Services are for.
Key takeaways
- A Pod is one or more containers that share a network identity and storage; it’s the smallest thing Kubernetes schedules, and it’s disposable.
- You almost never create bare Pods; a Deployment manages a ReplicaSet, which keeps the right number of Pods running.
- A Deployment’s
spec.selector.matchLabelsmust match the labels inspec.template.metadata.labels, and the selector can’t be changed after creation. - Keep manifests in Git and change them with
kubectl apply -f, so the files stay the source of truth. - Use current API versions such as
apps/v1; manifests copied from old posts withextensions/v1beta1no longer work.