Kubernetes for Express Developers: A Beginner-Friendly Guide
Are you a Node.js + Express developer who understands Docker and Linux, but feels overwhelmed by Kubernetes? This guide is for you. We are going to transition from standard Docker to Kubernetes step-by-step.
The Application
We are deploying a very simple Node.js + Express application packaged into a Docker image called myapp:v1.
const express = require("express");
const app = express();
app.get("/hi", (req, res) => {
res.send("Hi from Express");
});
app.listen(3000, () => console.log("Server running"));
Part 1: Without Kubernetes
Traditionally, you would run Docker containers directly on Linux servers with a Load Balancer in front. graph TD LB[Load Balancer] –> S1[Server 1\nmyapp:v1] LB –> S2[Server 2\nmyapp:v1]
The Problem:
If Server 2 crashes, the container dies. Manual intervention is required to provision a new server, install Docker, run the container, and update the load balancer to point to the new IP.
Part 2: Introducing Kubernetes
Kubernetes is an orchestration tool that automates the deployment, scaling, and management of containers.
- Control Plane: The “brain” making global decisions.
- Worker Nodes: Linux servers running your apps.
- Pod: A wrapper around your container (smallest unit in K8s).
- Deployment: Instructions (e.g., “Keep 2 copies running”).
- Service: Internal load balancer for your Pods.
Part 3 & 4: Deploying & Request Flow
When deployed on Kubernetes with replicas: 2, traffic flows from the browser, hits the K8s Service, and is load-balanced to the Pods across Worker Nodes. graph TD B[Browser] –>|GET /hi| S{Kubernetes Service} S –> P1[Worker 1\nPod-1] S –> P2[Worker 2\nPod-2]
Part 5: When a Node Crashes
If Worker 2 crashes, Kubernetes automatically fixes it without manual intervention.
- Desired State: 2 Pods
- Current State: 1 Pod (Node crashed)
- Reconciliation Loop: Control plane notices the mismatch.
- Scheduler: Automatically creates a new Pod on a healthy Node to reach the desired state.
Part 6: Scaling
Scaling is just changing a number in your Deployment manifest. Set replicas: 5 and the Scheduler spreads pods out: graph TD subgraph Worker 1 P1[Pod-1] P3[Pod-3] P5[Pod-5] end subgraph Worker 2 P2[Pod-2] P4[Pod-4] end
Part 7: Physical vs Logical Relationships
Understanding how the hardware maps to Kubernetes concepts: graph TD PS[Physical Server] –> WN[Worker Node\nK8s Managed] WN –> P1[Pod-1\nLogical Wrapper] P1 –> C1[Container\nmyapp:v1] WN –> P2[Pod-2] P2 –> C2[Container\nmyapp:v1]
Part 8: Real-World Analogies
- Docker Container: A Shipping Container (holds your cargo).
- Kubernetes Pod: A Truck Trailer (holds the shipping container).
- Deployment: A Fleet Manager (“I need 5 trucks running!”).
- Service: A Company Switchboard (Routes calls to available operators).
Part 9: Docker vs Kubernetes
| Concept | Docker | Kubernetes |
|---|---|---|
| Build Image | docker build | Still docker build (or similar) |
| Run App | docker run | Deployment (Asks cluster to run Pods) |
| Smallest Unit | Container | Pod (Contains the container) |
| Infrastructure | Docker Host | Worker Node (Part of a Cluster) |
| Orchestration | Docker Compose | Kubernetes (Multi-machine cluster) |
Part 10: Complete Request Lifecycle
Tracing a request from the user to your Express code: flowchart TD A[Browser] –>|URL| B{Service} B –>|Load balances| C[Pod\nNode-2, Port 3000] C –>|Forwards| D[Container] D –>|app.get| E[Express Route] E –>|Response| A
Part 11: Autoscaling (HPA)
Kubernetes can automatically scale your application up and down based on traffic using a Horizontal Pod Autoscaler (HPA).
To make autoscaling work, you need three things:
- Metrics Server: A cluster add-on that actively monitors pod CPU and memory usage.
- Resource Requests: Your
deployment.yamlmust define baseline CPU/Memory requests so Kubernetes knows what “100%” utilization means. - HPA Manifest: A configuration telling Kubernetes the minimum and maximum pods allowed (e.g., min: 2, max: 10).
Pro Tip: By keeping the HPA in a separate file (e.g.,
k8s/hpa/hpa.yaml), you make autoscaling modular. You can deploy the base app locally without it, and selectively apply the HPA only in Production!
Bonus: Running This Locally
If you have Docker and Minikube installed, you can try this exact setup yourself!
1. Start the cluster and build the image:
minikube start
eval $(minikube docker-env)
docker build -t myapp:v1 .
2. Deploy to Kubernetes:
kubectl apply -f k8s/deployment.yaml
kubectl apply -f k8s/service.yaml
3. Test the Load Balancer (v2)
If you update your app to return the pod name using os.hostname(), you can see Kubernetes load balancing in action!
docker build -t myapp:v2 .
kubectl set image deployment/myapp-deployment express-container=myapp:v2
Triggering the Autoscaler (Load Testing)
Want to see the autoscaler in action? Open three terminal windows to watch the magic happen:
- Terminal 1 (Watch Pods):
kubectl get pods -w - Terminal 2 (Watch HPA):
kubectl get hpa -w - Terminal 3 (Generate Load):
kubectl run -i --tty load-generator --rm --image=busybox:1.28 --restart=Never -- \
/bin/sh -c "while sleep 0.01; do wget -q -O- http://myapp-service/hi; echo ''; done"
The Cooldown Period: Node.js is incredibly fast. To force a scaling event, you might need to temporarily lower your HPA target to
10%. Once scaled up, if you stop the load generator, Kubernetes will wait for a 5-minute cooldown period before terminating the extra pods to prevent rapid “flapping”.
Stopping & Deleting the Cluster
If you are familiar with Docker Compose, Minikube commands will feel very similar:
minikube stop(Likedocker compose stop): Safely shuts down the virtual machine, but saves all your data, deployments, and services. You can runminikube starttomorrow and pick up exactly where you left off.minikube delete(Likedocker compose down -v): Completely destroys the cluster and wipes the database. You will get a 100% clean slate the next time you start it, and you will need to run yourkubectl applycommands again.