Back to Blog
Kubernetes
Deployments
DevOps
Reliability

Zero-Downtime Deployments on Kubernetes: A Practical Guide

2026-03-16 · 8 min read · Serhii Mazurok

Your users should never know you deployed. Here's how to make that happen on Kubernetes.


The Default Isn't Zero-Downtime

A basic Kubernetes Deployment with RollingUpdate strategy will update pods one at a time. But without proper configuration, you'll still drop requests during rollouts. Here's why:

1. Pod receives SIGTERM but is still in the Service endpoint list 2. Readiness probe hasn't been configured or is too slow 3. Application doesn't handle graceful shutdown - connections are cut mid-request 4. Load balancer hasn't removed the pod from its pool yet

Let's fix each of these.


Step 1: Readiness Probes

The readiness probe tells Kubernetes when a pod is ready to receive traffic. Without it, traffic is sent to pods that haven't finished starting.

readinessProbe:
  httpGet:
    path: /healthz
    port: 8080
  initialDelaySeconds: 5
  periodSeconds: 5
  failureThreshold: 3
  successThreshold: 1
Rules:
  • /healthz should return 200 only when the app is truly ready (DB connected, caches warmed)
  • initialDelaySeconds should cover your app's startup time
  • Don't make the probe too expensive - it runs every periodSeconds

Step 2: Graceful Shutdown

When Kubernetes sends SIGTERM, your app should:

1. Stop accepting new connections 2. Finish processing in-flight requests 3. Close database connections cleanly 4. Exit

// Go example
srv := &http.Server{Addr: ":8080"}

go func() { sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, syscall.SIGTERM) <-sigCh

ctx, cancel := context.WithTimeout(context.Background(), 25*time.Second) defer cancel() srv.Shutdown(ctx) }()

Set terminationGracePeriodSeconds to match:

spec:
  terminationGracePeriodSeconds: 30

Step 3: Pre-Stop Hook

There's a race condition: Kubernetes sends SIGTERM and removes the pod from endpoints simultaneously. The kube-proxy / ingress controller may not have updated yet, so traffic can still arrive at a terminating pod.

The fix: add a short delay before your app starts shutting down.

lifecycle:
  preStop:
    exec:
      command: ["sleep", "5"]

This gives the network 5 seconds to drain the pod from all load balancer pools before the app starts shutting down.


Step 4: Rolling Update Strategy

strategy:
  type: RollingUpdate
  rollingUpdate:
    maxUnavailable: 0      # never reduce below desired count
    maxSurge: 1            # add 1 extra pod during rollout
maxUnavailable: 0 is critical - it ensures the old pod stays running until the new pod passes its readiness probe.

Step 5: PodDisruptionBudget

Protect against voluntary disruptions (node drains, cluster upgrades):

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: app-pdb
spec:
  minAvailable: 1        # at least 1 pod must be running at all times
  selector:
    matchLabels:
      app: my-app

For larger deployments, use maxUnavailable: 25% instead of minAvailable to allow faster rollouts while maintaining capacity.


The Complete Configuration

apiVersion: apps/v1
kind: Deployment
metadata:
  name: app
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 0
      maxSurge: 1
  template:
    spec:
      terminationGracePeriodSeconds: 30
      containers:
        - name: app
          image: app:v1.2.3
          ports:
            - containerPort: 8080
          readinessProbe:
            httpGet:
              path: /healthz
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 5
          livenessProbe:
            httpGet:
              path: /healthz
              port: 8080
            initialDelaySeconds: 15
            periodSeconds: 10
          lifecycle:
            preStop:
              exec:
                command: ["sleep", "5"]

apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: app-pdb spec: minAvailable: 1 selector: matchLabels: app: app

Checklist

CheckWhy
Readiness probe configuredTraffic only goes to ready pods
maxUnavailable: 0Old pod stays up until new one is ready
preStop: sleep 5Drains pod from LB before shutdown
Graceful shutdown handles SIGTERMIn-flight requests complete
terminationGracePeriodSeconds ≥ shutdown timeK8s waits long enough
PodDisruptionBudget setNode drains don't kill all pods

Segla configures these patterns automatically for every deployment. Focus on your code, we handle the rollout. Get started →

The modern platform for cloud-native application delivery. From code to production in minutes.

Connect

© 2026 Segla. All rights reserved.

Made with in Ukraine 🇺🇦