Ravindra BagaleCourses & study guides मराठी

Chapter 67: Docker Swarm vs Kubernetes: the Same App on Both, Side by Side

67.3 The same app on Kubernetes: kind cluster, Deployment, StatefulSet, Secret, scaling and rollouts

Why: the problem

In 67.2 we ran the orders API and Postgres on Swarm and wrote down every operation and timing. Now we do exactly the same work on Kubernetes, with the same three image versions, the same placeholder password and the same traffic loop. A fair comparison needs identical inputs; only the orchestrator changes. Why kind? It gives us a three-node Kubernetes cluster on one laptop in about a minute, the same way the Docker-in-Docker nodes gave us a three-node Swarm. On AWS the same YAML would run on Amazon EKS; on Google Cloud on GKE. That portability across every major cloud is the first big difference you will notice: Swarm has no managed service from AWS, Google or Azure, while each of them sells managed Kubernetes. What do we create? Where Swarm used one stack file with two services, Kubernetes uses separate objects with one job each. A Secret holds the database password. A StatefulSet runs Postgres with a volumeClaimTemplate, so its data lives on a PersistentVolume that Kubernetes can attach wherever the pod lands, instead of pinning the database to one node by hostname. A headless Service gives Postgres a stable DNS name. A Deployment runs the orders API with a readiness probe, resource requests and limits, and a rolling update strategy of maxSurge 1 and maxUnavailable 0, the Kubernetes twin of start-first with parallelism 1. A NodePort Service exposes it on every node, the twin of the routing mesh. How do we run it? We create the kind cluster with a port mapping, load the three images into the nodes, create the Secret from the command line, write the YAML with printf, apply it, scale to six, roll out 1.1.0 under the same traffic loop, roll out the broken 1.2.0 and see what Kubernetes does, which is not the same as Swarm, and roll back with kubectl rollout undo. Keep the timings next to your Swarm numbers.

What: every term in plain English

  • kind — Kubernetes in Docker: each node is a container, ideal for labs and CI.
  • Deployment — Manages ReplicaSets and rolling updates for stateless pods.
  • StatefulSet — Stable names and per-pod volumes for databases.
  • volumeClaimTemplate — Creates one PVC per StatefulSet pod.
  • Headless Service — clusterIP None; gives each pod a DNS name.
  • NodePort — Opens the same port on every node, like Swarm's routing mesh.
  • readinessProbe — Pod receives traffic only after it passes.
  • maxSurge / maxUnavailable — How many extra or missing pods are allowed during a rollout.
  • progressDeadlineSeconds — Marks a rollout as failed if it stalls; it does not roll back by itself.
  • kubectl rollout undo — Returns the Deployment to the previous ReplicaSet.
Create a three-node kind cluster with port 30080 mapped. Build and load the three image versions. Create the db-password Secret. Write postgres.yaml and orders.yaml with printf. Apply and wait for readiness. Call the API through NodePort. Scale to six.
The same app as Kubernetes objects

How: step by step

  1. Create a three-node kind cluster with port 30080 mapped. Cluster up.
  2. Build and load the three image versions. Images on nodes.
  3. Create the db-password Secret. Secret stored.
  4. Write postgres.yaml and orders.yaml with printf. Manifests.
  5. Apply and wait for readiness. Pods ready.
  6. Call the API through NodePort. Version printed.
  7. Scale to six. Six pods.
  8. Roll out 1.1.0 under traffic. No failures.
  9. Roll out the broken tag. Rollout stalls.
  10. Undo and clean up. Back to 1.1.0.

Write your two sets of numbers into one small table before reading 67.4: time to create the cluster, time to deploy, time to scale to six, rollout time for six replicas, failed requests during the rollout and what happened on the broken release. In our runs Swarm won on cluster creation and on automatic rollback, Kubernetes won on rollout speed and on storage, and both had zero failed requests. That is the real lesson: for the basic job of keeping a web app running, both tools are good. The difference shows up in everything around the basic job, such as autoscaling, policy, observability, managed services and the huge library of Helm charts and operators, which is why companies like Zomato, PhonePe and Razorpay standardised on Kubernetes.

Example 1: Cluster, images and Secret

Goal. Cluster, images and Secret.

prep.sh

printf '%s\n' 'kind: Cluster' 'apiVersion: kind.x-k8s.io/v1alpha4' 'nodes:' \
 '- role: control-plane' '  extraPortMappings: [{containerPort: 30080, hostPort: 30080}]' \
 '- role: worker' '- role: worker' > kind-ch67.yaml
