Definition
- RollingUpdate – the default Kubernetes Deployment strategy that gradually replaces old pods with new pods a few at a time, keeping some old pods serving traffic while new pods start, achieving zero downtime at the cost of briefly running two versions and extra pods.
- Recreate – an alternative strategy that first terminates every old pod and only then starts new pods, guaranteeing a downtime window but ensuring exactly one version runs at any moment.
- maxSurge – a RollingUpdate parameter that sets how many extra pods above the desired replica count may exist temporarily during a roll.
- maxUnavailable – a RollingUpdate parameter that sets how many pods may be missing below the desired count during a roll.
- Readiness probe – the health check that must pass before Kubernetes routes traffic to a new pod, making it the gatekeeper of safe rolling updates.
Imagine National Bank of India needs to replace all the tellers at a branch with newly trained tellers. The rolling approach sends in one new teller while the old ones keep serving customers, then swaps them one by one, so the queue never stops – but for a while there are more tellers than desks. The recreate approach closes the branch, sends everyone home, brings in the new tellers, and reopens – nobody is served during the swap, but there is never any confusion about who is on duty.
Understanding the Concepts
What is a Kubernetes Deployment strategy?
A Kubernetes Deployment manages a set of identical pods through a ReplicaSet. When you change the pod template, for example by pointing at a new container image version, the Deployment must replace the old pods with new ones. The strategy.type field in the Deployment spec decides how that replacement happens. Kubernetes offers exactly two built-in strategies: RollingUpdate and Recreate.
# deployment.yaml for National Bank Transfer Service
apiVersion: apps/v1
kind: Deployment
metadata:
name: transfer-service
namespace: banking
spec:
replicas: 4
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
selector:
matchLabels:
app: transfer-service
template:
metadata:
labels:
app: transfer-service
spec:
containers:
- name: transfer-service
image: registry.nbi.com/transfer-service:2.5.0
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
How does RollingUpdate work?
With RollingUpdate, which is the default, Kubernetes replaces pods incrementally. Two parameters shape the roll. maxSurge says how many pods above the desired replica count may exist temporarily. maxUnavailable says how many pods below the desired count may be missing at once.
The practical effect is that Kubernetes spins up new pods, waits for them to pass their readiness probe, shifts traffic to them, and only then tears down old pods. Because old pods keep serving until new pods are healthy, users experience no interruption.
# RollingUpdate with zero unavailable pods
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 25% # up to 25% extra pods allowed
maxUnavailable: 0 # all desired pods always available
# Deploy the new version
kubectl set image deployment/transfer-service \
transfer-service=registry.nbi.com/transfer-service:2.6.0 \
-n banking
# Watch the rollout progress
kubectl rollout status deployment/transfer-service -n banking
Waiting for deployment "transfer-service" rollout to finish:
3 of 4 updated replicas are available...
4 of 4 updated replicas are available...
deployment "transfer-service" successfully rolled out
How does Recreate work?
With Recreate, Kubernetes scales the old ReplicaSet all the way down to zero, waits for every old pod to terminate, and only then scales the new ReplicaSet up. During the gap between the last old pod dying and the first new pod becoming ready, the service has no running pods and requests fail.
# Recreate strategy for the Settlement Scheduler
apiVersion: apps/v1
kind: Deployment
metadata:
name: settlement-scheduler
namespace: banking
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: settlement-scheduler
template:
metadata:
labels:
app: settlement-scheduler
spec:
containers:
- name: settlement-scheduler
image: registry.nbi.com/settlement-scheduler:3.1.0
env:
- name: SPRING_PROFILES_ACTIVE
value: production
# Deploy with Recreate -- the old pod terminates before new one starts
kubectl set image deployment/settlement-scheduler \
settlement-scheduler=registry.nbi.com/settlement-scheduler:3.2.0 \
-n banking
kubectl rollout status deployment/settlement-scheduler -n banking
Waiting for deployment "settlement-scheduler" rollout to finish:
0 of 1 updated replicas are available...
1 of 1 updated replicas are available...
deployment "settlement-scheduler" successfully rolled out
How is RollingUpdate different from Recreate?
| Aspect | RollingUpdate | Recreate |
|---|---|---|
| Downtime | Zero (when probes are correct) | Guaranteed gap |
| Two versions coexist | Yes, during the roll | Never |
| Rollback speed | Slow (pod-by-pod reverse) | Slow (full redeploy) |
| Extra resource cost | maxSurge pods temporarily | None |
| Best for | Stateless, backward-compatible services | Singletons, incompatible upgrades |
| Schema requirement | Expand-and-contract | Any migration is safe |
| Default | Yes | No |
What are the rules and constraints for RollingUpdate?
RollingUpdate only delivers genuine zero downtime if three things are true. First, readiness probes must accurately report when a pod can serve traffic, otherwise Kubernetes shifts traffic to a pod that is not ready and users see errors. Second, the application must shut down gracefully, draining in-flight requests when it receives the termination signal, which requires honoring preStop hooks and the termination grace period. Third, database schema changes must follow the expand-and-contract pattern.
# Graceful shutdown configuration
spec:
terminationGracePeriodSeconds: 60
containers:
- name: transfer-service
lifecycle:
preStop:
exec:
command: ["sh", "-c", "sleep 5"]
package com.bank.config;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class GracefulShutdownConfig {
@Bean
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> graceful() {
// Spring Boot 2.3+ supports graceful shutdown natively
// application.yml: server.shutdown=graceful
return factory -> factory.addConnectorCustomizers(
connector -> connector.setProperty("relaxedQueryChars", "[]")
);
}
}
What is the expand-and-contract pattern for schema changes?
When two versions of the application run simultaneously during a rolling update, database schema changes must be backward compatible. The expand-and-contract pattern splits a breaking change into three steps.
-- Step 1: EXPAND -- add the new column (both old and new code work)
ALTER TABLE transfers ADD COLUMN beneficiary_iban VARCHAR(34);
-- Step 2: MIGRATE -- new code writes to both columns, backfill old rows
UPDATE transfers
SET beneficiary_iban = beneficiary_account
WHERE beneficiary_iban IS NULL;
-- Step 3: CONTRACT -- remove the old column (only after old code is gone)
ALTER TABLE transfers DROP COLUMN beneficiary_account;
Because every intermediate state is readable and writable by whichever versions are live, a rolling update never breaks, and a rollback never lands on a schema the old code cannot handle.
When should you use RollingUpdate?
Choose RollingUpdate for stateless, backward-compatible services where downtime is unacceptable. At National Bank of India, the Transfer API serves customers around the clock and must never go offline for a deployment.
# Verify zero-downtime during a rolling update
# Run a load test while deploying
kubectl set image deployment/transfer-service \
transfer-service=registry.nbi.com/transfer-service:2.7.0 \
-n banking &
# In another terminal, watch for errors
while true; do
curl -s -o /dev/null -w "%{http_code}" http://transfer.nbi.com/health
echo
sleep 0.5
done
200
200
200
200
... (no 5xx errors if probes and graceful shutdown are correct)
When should you use Recreate?
Choose Recreate when two versions absolutely cannot coexist, when the service holds an exclusive resource, or when a short maintenance window is acceptable. A singleton like the Settlement Scheduler that sweeps and settles trades once a night would double-settle if two instances ran together.
# CronJob alternative for batch singletons
apiVersion: batch/v1
kind: CronJob
metadata:
name: nightly-settlement
namespace: banking
spec:
schedule: "0 2 * * *"
concurrencyPolicy: Forbid
jobTemplate:
spec:
template:
spec:
containers:
- name: settlement
image: registry.nbi.com/settlement:3.2.0
restartPolicy: OnFailure
How do you roll back a failed deployment?
Kubernetes keeps a history of Deployment revisions. If a new version fails readiness probes or causes errors, you can roll back to the previous revision instantly.
# Check rollout history
kubectl rollout history deployment/transfer-service -n banking
# Roll back to the previous version
kubectl rollout undo deployment/transfer-service -n banking
# Roll back to a specific revision
kubectl rollout undo deployment/transfer-service \
--to-revision=3 -n banking
deployment.apps/transfer-service rolled back
What are common mistakes?
The most common mistake is using RollingUpdate without proper readiness probes. If the readiness probe is missing or always returns healthy, Kubernetes routes traffic to pods that are still starting, and users see errors during every deploy.
# WRONG: no readiness probe -- traffic hits pods before they are ready
spec:
containers:
- name: transfer-service
image: registry.nbi.com/transfer-service:2.6.0
ports:
- containerPort: 8080
# No readinessProbe defined!
# CORRECT: readiness probe gates traffic
spec:
containers:
- name: transfer-service
image: registry.nbi.com/transfer-service:2.6.0
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
failureThreshold: 3
What should a senior engineer know?
A senior engineer knows that RollingUpdate is only one piece of zero-downtime deployment. The full picture includes readiness probes that honestly reflect warm-up, graceful shutdown that drains in-flight requests, expand-and-contract for database migrations, and pod disruption budgets that prevent too many pods from going down at once during node maintenance.
# PodDisruptionBudget -- at least 3 of 4 pods must stay available
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: transfer-service-pdb
namespace: banking
spec:
minAvailable: 3
selector:
matchLabels:
app: transfer-service
Visual Overview
RollingUpdate (zero downtime)
─────────────────────────────────────────────
Time 0 [OLD-1] [OLD-2] [OLD-3] [OLD-4]
▼ maxSurge=1
Time 1 [OLD-1] [OLD-2] [OLD-3] [OLD-4] [NEW-1 starting...]
▼ readiness passes
Time 2 [OLD-2] [OLD-3] [OLD-4] [NEW-1] [NEW-2 starting...]
Time 3 [OLD-3] [OLD-4] [NEW-1] [NEW-2] [NEW-3 starting...]
Time 4 [OLD-4] [NEW-1] [NEW-2] [NEW-3] [NEW-4 starting...]
Time 5 [NEW-1] [NEW-2] [NEW-3] [NEW-4] ← done, zero gap
Recreate (guaranteed downtime)
─────────────────────────────────────────────
Time 0 [OLD-1] [OLD-2] [OLD-3] [OLD-4]
▼ all terminated
Time 1 [ ...no pods running... ] ← DOWNTIME
▼ new pods start
Time 2 [NEW-1 starting...] [NEW-2 starting...]
Time 3 [NEW-1] [NEW-2] [NEW-3] [NEW-4] ← done
Key Takeaway
Kubernetes gives a Deployment two built-in strategies for replacing old pods with new ones. RollingUpdate, the default, replaces pods incrementally: it brings up new pods, waits for them to pass their readiness probe, shifts traffic, and only then removes old pods. Because old pods keep serving until the new ones are healthy, users see no interruption. The cost is that two versions briefly coexist, and you temporarily consume extra resources for the surge pods. Recreate does the opposite: it terminates every old pod first and only then starts the new ones, guaranteeing a downtime window but ensuring two versions never overlap.
Choosing between them comes down to whether the application can tolerate two versions running together and whether you can tolerate downtime. For a stateless, customer-facing service like the Transfer API at National Bank of India, where downtime is unacceptable, RollingUpdate is the clear choice. For a singleton like the Settlement Scheduler that must never run as two instances, Recreate is the safe choice.
The nuance that separates a senior answer is that RollingUpdate only delivers genuine zero downtime when three things hold: the readiness probe honestly reflects warm-up, the application shuts down gracefully and drains in-flight requests, and any database schema change follows the expand-and-contract pattern so both versions can read and write safely during the overlap.
Remember one sentence: RollingUpdate keeps the service alive by overlapping versions, Recreate keeps the service pure by never overlapping them, and the choice depends on whether two versions can safely coexist.
---