Phase 2: Pods, Deployments, Services
Time: weeks 1-2. Goal: the holy trinity. This is 70% of day-to-day k8s.
Pod
The smallest deployable unit. A Pod wraps one or more containers that share a network namespace and volumes - they see each other on localhost.
- Why a wrapper instead of bare containers: sidecars (log shippers, proxies) need to live next to the main container.
- You almost never create bare Pods directly. No controller watches them, so they don't survive node failures or deletion.
- Know the lifecycle states: Pending, Running, Succeeded, Failed - and
restartPolicy.
Deployment
Your docker compose service equivalent, but with superpowers.
- Declares "N replicas of this pod template must exist". A ReplicaSet underneath enforces the count.
- Rolling updates: change the image tag, k8s replaces pods gradually.
- Rollbacks:
kubectl rollout undo deployment/<name>.
The self-healing demo that makes k8s click:
kubectl create deployment web --image=nginx --replicas=3
kubectl get pods -w
# in another terminal: kill one
kubectl delete pod <one-of-them>
# watch it resurrectService
Pods are mortal and their IPs change. A Service gives a stable virtual IP and DNS name in front of a moving set of pods.
- ClusterIP (default): internal only, like compose service names.
- NodePort: opens a port on every node. Mostly for local testing.
- LoadBalancer: provisions a cloud load balancer. Costs money per service.
How a Service finds its pods: label selectors. The Service selects app: web, every pod with that label gets traffic. Labels are the duct tape of all of k8s - internalize this. Nothing is linked by name; everything is linked by label.
Cluster DNS
Every Service gets a DNS name: my-service.my-namespace.svc.cluster.local. Within the same namespace, apps just use http://my-service. This is why compose-style inter-service URLs often survive migration unchanged.
Namespaces
Lightweight isolation, like compose project names. Use them to separate apps or environments on one cluster.
YAML anatomy
Every manifest has the same four top-level fields:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
# the resource-specific partUse kubectl explain deployment.spec (and deeper, e.g. deployment.spec.template.spec.containers) instead of googling field names.
Exercise
Full playbook with solutions, expected output, and a load-balancing demo: Lab 2.
Take the traefik/whoami image and, by hand, no copy-paste:
- Write a Deployment with 3 replicas.
- Write a ClusterIP Service in front of it.
kubectl applyboth, port-forward to the Service, verify.- Do a rolling update to a new image tag, watch with
kubectl rollout status. - Roll it back with
kubectl rollout undo.
Checkpoint
- You can explain Pod vs ReplicaSet vs Deployment and why the layers exist.
- You can explain how a Service knows which pods to send traffic to.
- You wrote a working Deployment + Service from a blank file.