Two objects do most of the work for a typical web app on Kubernetes: a Deployment keeps the right number of pods running and updates them safely, and a Service gives those pods a stable network address. Here’s how they work, with a complete example you can apply.
Set up a cluster first: k3s or kubeadm.
Deployment → ReplicaSet → Pods
- A Pod runs one or more containers. Pods are disposable.
- A ReplicaSet keeps N identical pods running.
- A Deployment manages ReplicaSets, and adds rolling updates and rollbacks.
You almost always create Deployments, not bare pods.
A complete example
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
labels: { app: web }
spec:
replicas: 3
selector:
matchLabels: { app: web }
strategy:
type: RollingUpdate
rollingUpdate: { maxUnavailable: 0, maxSurge: 1 }
template:
metadata:
labels: { app: web }
spec:
containers:
- name: web
image: registry.example.com/myapp:1.8.3
ports:
- containerPort: 8080
resources:
requests: { cpu: 100m, memory: 128Mi }
limits: { memory: 256Mi }
readinessProbe:
httpGet: { path: /health, port: 8080 }
periodSeconds: 5
livenessProbe:
httpGet: { path: /health, port: 8080 }
initialDelaySeconds: 15
periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
name: web
spec:
selector: { app: web }
ports:
- port: 80
targetPort: 8080
kubectl apply -f web.yaml
kubectl get deploy,rs,pods,svc -l app=web
Probes: readiness vs liveness
| Probe | If it fails… | Use for |
|---|---|---|
| readinessProbe | Pod is removed from the Service (no traffic) | “Ready to serve” (warm-up, dependencies) |
| livenessProbe | Container is restarted | Deadlocks, hung processes |
| startupProbe | Delays other probes until it succeeds | Slow-starting apps |
Without a readiness probe, traffic reaches pods before they’re ready. Don’t make liveness probes depend on external services (like the database), or a database outage restarts every pod.
Resources
- requests: what the scheduler reserves (placement decisions).
- limits: the maximum. Exceeding the memory limit kills the container (OOMKilled).
Set realistic requests, or nodes get overcommitted. See Linux cgroups.
Rolling updates and rollbacks
kubectl set image deployment/web web=registry.example.com/myapp:1.8.4
kubectl rollout status deployment/web
kubectl rollout history deployment/web
kubectl rollout undo deployment/web # back to the previous version
kubectl rollout restart deployment/web # restart pods (e.g. after a config change)
With maxUnavailable: 0 and good readiness probes, updates happen without downtime. Better still, change the image tag in the YAML (kept in Git), and apply it. See Docker image tags best practices.
Service types
| Type | Reachable from | Use |
|---|---|---|
| ClusterIP (default) | Inside the cluster (web.default.svc.cluster.local) |
Internal services, behind an Ingress |
| NodePort | Every node’s IP on a high port (30000–32767) | Simple external access, testing |
| LoadBalancer | An external load balancer IP | Cloud environments, or MetalLB/k3s ServiceLB on VPS |
| ExternalName | DNS alias to an outside name | Point to external services |
For websites, use ClusterIP + Ingress with TLS. See Kubernetes ingress and cert-manager.
Scaling
kubectl scale deployment/web --replicas=5
Or automatically, based on CPU or metrics. See Kubernetes HPA autoscaling.
Debugging
kubectl describe deployment web
kubectl get pods -l app=web -o wide
kubectl logs -l app=web --tail=50
kubectl port-forward svc/web 8080:80 # test locally: http://localhost:8080
See Kubernetes debugging pods and kubectl cheat sheet.
Quick reference
| Task | Command |
|---|---|
| Apply | kubectl apply -f web.yaml |
| Status | kubectl rollout status deploy/web |
| Update image | kubectl set image deploy/web web=img:tag |
| Roll back | kubectl rollout undo deploy/web |
| Scale | kubectl scale deploy/web --replicas=5 |
| Test a Service | kubectl port-forward svc/web 8080:80 |
Related: Kubernetes ConfigMaps and Secrets and Helm.