DEV Community

Aisalkyn Aidarova
Aisalkyn Aidarova

Posted on

project: deployment strategy types in k8s

Common commands (use for every strategy)

kubectl get deploy,pods,rs,svc
kubectl rollout status deploy/<name>
kubectl rollout history deploy/<name>
kubectl describe pod <pod>
kubectl logs <pod>
kubectl get events --sort-by=.lastTimestamp | tail -30
Enter fullscreen mode Exit fullscreen mode

If you need a quick request loop to see behavior:

URL="<your service url>"
while true; do curl -s --max-time 1 "$URL" || echo "DOWN"; echo; sleep 0.3; done
Enter fullscreen mode Exit fullscreen mode

1) RollingUpdate (most used)

Why it’s used most

  • It is the default Kubernetes Deployment behavior.
  • It provides continuous availability when configured correctly.
  • It’s simple to operate (built-in rollback/history).

Behavior

  • Old and new Pods exist at the same time.
  • Replacement happens in steps controlled by:

    • maxSurge (extra Pods allowed above desired replicas)
    • maxUnavailable (Pods allowed to be unavailable during rollout)
  • If readiness is correct, traffic only goes to ready Pods.

(Deployment + Service)

rolling.yaml

apiVersion: apps/v1
kind: Deployment
metadata: {name: rolling}
spec:
  replicas: 3
  selector: {matchLabels: {app: rolling}}
  strategy:
    type: RollingUpdate
    rollingUpdate: {maxSurge: 1, maxUnavailable: 1}
  template:
    metadata: {labels: {app: rolling}}
    spec:
      containers:
      - name: app
        image: hashicorp/http-echo:0.2.3
        args: ["-listen=:5678","-text=ROLLING v1"]
        ports: [{containerPort: 5678}]
---
apiVersion: v1
kind: Service
metadata: {name: rolling-svc}
spec:
  selector: {app: rolling}
  ports: [{port: 80, targetPort: 5678}]
  type: NodePort
Enter fullscreen mode Exit fullscreen mode

How to use (demo flow)

kubectl apply -f rolling.yaml
kubectl get pods -w
kubectl rollout status deploy/rolling
Enter fullscreen mode Exit fullscreen mode

Get URL:

minikube service rolling-svc --url
Enter fullscreen mode Exit fullscreen mode

Update “version” (triggers rollout):

kubectl patch deploy rolling --type='json' -p='[
  {"op":"replace","path":"/spec/template/spec/containers/0/args/1","value":"-text=ROLLING v2"}
]'
kubectl rollout status deploy/rolling
Enter fullscreen mode Exit fullscreen mode

Troubleshooting (production style)

Symptom: rollout stuck / never completes

  1. Check rollout:
kubectl rollout status deploy/rolling
kubectl get rs
kubectl get pods
Enter fullscreen mode Exit fullscreen mode
  1. Find failing Pod and inspect:
kubectl describe pod <new-pod>
kubectl logs <new-pod>
kubectl get events --sort-by=.lastTimestamp | tail -30
Enter fullscreen mode Exit fullscreen mode

Common causes:

  • Image pull error (ErrImagePull, ImagePullBackOff)
  • App crashes (CrashLoopBackOff)
  • Readiness would fail (in real apps); traffic may route to bad pods if probes missing

Fast rollback

kubectl rollout undo deploy/rolling
kubectl rollout status deploy/rolling
Enter fullscreen mode Exit fullscreen mode

Operational note
In production you nearly always add readiness checks, but they are omitted here to keep YAML minimal.


2) Recreate (rarely used)

Why it’s rarely used

  • It introduces downtime: old Pods stop before new ones are ready.
  • Most services can’t tolerate that, so it’s avoided.

When it’s used

  • Workloads that cannot run two versions simultaneously (hard constraints).
  • Maintenance-window deployments.
  • Certain stateful/legacy patterns (still better handled with careful rolling + app-level compatibility when possible).

Behavior

  • All old Pods terminate first.
  • Then new Pods start.
  • A period of “no endpoints” is expected.

recreate.yaml

