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.
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.
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. Theselectoris how the Deployment identifies which Pods belong to it; thetemplateis 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: 1means at most 1 Pod can be down at a time during the rollout;maxSurge: 1means 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.limits—requestsis what the scheduler guarantees is reserved for this container when deciding which node to place it on;limitsis 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.
$ 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:
$ 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.
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 |
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.
$ 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
selectorlabels 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
LoadBalancerorNodePortby mistake, unnecessarily exposing it to traffic beyond the cluster. - Omitting a
readinessProbeon 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.