This is the first post in our Kubernetes fundamentals series. Later posts cover networking, ConfigMaps and Secrets, storage, and autoscaling.
Docker (covered in our previous series) answers "how do I package and run one container." Kubernetes answers a different question: "how do I run hundreds of containers, across many machines, so that if one fails, something notices and fixes it automatically." That's a genuinely different problem, and it needs a few new concepts to solve it.
Pods: the smallest thing Kubernetes schedules
A Pod is the smallest deployable unit in Kubernetes — not a container itself, but a thin wrapper around one or more containers that always get scheduled onto the same machine and share a network namespace.
# pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: web-pod
spec:
containers:
- name: web
image: my-app:1.0
ports:
- containerPort: 8080kubectl apply -f pod.yaml
kubectl get podsNAME READY STATUS RESTARTS AGE
web-pod 1/1 Running 0 12s
Most Pods run exactly one container — the multi-container case exists for genuinely coupled containers that must share a network and lifecycle, like a "sidecar" that ships logs from a main application container sitting right next to it in the same Pod.
Why you don't create Pods directly
Here's the detail that actually matters: delete that Pod, and Kubernetes does not recreate it.
kubectl delete pod web-podA bare Pod has no supervisor watching it. If the node it's running on fails, or someone deletes it, it's simply gone. That's almost never what you actually want in a real system — which is exactly why, in practice, you essentially never create bare Pods. You create a Deployment, and let it create and manage Pods for you.
Deployments: the thing that actually keeps Pods running
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: web
image: my-app:1.0
ports:
- containerPort: 8080kubectl apply -f deployment.yaml
kubectl get podsNAME READY STATUS RESTARTS AGE
web-7d9f8c6b5d-2xk9p 1/1 Running 0 8s
web-7d9f8c6b5d-4mn7q 1/1 Running 0 8s
web-7d9f8c6b5d-9wq2r 1/1 Running 0 8s
A Deployment declares "I want 3 replicas of this Pod template, always." It creates a ReplicaSet (a controller whose only job is maintaining a specific replica count) which in turn creates the actual Pods. Delete one of those Pods, and — unlike the bare Pod earlier — Kubernetes notices the replica count has dropped below 3 and creates a replacement within seconds, without anyone intervening:
kubectl delete pod web-7d9f8c6b5d-2xk9p
kubectl get podsNAME READY STATUS RESTARTS AGE
web-7d9f8c6b5d-4mn7q 1/1 Running 0 45s
web-7d9f8c6b5d-9wq2r 1/1 Running 0 45s
web-7d9f8c6b5d-k8j3x 1/1 Running 0 3s
This self-healing behavior — actual state continuously reconciled against desired state — is the core idea underneath essentially everything in Kubernetes, and it's the direct payoff of never creating bare Pods in a real deployment.
Rolling updates: changing the image without downtime
kubectl set image deployment/web web=my-app:2.0
kubectl rollout status deployment/webWaiting for deployment "web" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "web" rollout to finish: 2 out of 3 new replicas have been updated...
deployment "web" successfully rolled out
A Deployment updates Pods gradually by default — creating new ones running the new image, waiting for them to become ready, then removing old ones — rather than terminating everything at once. If a rollout turns out to be bad:
kubectl rollout undo deployment/webreverts to the previous version, the same gradual way.
Services: giving a moving target a stable address
Pods are disposable — they get recreated, and each new one gets a new IP address. Nothing that depends on a Pod's IP directly can survive a Pod being replaced, which happens routinely. A Service solves this by providing one stable address that automatically routes to whichever Pods currently match its selector.
# service.yaml
apiVersion: v1
kind: Service
metadata:
name: web
spec:
selector:
app: web
ports:
- port: 80
targetPort: 8080kubectl apply -f service.yamlAny other Pod in the cluster can now reach this Deployment's Pods at http://web, regardless of how many times individual Pods get replaced underneath it — the selector: app: web is what continuously matches the Service to whichever Pods currently carry that label, updating automatically as Pods come and go. We go much deeper on exactly how that routing works, and the different Service types available, in the next post in this series.
What to actually remember from this post
- A Pod is the smallest schedulable unit, but bare Pods have no self-healing — almost nothing in real usage creates them directly.
- A Deployment manages Pods for you, maintaining a desired replica count and replacing failed or deleted Pods automatically.
- Rolling updates replace Pods gradually, with an easy, equally gradual rollback if something goes wrong.
- A Service provides a stable address for a set of Pods whose individual IPs are constantly changing underneath it.
Next in the series: Kubernetes Networking: ClusterIP, NodePort, and Ingress, where we cover how traffic actually gets from outside the cluster to a Service.