apiVersion: apps/v1
kind: Deployment
metadata: {name: recreate}
spec:
  replicas: 3
  strategy: {type: Recreate}
  selector: {matchLabels: {app: recreate}}
  template:
    metadata: {labels: {app: recreate}}
    spec:
      containers:
      - name: app
        image: hashicorp/http-echo:0.2.3
        args: ["-listen=:5678","-text=RECREATE v1"]
        ports: [{containerPort: 5678}]
---
apiVersion: v1
kind: Service
metadata: {name: recreate-svc}
spec:
  selector: {app: recreate}
  ports: [{port: 80, targetPort: 5678}]
  type: NodePort
Enter fullscreen mode Exit fullscreen mode

How to use

kubectl apply -f recreate.yaml
URL=$(minikube service recreate-svc --url)
Enter fullscreen mode Exit fullscreen mode

Trigger change:

kubectl patch deploy recreate --type='json' -p='[
  {"op":"replace","path":"/spec/template/spec/containers/0/args/1","value":"-text=RECREATE v2"}
]'
Enter fullscreen mode Exit fullscreen mode

Troubleshooting

Symptom: brief outage during deploy

  • That’s the expected behavior. The correct response is strategy choice + maintenance window + comms.

Symptom: service never returns

  • Same checks as rolling:
kubectl get pods
kubectl describe pod <pod>
kubectl logs <pod>
kubectl get events --sort-by=.lastTimestamp | tail -30
Enter fullscreen mode Exit fullscreen mode

3) Blue–Green (common for critical releases)

Why it’s used

  • Very safe cutover: new environment runs fully before traffic switch.
  • Instant rollback: switch traffic back immediately.
  • Great when you need deterministic release control.

Tradeoffs

  • You run two environments (cost/capacity).
  • You must manage data migrations carefully; schema changes can still be risky.

Behavior

  • Both “blue” and “green” run side-by-side.
  • A Service points to one of them.
  • Switching the Service selector flips traffic immediately.

Smallest YAML (blue+green+svc)

blue-green.yaml

apiVersion: apps/v1
kind: Deployment
metadata: {name: blue}
spec:
  replicas: 2
  selector: {matchLabels: {app: bg, color: blue}}
  template:
    metadata: {labels: {app: bg, color: blue}}
    spec:
      containers:
      - name: app
        image: hashicorp/http-echo:0.2.3
        args: ["-listen=:5678","-text=BLUE v1"]
        ports: [{containerPort: 5678}]
---
apiVersion: apps/v1
kind: Deployment
metadata: {name: green}
spec:
  replicas: 2
  selector: {matchLabels: {app: bg, color: green}}
  template:
    metadata: {labels: {app: bg, color: green}}
    spec:
      containers:
      - name: app
        image: hashicorp/http-echo:0.2.3
        args: ["-listen=:5678","-text=GREEN v2"]
        ports: [{containerPort: 5678}]
---
apiVersion: v1
kind: Service
metadata: {name: bg-svc}
spec:
  selector: {app: bg, color: blue}
  ports: [{port: 80, targetPort: 5678}]
  type: NodePort
Enter fullscreen mode Exit fullscreen mode

How to use

kubectl apply -f blue-green.yaml
URL=$(minikube service bg-svc --url)
curl -s $URL && echo
Enter fullscreen mode Exit fullscreen mode

Switch to green:

kubectl patch svc bg-svc -p '{"spec":{"selector":{"app":"bg","color":"green"}}}'
curl -s $URL && echo
Enter fullscreen mode Exit fullscreen mode

Rollback to blue:

kubectl patch svc bg-svc -p '{"spec":{"selector":{"app":"bg","color":"blue"}}}'
Enter fullscreen mode Exit fullscreen mode

Troubleshooting

Symptom: after switch you still hit old version

  1. Check Service selector and endpoints:
kubectl get svc bg-svc -o yaml | sed -n '1,80p'
kubectl get endpoints bg-svc -o wide
Enter fullscreen mode Exit fullscreen mode
  1. Verify labels match:
kubectl get pods --show-labels | grep bg
Enter fullscreen mode Exit fullscreen mode

Most common root cause:

  • selector typo or missing label

Symptom: green is unhealthy

  • Validate green before switch:
