
Kubernetes Orchestration Example: Deploy, Scale, Update
Kubernetes orchestration means you describe the state you want, such as "run three copies of this container, reachable at this address, with this config", and Kubernetes keeps the cluster in that state. It schedules containers onto nodes, restarts them when they crash, scales them with load, and swaps versions without downtime. The example below runs that workflow end to end on a local cluster. Every manifest is copy-pasteable, and each step shows which orchestration feature does the work.
As of October 2026 the current release is Kubernetes 1.37, and 1.36 and 1.35 are still supported (kubernetes.io/releases). Everything here uses stable APIs (apps/v1, v1, autoscaling/v2, batch/v1) that work on any supported version.
What the example covers
| Step | Kubernetes object | Orchestration job |
|---|---|---|
| 1 | Cluster (kind) | Nodes to schedule onto |
| 2 | ConfigMap, Secret | Configuration kept out of the image |
| 3 | Deployment | Desired replicas, health checks, self-healing |
| 4 | Service | Stable address and load balancing across pods |
| 5 | HorizontalPodAutoscaler | Scaling with CPU load |
| 6 | Deployment rollout | Zero-downtime updates and rollback |
| 7 | CronJob | Scheduled batch work |
| 8 | Gateway API | Traffic from outside the cluster |
Step 1: Create a local cluster
kind runs Kubernetes nodes as Docker containers, which is the quickest way to get a disposable cluster. You need Docker and kubectl.
brew install kind # or: go install sigs.k8s.io/kind@latest
kind create cluster --name demo
kubectl get nodes
minikube or Docker Desktop's built-in Kubernetes work too. The manifests below don't change.
Step 2: Configuration with a ConfigMap and a Secret
Keep settings out of the container image so the same image runs in every environment.
# config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: web-config
data:
APP_MODE: "production"
LOG_LEVEL: "info"
---
apiVersion: v1
kind: Secret
metadata:
name: web-secret
type: Opaque
stringData:
API_KEY: "replace-me"
Secrets are only base64-encoded by default, not encrypted. In a real cluster, turn on encryption at rest and limit who can read Secrets with RBAC (Kubernetes docs: Secrets).
Step 3: A Deployment that heals itself
The Deployment is the core of the workflow. It tells Kubernetes how many pods to run, what's in them, and how to tell whether they're healthy.
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: 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: nginx:1.28
ports:
- containerPort: 80
envFrom:
- configMapRef:
name: web-config
- secretRef:
name: web-secret
resources:
requests:
cpu: 100m
memory: 64Mi
limits:
memory: 128Mi
readinessProbe:
httpGet:
path: /
port: 80
periodSeconds: 5
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 10
periodSeconds: 10
kubectl apply -f config.yaml -f deployment.yaml
kubectl get pods -l app=web
What each part does:
replicas: 3: the ReplicaSet the Deployment creates keeps exactly three pods running.resources.requests: the scheduler uses these to choose a node with room. The HPA in step 5 also needs a CPU request to compute utilization.readinessProbe: a pod gets traffic only after it passes.livenessProbe: a container that stops responding gets restarted.
Now test self-healing. Delete a pod and watch a replacement appear within seconds:
kubectl delete $(kubectl get pods -l app=web -o name | head -n 1)
kubectl get pods -l app=web -w
That's orchestration at its simplest: you never told Kubernetes to start a new pod. It saw that the actual state (two pods) didn't match the desired state (three) and fixed it.
Step 4: A Service for a stable address
Pods come and go, and their IPs change. A Service gives them one stable name and load-balances across whichever pods are ready.
# service.yaml
apiVersion: v1
kind: Service
metadata:
name: web
spec:
selector:
app: web
ports:
- port: 80
targetPort: 80
kubectl apply -f service.yaml
kubectl port-forward service/web 8080:80 # then open http://localhost:8080
Inside the cluster, other pods reach it at http://web (or web.default.svc.cluster.local).
Step 5: Autoscaling with a HorizontalPodAutoscaler
The HPA needs resource metrics, which come from Metrics Server:
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
On kind, Metrics Server usually can't verify the kubelet certificates. For a local demo only, add --kubelet-insecure-tls to its container args with kubectl -n kube-system edit deployment metrics-server.
# hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: web
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: web
minReplicas: 3
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60
kubectl apply -f hpa.yaml
kubectl get hpa web -w
When average CPU goes above 60% of the requested 100m, the HPA adds pods (up to 10). When load drops, it scales back down after a stabilization window. The official HPA walkthrough includes a load generator if you want to watch it happen.
Step 6: Rolling update and rollback
Change the image and Kubernetes replaces pods one at a time. Because of maxUnavailable: 0, it only removes an old pod once a new one passes its readiness probe.
kubectl set image deployment/web web=nginx:1.29
kubectl rollout status deployment/web
kubectl rollout history deployment/web
If the new version misbehaves:
kubectl rollout undo deployment/web
In practice, make the change in deployment.yaml and kubectl apply it (or let a GitOps tool such as Argo CD or Flux apply it from Git), so the file stays the source of truth.
Step 7: Scheduled work with a CronJob
Batch tasks such as reports, cleanups and backups get the same scheduling and retry machinery:
# cronjob.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
name: nightly-report
spec:
schedule: "0 3 * * *"
concurrencyPolicy: Forbid
jobTemplate:
spec:
backoffLimit: 2
template:
spec:
restartPolicy: OnFailure
containers:
- name: report
image: busybox:1.36
command: ["sh", "-c", "date; echo generating report"]
Trigger it once without waiting for 3 a.m.: kubectl create job --from=cronjob/nightly-report test-run.
Step 8: Getting traffic in from outside
For production traffic, Kubernetes' recommended API is now the Gateway API, which replaces the feature-frozen Ingress API. This matters if you followed older tutorials: the popular ingress-nginx controller was retired in March 2026 and no longer gets security fixes (Kubernetes blog). With a Gateway controller installed (Envoy Gateway, Cilium, Istio, or your cloud's), routing to the Service looks like this:
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: web
spec:
parentRefs:
- name: my-gateway
hostnames: ["app.example.com"]
rules:
- backendRefs:
- name: web
port: 80
Clean up the demo with kind delete cluster --name demo.
What happens behind the scenes
When you run kubectl apply, the API server validates the object and stores it in etcd. Controllers in the controller manager (Deployment, ReplicaSet, HPA, CronJob) watch for differences between desired and actual state and create or delete objects to close the gap. The scheduler places new pods on nodes with enough requested capacity, and the kubelet on each node starts the containers and runs the probes. Each component does one job, and together they produce the self-healing, scaling behavior above (Kubernetes components).
When Kubernetes is the wrong tool
Kubernetes adds real operational work: upgrades every few months (each minor version gets about 14 months of patches), networking, storage, security hardening and monitoring. For one or two services, a platform as a service or a managed container service is usually cheaper in engineering time. See free Heroku alternatives and DevHunt's hosting tools. If you do run Kubernetes, harden your images and cluster from the start. Our STIG container hardening guide covers that side, and DevHunt's DevOps category lists newer deployment tools.
FAQ
What is orchestration in Kubernetes?
It's the automated management of containers across a cluster: placing them on nodes, restarting failed ones, scaling replicas, routing traffic and rolling out new versions. You declare the desired state in YAML, and Kubernetes controllers keep reconciling the cluster to match.
What are examples of container orchestration tools?
Kubernetes (including managed versions such as Amazon EKS, Google GKE and Azure AKS), Docker Swarm, HashiCorp Nomad and Amazon ECS. Kubernetes is the most widely used.
Is Docker an orchestration tool?
Docker on its own builds and runs containers on a single host. Docker Swarm mode adds basic orchestration across hosts. Kubernetes doesn't need Docker: it runs containers through a CRI runtime such as containerd, though images built with Docker work fine.
What's the difference between a Deployment and a ReplicaSet?
A ReplicaSet keeps a fixed number of identical pods running. A Deployment manages ReplicaSets for you and adds rolling updates, rollout history and rollback. You almost never create ReplicaSets directly.
Which Kubernetes version should I use?
As of October 2026, Kubernetes 1.37 is the newest release, and 1.36 and 1.35 still get patches. Managed providers usually trail upstream by a version or two, so use the newest version your provider offers.