Pods, Deployments, and Services

Pods, a complete Deployment example with rolling updates, and the three Service types.

Pods: the smallest deployable unit

A Pod is the smallest unit Kubernetes schedules and runs — not a container directly. Most Pods contain exactly one container, but a Pod can hold multiple tightly-coupled containers that must always be scheduled together, share the same network namespace (so they can talk to each other via localhost), and share storage volumes.

You'll rarely create a bare Pod directly in practice, because a Pod on its own has no self-healing behavior — if it crashes or the node dies, nothing brings it back. That's what a Deployment is for.

YAML
apiVersion: v1
kind: Pod
metadata:
  name: my-app-pod
  labels:
    app: my-app
spec:
  containers:
    - name: app
      image: my-app:1.0
      ports:
        - containerPort: 3000

Deployments: Pods with self-healing and rollouts

A Deployment describes a desired set of identical Pods and manages them for you — creating replacements when Pods fail, and rolling out new versions gradually and safely.

YAML
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
  labels:
    app: my-app
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1
      maxSurge: 1
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
        - name: app
          image: my-app:1.2
          ports:
            - containerPort: 3000
          resources:
            requests:
              cpu: "100m"
              memory: "128Mi"
            limits:
              cpu: "500m"
              memory: "256Mi"
          readinessProbe:
            httpGet:
              path: /healthz
              port: 3000
            initialDelaySeconds: 5
            periodSeconds: 10

Key fields:

  • replicas: 3 — the desired number of identical Pods. If one crashes or its node fails, the Deployment's controller notices the actual count is now below 3 and creates a replacement automatically — this is the reconciliation loop from the introduction in action.
  • selector / template.metadata.labels — these must match. The selector is how the Deployment identifies which Pods belong to it; the template is the exact spec used to create each one.
  • strategy: RollingUpdate — when you change the image tag and re-apply, Kubernetes replaces old Pods with new ones gradually rather than all at once. maxUnavailable: 1 means at most 1 Pod can be down at a time during the rollout; maxSurge: 1 means it can temporarily run 1 extra Pod above the desired count to keep capacity up while the old one is still terminating.
  • resources.requests / resources.limitsrequests is what the scheduler guarantees is reserved for this container when deciding which node to place it on; limits is the hard ceiling the container can't exceed (exceeding a memory limit gets the container killed).
  • readinessProbe — Kubernetes only sends traffic to a Pod once its readiness probe succeeds, and can pull a Pod out of rotation if it starts failing later — critical for avoiding sending live traffic to a Pod that's still starting up.
Bash
$ kubectl apply -f deployment.yaml
deployment.apps/my-app created

$ kubectl get deployments
NAME     READY   UP-TO-DATE   AVAILABLE   AGE
my-app   3/3     3            3           2m

$ kubectl get pods
NAME                     READY   STATUS    RESTARTS   AGE
my-app-7d8f9c-4k2j9      1/1     Running   0          2m
my-app-7d8f9c-x2j4k      1/1     Running   0          2m
my-app-7d8f9c-z9p1m      1/1     Running   0          2m

Rolling out a new version is just editing the image tag and re-applying:

Bash
$ kubectl set image deployment/my-app app=my-app:1.3
deployment.apps/my-app image updated

$ kubectl rollout status deployment/my-app
Waiting for deployment "my-app" rollout to finish: 1 out of 3 new replicas have been updated...
deployment "my-app" successfully rolled out

# If the new version turns out to be broken, roll back instantly
$ kubectl rollout undo deployment/my-app

Services: stable networking for a set of Pods

Pods are disposable — they get new IP addresses every time they're recreated. A Service gives a stable, unchanging way to reach a set of Pods (selected by label, the same way a Deployment selects its Pods), load-balancing traffic across all matching, healthy Pods.

YAML
apiVersion: v1
kind: Service
metadata:
  name: my-app
spec:
  type: ClusterIP
  selector:
    app: my-app
  ports:
    - port: 80
      targetPort: 3000

selector: app: my-app matches the exact same label used by the Deployment's Pods — the Service automatically tracks whichever Pods currently match, even as they're replaced during rollouts or failures.

The three common Service types

Type Reachable from Typical use
ClusterIP (default) Only inside the cluster Internal service-to-service communication — e.g., an API talking to an internal auth service
NodePort Any cluster node's IP, on a fixed port (30000-32767) Simple external access, often for development/testing, or as a building block underneath other mechanisms
LoadBalancer The public internet, via a cloud provider's real load balancer Production external access — the cloud provider provisions an actual external IP/load balancer pointing at the service
YAML
apiVersion: v1
kind: Service
metadata:
  name: my-app-public
spec:
  type: LoadBalancer
  selector:
    app: my-app
  ports:
    - port: 80
      targetPort: 3000

In practice: internal services (a database, an internal API another service calls) use ClusterIP; anything the public internet needs to reach directly uses LoadBalancer (on a cloud provider) or is routed through an Ingress — a separate, more flexible resource for HTTP(S) routing and TLS that sits in front of one or more Services, but is beyond this introduction's scope.

Bash
$ kubectl get services
NAME             TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)        AGE
my-app           ClusterIP      10.96.42.111    <none>          80/TCP         5m
my-app-public    LoadBalancer   10.96.88.201    34.120.11.45    80:31842/TCP   5m

Common mistakes

  • Creating bare Pods directly for anything long-lived — without a Deployment (or similar controller) managing them, a crashed Pod simply stays dead; nothing recreates it.
  • Forgetting that selector labels must exactly match the Pod template's labels — a typo here silently results in a Service or Deployment matching zero Pods, with no error thrown.
  • Exposing an internal-only service (like a database) as LoadBalancer or NodePort by mistake, unnecessarily exposing it to traffic beyond the cluster.
  • Omitting a readinessProbe on a service that takes a few seconds to actually be ready after starting — the Service can send it live traffic before it's actually able to handle requests.

Interview questions

Q: What's the difference between a Pod, a Deployment, and a Service? A Pod is the smallest unit Kubernetes runs — one or more tightly-coupled containers sharing network and storage. A Deployment manages a set of identical Pods declaratively, handling self-healing (recreating failed Pods) and rolling updates to new versions. A Service provides a stable network identity and load balancing across whichever Pods currently match its label selector, since individual Pod IPs are not stable across restarts.

Q: How does a rolling update work, and what do maxUnavailable and maxSurge control? A rolling update replaces old Pods with new ones gradually rather than all at once, so the application stays available throughout the rollout. maxUnavailable caps how many Pods can be down/unavailable at once during the rollout, and maxSurge caps how many extra Pods above the desired replica count can be created temporarily to maintain capacity while old ones are being phased out.

Q: When would you use a ClusterIP Service versus a LoadBalancer Service? ClusterIP is for traffic that only needs to originate from inside the cluster, like one internal service calling another — it's not reachable from outside at all, which is also a security benefit. LoadBalancer provisions an actual external IP/load balancer from the cloud provider and is used when a service needs to be reachable directly from the public internet.