Kubernetes Core Concepts: Pods, Deployments & Services
Quick answer
Learn how Kubernetes works — the cluster model, Pods, Deployments, Services, Namespaces, and the kubectl commands you'll use every day. If you know Docker Compose, you already know the mental model.
- How a Cluster Works
- Setting Up a Local Cluster
- Pods — The Atomic Unit
- Deployments — Desired State
- Services — Stable Endpoints
beginner · 60 min
Before you begin
- Docker fundamentals — images, containers, Dockerfile
- Docker Compose — services, ports, environment variables
- kubectl installed and a local cluster (Docker Desktop K8s, kind, or minikube)
Kubernetes Core Concepts: Pods, Deployments & Services
If you've worked through the Docker and Docker Compose tutorials, you already have the mental model. Kubernetes extends the same concepts across a cluster of machines. Everything in Compose has a direct equivalent:
| Docker Compose | Kubernetes |
|---|---|
| Container | Pod |
services: entry | Deployment + Service |
| Service name DNS | Service ClusterIP DNS |
| Named volume | PersistentVolumeClaim |
.env / env_file | ConfigMap + Secret |
depends_on: condition: service_healthy | readinessProbe |
docker compose up | kubectl apply -f |
docker compose down | kubectl delete -f |
The key difference: Compose runs everything on one machine. Kubernetes runs workloads across a cluster of nodes, scheduling them automatically, restarting them on failure, and routing traffic between them.
How a Cluster Works
A Kubernetes cluster has two roles:
Control plane — the cluster's brain (usually 3 nodes for high availability):
kube-apiserver— the single entry point; everykubectlcommand talks to this REST APIetcd— key-value store; the source of truth for all cluster statekube-scheduler— watches for unscheduled Pods and assigns them to nodeskube-controller-manager— runs control loops (Deployment controller, Node controller, etc.)
Worker nodes — where your workloads run:
kubelet— agent on each node; ensures Pods described by specs are actually runningkube-proxy— maintains network rules for Service routing (iptables/IPVS)- Container runtime —
containerdorCRI-O(Docker daemon was removed in K8s 1.24)
Docker images still work. Kubernetes speaks the OCI image format, which Docker produces. Only the runtime — the thing that actually runs containers — changed.
Setting Up a Local Cluster
1# kubectl — the Kubernetes CLI
2brew install kubectl # macOS
3# Linux: follow https://kubernetes.io/docs/tasks/tools/
4
5# Local cluster options (pick one)
6# Docker Desktop — enable Kubernetes in Settings → Kubernetes
7kind create cluster --name dev # kind — Kubernetes IN Docker (recommended for CI)
8minikube start # minikube — full local cluster with add-ons
9
10# Verify
11kubectl cluster-info
12kubectl get nodesPods — The Atomic Unit
A Pod is one or more containers that:
- Share the same network namespace (same IP address, communicate via
localhost) - Can share volumes
- Are always scheduled on the same node
In practice, most Pods run a single container. The multi-container pattern (sidecars) is for helpers that need to share the same localhost — a log shipper, a proxy, a config reloader.
1# pod.yaml
2apiVersion: v1
3kind: Pod
4metadata:
5 name: nginx
6 labels:
7 app: nginx
8spec:
9 containers:
10 - name: nginx
11 image: nginx:1.27-alpine
12 ports:
13 - containerPort: 80
14 resources:
15 requests:
16 cpu: "100m" # 100 millicores = 0.1 CPU
17 memory: "64Mi"
18 limits:
19 cpu: "200m"
20 memory: "128Mi"kubectl apply -f pod.yaml
kubectl get pods
kubectl describe pod nginx # Full details — events, node, IP, container state
kubectl logs nginx
kubectl exec -it nginx -- sh # Alpine uses sh, not bash
kubectl delete pod nginxYou rarely create bare Pods directly. If the node a bare Pod runs on dies, the Pod is gone — nothing reschedules it. Use a Deployment instead.
Deployments — Desired State
A Deployment tells Kubernetes: "I want N replicas of this Pod running at all times." Kubernetes continuously reconciles actual state toward desired state.
1# deployment.yaml
2apiVersion: apps/v1
3kind: Deployment
4metadata:
5 name: api
6spec:
7 replicas: 3
8 selector:
9 matchLabels:
10 app: api # Must match template labels
11 template:
12 metadata:
13 labels:
14 app: api
15 spec:
16 containers:
17 - name: api
18 image: nginx:1.27-alpine
19 ports:
20 - containerPort: 80
21 resources:
22 requests:
23 cpu: "100m"
24 memory: "128Mi"
25 limits:
26 cpu: "500m"
27 memory: "256Mi"
28 readinessProbe:
29 httpGet:
30 path: /health
31 port: 80
32 initialDelaySeconds: 5
33 periodSeconds: 10
34 livenessProbe:
35 httpGet:
36 path: /health
37 port: 80
38 initialDelaySeconds: 15
39 periodSeconds: 20
40 strategy:
41 type: RollingUpdate
42 rollingUpdate:
43 maxSurge: 1 # Create 1 extra pod before removing old ones
44 maxUnavailable: 0 # Never drop below desired replica countProbes
| Probe | Failure effect | Use case |
|---|---|---|
readinessProbe | Pod removed from Service endpoints (not restarted) | App is starting up or temporarily overloaded |
livenessProbe | Container restarted | App is deadlocked and needs to be killed |
startupProbe | Gates liveness/readiness until it passes | Slow-starting apps (JVM, database migrations) |
Rolling updates and rollbacks
1# Update the image — triggers a rolling update
2kubectl set image deployment/api api=nginx:1.28-alpine
3
4# Watch the rollout progress
5kubectl rollout status deployment/api
6
7# Roll back to the previous version
8kubectl rollout undo deployment/api
9
10# See rollout history
11kubectl rollout history deployment/api
12
13# Roll back to a specific revision
14kubectl rollout undo deployment/api --to-revision=2
15
16# Scale manually
17kubectl scale deployment/api --replicas=5Services — Stable Endpoints
Pods are ephemeral — they restart, reschedule, and get new IP addresses. A Service gives a stable DNS name and virtual IP (the ClusterIP) that load-balances across matching Pods.
Services find Pods using label selectors. Any Pod with app: api is automatically included in the Service's endpoint list.
1# service.yaml
2apiVersion: v1
3kind: Service
4metadata:
5 name: api
6spec:
7 selector:
8 app: api # Matches any Pod with this label
9 ports:
10 - protocol: TCP
11 port: 80 # Port the Service listens on
12 targetPort: 80 # Port on the Pod
13 type: ClusterIP # Default — only reachable within the clusterInside the cluster, api is reachable at:
api(same namespace)api.default(cross-namespace short form)api.default.svc.cluster.local(fully qualified DNS name)
Service types
| Type | Reachable from | Use case |
|---|---|---|
ClusterIP | Inside cluster only | Microservice-to-microservice (default) |
NodePort | Outside, via <NodeIP>:<Port> (30000–32767) | Dev/testing without a cloud LB |
LoadBalancer | Outside, via cloud LB IP | Production external traffic (EKS, GKE, AKS) |
ExternalName | DNS alias to an external hostname | Point db to db.rds.amazonaws.com |
1# LoadBalancer — cloud provider creates an external LB
2spec:
3 type: LoadBalancer
4 selector:
5 app: api
6 ports:
7 - port: 80
8 targetPort: 80Namespaces — Logical Isolation
Namespaces partition resources within a cluster. They're organisational, not security boundaries (for traffic isolation use NetworkPolicies).
1kubectl get namespaces
2# default — where resources go when you don't specify -n
3# kube-system — Kubernetes system components (DNS, kube-proxy, etc.)
4# kube-public — publicly readable cluster info
5
6kubectl create namespace staging
7kubectl apply -f deployment.yaml -n staging
8kubectl get pods -n staging
9kubectl get pods -A # All namespacesSet a default namespace so you don't type -n on every command:
kubectl config set-context --current --namespace=stagingEssential kubectl Commands
1# Listing resources
2kubectl get pods
3kubectl get pods -o wide # Include node and IP columns
4kubectl get deployments
5kubectl get services
6kubectl get all # Pods, deployments, services, replicasets
7
8# Inspecting
9kubectl describe pod <name> # Full details including events
10kubectl describe deployment <name>
11kubectl logs <pod>
12kubectl logs -f <pod> # Follow live
13kubectl logs <pod> -c <container> # Specific container in multi-container pod
14kubectl logs --previous <pod> # Logs from the last terminated container
15
16# Executing
17kubectl exec -it <pod> -- bash
18kubectl exec -it <pod> -- sh # Alpine images
19kubectl exec <pod> -- env # Print environment variables
20
21# Applying and deleting
22kubectl apply -f manifest.yaml # Create or update (idempotent)
23kubectl delete -f manifest.yaml
24kubectl delete pod <name>
25kubectl delete deployment <name>
26
27# Debugging
28kubectl get events --sort-by=.metadata.creationTimestamp
29kubectl port-forward pod/<name> 8080:80 # Forward local port → pod port
30kubectl port-forward service/<name> 8080:80
31kubectl top pods # CPU/memory (requires metrics-server)
32kubectl top nodesThe Reconciliation Loop
Kubernetes' core pattern: declare desired state, Kubernetes makes it happen.
You write YAML describing what you want. Kubernetes continuously watches the actual state of the cluster and acts to make it match:
- A node dies → Pods are rescheduled on remaining nodes
- A Pod crashes → the Deployment controller starts a replacement
- You update an image → a rolling update runs automatically
- You delete a Pod manually → the Deployment creates a new one immediately
This is fundamentally different from imperative container management (docker run, docker stop). In Kubernetes you never manage individual containers — you manage desired state.
A Complete Example
1# app.yaml — Deployment + Service
2apiVersion: apps/v1
3kind: Deployment
4metadata:
5 name: api
6spec:
7 replicas: 2
8 selector:
9 matchLabels:
10 app: api
11 template:
12 metadata:
13 labels:
14 app: api
15 spec:
16 containers:
17 - name: api
18 image: nginx:1.27-alpine
19 ports:
20 - containerPort: 80
21 resources:
22 requests:
23 cpu: "100m"
24 memory: "64Mi"
25 limits:
26 cpu: "200m"
27 memory: "128Mi"
28---
29apiVersion: v1
30kind: Service
31metadata:
32 name: api
33spec:
34 selector:
35 app: api
36 ports:
37 - port: 80
38 targetPort: 80
39 type: ClusterIPkubectl apply -f app.yaml
kubectl get pods
kubectl get service api
kubectl port-forward service/api 8080:80
curl http://localhost:8080Frequently Asked Questions
Why do I need a Deployment rather than creating Pods directly?
A bare Pod is not recreated if its node fails or it is deleted — there is nothing watching it. A Deployment maintains a desired replica count, handles rolling updates and lets you roll back. Create Pods directly only for genuinely one-off debugging.
What actually happens when I apply a manifest?
The API server validates and stores the desired state in etcd. Controllers notice the difference between desired and actual and act — the deployment controller creates a ReplicaSet, which creates Pods, and the scheduler assigns them to nodes. Nothing is executed imperatively; everything is reconciled.
When should I use a namespace?
To separate environments, teams or tenants within a cluster, and as the boundary for quotas, RBAC and network policy. Namespaces are not a security boundary on their own — they are the unit those controls apply to. A handful of related services does not need one each.
What is the difference between a Service and an Ingress?
A Service gives a stable address for a set of Pods, mostly for traffic inside the cluster. An Ingress routes external HTTP traffic to Services by host and path, and needs a controller to do anything. Service for connectivity, Ingress for the front door.
What's Next
- Kubernetes Networking & Ingress — how traffic flows inside and into the cluster, Network Policies, and Ingress controllers
- Kubernetes Storage, ConfigMaps & Secrets — externalizing configuration and persisting data
- Kubernetes RBAC & Security — service accounts, roles, pod security contexts
These tutorials are part of the Kubernetes Foundations learning path — Stage 3 of the Platform Engineering Roadmap.
Next in Kubernetes Foundations
Kubernetes Networking & Ingress
We built Podscape to simplify Kubernetes workflows like this — logs, events, and cluster state in one interface, without switching tools.
Struggling with this in production?
We help teams fix these exact issues. Our engineers have deployed these patterns across production environments at scale.