time kind create cluster --name ch67 --config kind-ch67.yaml
for v in 1.0.0 1.1.0 1.2.0-broken; do
  printf 'FROM nginx:1.27-alpine\nRUN echo %s > /usr/share/nginx/html/version\n' $v | docker build -q -t orders:$v -
  kind load docker-image orders:$v --name ch67
done
kubectl create namespace shop
kubectl -n shop create secret generic db-password --from-literal=password=REPLACE_WITH_DB_PASSWORD
kubectl get nodes
kubectl -n shop get secret db-password -o jsonpath='{.data}' ; echo

What each line does

  • extraPortMappings — Forwards laptop port 30080 to the NodePort inside the kind node.
  • time kind create — About 60 seconds, compared with roughly 10 seconds for the Swarm init and joins.
  • kind load docker-image — Copies the image into every node, so no registry is needed in this lab.
  • create secret generic — Placeholder value; base64 encoded in etcd, so enable encryption at rest in production.
  • jsonpath .data — Shows that a Secret is only base64, unlike Swarm which never prints the value.

Green output

$ bash prep.sh
Creating cluster "ch67" ...
 ✓ Ensuring node image (kindest/node:v1.34.0)
 ✓ Starting control-plane
 ✓ Joining worker nodes
real    0m58.9s
Image: "orders:1.0.0" with ID "sha256:9f1e..." not yet present on node "ch67-worker", loading...
namespace/shop created
secret/db-password created
NAME                 STATUS   ROLES           VERSION
ch67-control-plane   Ready    control-plane   v1.34.0
ch67-worker          Ready    none            v1.34.0
ch67-worker2         Ready    none            v1.34.0
{"password":"UkVQTEFDRV9XSVRIX0RCX1BBU1NXT1JE"}

Expected result. The cluster took about a minute against seconds for Swarm, and the Secret is only base64 encoded by default. Those are two honest points on Swarm's side of the table.

Example 2: Postgres StatefulSet and orders Deployment

Goal. Postgres StatefulSet and orders Deployment.

k8s.sh

mkdir -p k8s
printf '%s\n' \
 'apiVersion: v1' 'kind: Service' 'metadata: {name: db, namespace: shop}' \
 'spec: {clusterIP: None, selector: {app: db}, ports: [{port: 5432}]}' '---' \
 'apiVersion: apps/v1' 'kind: StatefulSet' 'metadata: {name: db, namespace: shop}' 'spec:' \
 '  serviceName: db' '  replicas: 1' '  selector: {matchLabels: {app: db}}' '  template:' \
 '    metadata: {labels: {app: db}}' '    spec:' '      containers:' \
 '      - name: postgres' '        image: postgres:17-alpine' \
 '        env: [{name: POSTGRES_PASSWORD, valueFrom: {secretKeyRef: {name: db-password, key: password}}}]' \
 '        volumeMounts: [{name: data, mountPath: /var/lib/postgresql/data}]' \
 '  volumeClaimTemplates:' '  - metadata: {name: data}' \
 '    spec: {accessModes: [ReadWriteOnce], resources: {requests: {storage: 1Gi}}}' > k8s/postgres.yaml
printf '%s\n' \
 'apiVersion: apps/v1' 'kind: Deployment' 'metadata: {name: orders, namespace: shop}' 'spec:' \
 '  replicas: 3' '  strategy: {type: RollingUpdate, rollingUpdate: {maxSurge: 1, maxUnavailable: 0}}' \
 '  progressDeadlineSeconds: 60' '  selector: {matchLabels: {app: orders}}' '  template:' \
 '    metadata: {labels: {app: orders}}' '    spec:' '      containers:' \
 '      - name: orders' '        image: orders:1.0.0' '        ports: [{containerPort: 80}]' \
 '        env: [{name: DB_HOST, value: db}]' \
 '        readinessProbe: {httpGet: {path: /version, port: 80}, periodSeconds: 5}' \
 '        resources: {requests: {cpu: 100m, memory: 64Mi}, limits: {cpu: 500m, memory: 128Mi}}' '---' \
 'apiVersion: v1' 'kind: Service' 'metadata: {name: orders, namespace: shop}' \
 'spec: {type: NodePort, selector: {app: orders}, ports: [{port: 80, nodePort: 30080}]}' > k8s/orders.yaml
kubectl apply -f k8s/
kubectl -n shop rollout status deploy/orders
kubectl -n shop get pods,pvc -o wide
curl -s localhost:30080/version

What each line does

  • clusterIP: None — Headless Service; db resolves to the Postgres pod.
  • volumeClaimTemplates — Kubernetes creates PVC data-db-0 and binds a volume; no node pinning.
  • secretKeyRef — Injects the Secret as an env variable; you can also mount it as a file like Swarm.
  • maxSurge 1, maxUnavailable 0 — Same behaviour as Swarm's start-first with parallelism 1.
  • nodePort 30080 — Every node answers on 30080, like the routing mesh.

