Skip to content

Lesson 1 of 9 · 20m

Pods, Deployments, and Services: the shape of a workload

The three objects that carry almost every workload, and why you manage ReplicaSets by declaring a Deployment.

Your service just got its first real traffic and someone asks the question that matters: what happens when a node dies at 2am? In Kubernetes you don't deploy processes — you declare desired state, and controllers keep converging reality toward it. Three objects carry almost every workload you'll ever ship: the Pod, the Deployment, and the Service.

A Pod is the scheduling unit: one or more containers that share a network namespace (same IP, same localhost) and can share volumes. A pod is also disposable by design — when its node dies, the pod is gone. You almost never create bare Pods in production, because nothing reschedules them.

A Deployment is what you actually write. It manages a ReplicaSet, which keeps n identical pods alive, and it gives you rolling updates by creating a new ReplicaSet per pod-template change. A Service gives a stable virtual IP and DNS name in front of a shifting set of pods, selecting them by labels:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 3
  selector:
    matchLabels:
      app: api
  template:
    metadata:
      labels:
        app: api
    spec:
      containers:
        - name: api
          image: registry.example.com/api:1.4.2
          ports:
            - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: api
spec:
  selector:
    app: api
  ports:
    - port: 80
      targetPort: 8080

Apply it and watch the machinery work:

kubectl apply -f api.yaml
kubectl rollout status deployment/api
kubectl get pods -o wide          # which node is each pod on?
kubectl get endpointslices -l kubernetes.io/service-name=api

The Service doesn't know about pods directly — the controller writes EndpointSlice objects listing ready pod IPs, and kube-proxy (or your CNI's replacement) programs the dataplane. Inside the cluster, api.default.svc.cluster.local resolves to the Service IP. A Deployment's pod template labels must match its selector, and the selector is effectively immutable: changing it is rejected, because it would orphan every running pod.

Try this

Deploy the manifest above against kind or minikube, scale to five replicas with kubectl scale deployment/api --replicas=5, then delete a pod by name. Watch kubectl get pods -w replace it within seconds. Then delete the whole node if you're on kind (docker stop a worker container) and watch the pods reschedule elsewhere.

Pitfalls

  • Running bare Pods. They die with their node and nothing brings them back. Always go through a Deployment, StatefulSet, or DaemonSet.
  • Confusing port (what the Service listens on) with targetPort (what the pod listens on). A mismatched targetPort is a silent blackhole: the Service exists, endpoints exist, and every request times out.
  • Expecting Services to route by path. They don't — that's what Ingress or the Gateway API is for.