kubectl rollout status deploy/green
kubectl logs deploy/green
Enter fullscreen mode Exit fullscreen mode

Operational best practice:

  • Run smoke checks on green (health endpoint, critical flows) before switching.

4) Canary (highly used in mature production setups)

Why it’s used

  • Reduces blast radius: a small portion of traffic sees the new version first.
  • Lets you validate with real traffic + monitoring before full rollout.

Tradeoffs

  • Requires observability (metrics/alerts) and clear abort criteria.
  • True weighted traffic splitting is usually done via ingress/service mesh; here we demonstrate the core idea with replicas.

Behavior

  • Stable and canary versions run simultaneously.
  • Traffic is distributed roughly proportional to replica counts (not precise, but effective for demonstration).

canary.yaml

apiVersion: apps/v1
kind: Deployment
metadata: {name: stable}
spec:
  replicas: 3
  selector: {matchLabels: {app: canary, track: stable}}
  template:
    metadata: {labels: {app: canary, track: stable}}
    spec:
      containers:
      - name: app
        image: hashicorp/http-echo:0.2.3
        args: ["-listen=:5678","-text=STABLE v1"]
        ports: [{containerPort: 5678}]
---
apiVersion: apps/v1
kind: Deployment
metadata: {name: canary}
spec:
  replicas: 1
  selector: {matchLabels: {app: canary, track: canary}}
  template:
    metadata: {labels: {app: canary, track: canary}}
    spec:
      containers:
      - name: app
        image: hashicorp/http-echo:0.2.3
        args: ["-listen=:5678","-text=CANARY v2"]
        ports: [{containerPort: 5678}]
---
apiVersion: v1
kind: Service
metadata: {name: canary-svc}
spec:
  selector: {app: canary}
  ports: [{port: 80, targetPort: 5678}]
  type: NodePort
Enter fullscreen mode Exit fullscreen mode

How to use

kubectl apply -f canary.yaml
URL=$(minikube service canary-svc --url)
for i in {1..25}; do curl -s $URL; echo; done
Enter fullscreen mode Exit fullscreen mode

Promote (increase canary):

kubectl scale deploy/canary --replicas=2
Enter fullscreen mode Exit fullscreen mode

Abort (remove canary immediately):

kubectl scale deploy/canary --replicas=0
Enter fullscreen mode Exit fullscreen mode

Troubleshooting

Symptom: canary never receives traffic

  • Service selector not matching both sets:
kubectl get svc canary-svc -o yaml | grep -A3 selector
kubectl get pods --show-labels | grep canary
Enter fullscreen mode Exit fullscreen mode

Symptom: canary causing errors

  • Immediate mitigation is scaling canary to 0 while investigating:
kubectl scale deploy/canary --replicas=0
kubectl logs deploy/canary
kubectl describe pod <canary-pod>
Enter fullscreen mode Exit fullscreen mode

Operational best practice:

  • Define SLO-based abort rules (error rate, latency, saturation) and automate promotion/abort (Argo Rollouts/Flagger).

5) A/B (specialized; product-driven)

Why it’s not “default”

  • It’s primarily for experimentation (feature comparison), not just safe rollout.
  • Requires request-based routing (headers/cookies/users).
  • DevOps provides the routing/infra and guardrails.

Behavior

  • Specific requests go to version B, others to version A.
  • Commonly implemented via Ingress/service mesh.

Minimal A/B in pure Kubernetes (no ingress) isn’t truly possible

Without ingress/mesh you can’t route by headers; you can only do replica-based canary. In production, A/B is usually NGINX Ingress / Istio / Linkerd.

If you want the smallest A/B that is real, it needs ingress + annotations. I can give that minimal manifest as a separate block (it will still be longer than others because routing rules require extra objects).


Summary: what is used more vs rarely (and why)

  • Most used: RollingUpdate
    Default, reliable, simplest operationally.

  • Highly used in mature orgs: Canary
    Lower risk, progressive delivery, relies on monitoring.

  • Common for high-stakes releases: Blue–Green
    Deterministic cutover + instant rollback, higher cost.

  • Rare: Recreate
    Downtime; only used when two versions cannot coexist or downtime is acceptable.

Top comments (0)