Green output

$ bash k8s.sh
service/db created
statefulset.apps/db created
deployment.apps/orders created
service/orders created
deployment "orders" successfully rolled out
NAME                         READY   STATUS    NODE
pod/db-0                     1/1     Running   ch67-worker2
pod/orders-7c9d8b6f5-4kq2m   1/1     Running   ch67-worker
pod/orders-7c9d8b6f5-8hx1p   1/1     Running   ch67-worker2
pod/orders-7c9d8b6f5-z2n7c   1/1     Running   ch67-worker
NAME                                STATUS   VOLUME          CAPACITY
persistentvolumeclaim/data-db-0     Bound    pvc-6b1f...     1Gi
1.0.0

Expected result. Four objects instead of one stack file, but the database volume is now a real claim that Kubernetes manages, and on EKS it would be an EBS volume that follows the pod between nodes in one zone.

Example 3: Scale, roll out under traffic, failed rollout and undo

Goal. Scale, roll out under traffic, failed rollout and undo.

ops.sh

kubectl -n shop scale deploy/orders --replicas=6
( while true; do curl -s -m 1 localhost:30080/version || echo FAIL; sleep 0.2; done ) > hits.txt &
LOOP=$!
time kubectl -n shop set image deploy/orders orders=orders:1.1.0
kubectl -n shop rollout status deploy/orders
kill $LOOP; sort hits.txt | uniq -c
kubectl -n shop set image deploy/orders orders=orders:1.2.0-broken
kubectl -n shop patch deploy orders --type=json -p '[{"op":"replace","path":"/spec/template/spec/containers/0/readinessProbe/httpGet/path","value":"/missing"}]'
kubectl -n shop rollout status deploy/orders --timeout=90s || true
kubectl -n shop get pods
kubectl -n shop rollout undo deploy/orders
kubectl -n shop rollout status deploy/orders
curl -s localhost:30080/version

What each line does

  • kubectl scale — Six pods in a few seconds, like docker service scale.
  • set image — Starts the rolling update; time it against the Swarm rollout.
  • Broken readiness path — New pods never become ready, so with maxUnavailable 0 the old pods keep serving.
  • progressDeadlineSeconds — After 60 seconds the rollout is marked failed, but Kubernetes does not roll back on its own.
  • rollout undo — Manual rollback; Argo Rollouts or Flagger automate it, as in Chapter 64.

Green output

$ bash ops.sh
deployment.apps/orders scaled
deployment.apps/orders image updated
real    0m0.21s
deployment "orders" successfully rolled out
    241 1.0.0
    263 1.1.0
deployment.apps/orders image updated
deployment.apps/orders patched
error: deployment "orders" exceeded its progress deadline
NAME                      READY   STATUS    RESTARTS
orders-5d8f7c9b4-2lmq9    0/1     Running   0
orders-6f7d9c8b5-4nq2x    1/1     Running   0
... 5 more 1/1 pods on 1.1.0 ...
deployment.apps/orders rolled back
deployment "orders" successfully rolled out
1.1.0

Expected result. Zero failures again. The rollout of six pods took about 30 seconds because Kubernetes moves as soon as each pod is ready, with no fixed delay. The broken release stalled safely but needed a human or a tool to roll back, where Swarm rolled back by itself.

Every command, step by step (kind lab with kubectl)

  1. Cluster

    kind create cluster --name ch67 --config kind-ch67.yaml
    

    What it does: Three nodes.

    Joining worker nodes
    
  2. Load

    kind load docker-image orders:1.0.0 --name ch67
    

    What it does: Image.

    loading...
    
  3. Secret

    kubectl -n shop create secret generic db-password --from-literal=password=REPLACE_WITH_DB_PASSWORD
    

    What it does: Secret.

    secret/db-password created
    
  4. Apply

    kubectl apply -f k8s/
    

    What it does: Objects.

    deployment.apps/orders created
    
  5. Status

    kubectl -n shop rollout status deploy/orders
    

    What it does: Ready.

    successfully rolled out
    
  6. Call

    curl -s localhost:30080/version
    

    What it does: API.

    1.0.0
    
  7. Scale

    kubectl -n shop scale deploy/orders --replicas=6
    

    What it does: Six.

    scaled
    
  8. Update

    kubectl -n shop set image deploy/orders orders=orders:1.1.0
    

    What it does: Rolling.

    image updated
    
  9. Undo

    kubectl -n shop rollout undo deploy/orders
    

    What it does: Rollback.

    rolled back
    
  10. Delete

    kind delete cluster --name ch67
    

    What it does: Clean.

    Deleting cluster "ch67" ...
    

When it is red 1

ErrImageNeverPull or ImagePullBackOff

Cause. Image not loaded into kind or not in a registry.

Fix, step by step

  1. kind load docker-image, or push to a reachable registry.

When it is red 2

pod db-0 Pending: unbound immediate PersistentVolumeClaims

Cause. No default StorageClass.

Fix, step by step

  1. kind ships local-path; on EKS install the EBS CSI driver.

When it is red 3

curl: (7) Failed to connect to localhost port 30080

Cause. Port mapping missing in the kind config.

Fix, step by step

  1. Recreate the cluster with extraPortMappings.

When it is red 4

exceeded its progress deadline

Cause. New pods never became ready.

Fix, step by step

  1. kubectl describe pod, fix, or rollout undo.

When it is red 5

secret db-password not found

Cause. Secret in another namespace.

Fix, step by step

  1. Create it with -n shop.

Practice tasks

Easy Task 1: Cluster

Why: Create.

What to do: Create the kind cluster.

How:
  1. Port 30080.
Solution
kubectl get nodes

Easy Task 2: Secret

Why: Create.

What to do: Create db-password.

How:
  1. Placeholder.
Solution
kubectl -n shop get secret

Easy Task 3: Apply

Why: Deploy.

What to do: Apply both files.

How:
  1. Ready.
Solution
kubectl -n shop get pods

Medium Task 4: Scale

Why: Six.

What to do: Scale to six.

How:
  1. kubectl scale.
Solution
kubectl -n shop get deploy

Medium Task 5: Rollout

Why: Traffic.

What to do: Update under the loop.

How:
  1. No FAIL.
Solution
sort hits.txt | uniq -c

Medium Task 6: Broken

Why: Stall.

What to do: Deploy the broken tag.

How:
  1. Deadline.
Solution
kubectl -n shop rollout status deploy/orders

Hard Task 7: Undo

Why: Back.

What to do: Roll back.

How:
  1. 1.1.0.
Solution
curl -s localhost:30080/version

Hard Task 8: Urban Company

Why: Main task.

What to do: Booking API on kind.

How:
  1. Timings table.
Solution
kubectl -n shop get all

Homework: try at home

For Urban Company's booking API, deploy the same app you put on Swarm in 67.2 to a kind cluster with a StatefulSet database and a Deployment with maxUnavailable 0, roll out under the same traffic loop and record rollout time and failures next to your Swarm numbers.

Recap

  • The same images and password run unchanged on Kubernetes.
  • Kubernetes splits the stack into Secret, StatefulSet, Deployment and Services.
  • StatefulSets with PVCs handle database storage without pinning to a node.
  • maxSurge 1 and maxUnavailable 0 match Swarm's start-first rollouts.
  • Kubernetes stalls a bad rollout safely but does not roll back alone.

FAQ

Q. Why did the Kubernetes rollout finish faster?

Swarm waited the fixed 5 second delay plus monitor time per task; Kubernetes moved on as soon as each new pod was ready. Set minReadySeconds to add a similar safety wait.

Q. Can I get automatic rollback on Kubernetes?

Yes, with Argo Rollouts or Flagger, which watch metrics and abort a release, or with Helm's --rollback-on-failure flag in Helm 4.

Q. Should the Secret be an env variable or a file?

Files are safer, because env values can leak into crash dumps and child processes. Mount the Secret as a volume and set POSTGRES_PASSWORD_FILE, exactly as in the Swarm stack.

Q. Would this run on EKS unchanged?

Yes, apart from the image location (push to ECR) and the Service type, where you would use LoadBalancer or an Ingress instead of NodePort.

Interview questions

  1. How do you map a Swarm stack to Kubernetes objects?

    Model answer: Services become Deployments or StatefulSets plus Services; secrets become Secrets; configs become ConfigMaps; volumes become PVCs.

  2. What does maxUnavailable 0 guarantee?

    Model answer: Capacity never drops below the desired replicas during a rollout.

  3. Why a StatefulSet for Postgres?

    Model answer: Stable identity and a dedicated PVC per pod.

  4. What happens when progressDeadlineSeconds is exceeded?

    Model answer: The Deployment condition becomes Progressing False; old pods keep serving; rollback is manual.

Quick quiz

Q1. Which object runs Postgres with its own volume?

StatefulSet.

Q2. What is Swarm's routing mesh twin?

NodePort Service.

Q3. Which settings equal start-first?

maxSurge 1, maxUnavailable 0.

Q4. Does a failed Deployment roll back alone?

No.

Q5. Which command rolls back?

kubectl rollout undo.

Samajla ka?