Skip to main content
LadeStack LogoLadeStack
DevOps & CI/CD 6 min read October 27, 2024

Building Zero-Downtime Deployment Pipelines: Blue-Green, Canary, and Database Migrations

Engineering zero-downtime deployment pipelines: blue-green and canary rollouts, traffic shifting, graceful shutdown, health probes, and expand-contract database migrations.

Zero-Downtime Deployment Pipeline Architecture

1. Zero-Downtime Deployments as an Engineering Target

In enterprise software delivery, “zero downtime” is an operational target bounded by physics, network topology, and distributed consensus—not a turnkey vendor guarantee. Transient packet loss during routing table propagation, uncoordinated database locks, and abruptly terminated in-flight TCP connections can create perceived outages even while deployment tools report green health checks.

Achieving high-availability upgrades requires eliminating single points of failure across three distinct layers:

  1. Network & Ingress: Seamless traffic shifting and dynamic routing updates without dropping established TCP sessions.
  2. Compute Runtime: Pod lifecycle coordination, graceful shutdown signal handling, and decoupled health probes.
  3. Data Tier: Backward-compatible database schema evolution that tolerates concurrent old and new code paths.
                                  Edge Ingress / Load Balancer
                                 +----------------------------+
                                 | Dynamic Traffic Controller |
                                 +--------------+-------------+
                                                |
                        +-----------------------+-----------------------+
                        | (90% Production)                              | (10% Canary)
                        v                                               v
         +-----------------------------+                 +-----------------------------+
         |     Active Version (v1)     |                 |     Canary Version (v2)     |
         |  - Connection Draining      |                 |  - Metrics Analysis         |
         |  - Pod PreStop Hooks        |                 |  - Error Budget Check       |
         +--------------+--------------+                 +--------------+--------------+
                        |                                               |
                        +-----------------------+-----------------------+
                                                |
                                                v
                                 +----------------------------+
                                 |  Dual-Compatible Database  |
                                 |  Expand-Contract Schema    |
                                 +----------------------------+

2. Deployment Strategies: Trade-Off Analysis

Selecting an appropriate deployment topology requires balancing resource costs, blast radius, and migration complexity:

Strategy Resource Overhead Blast Radius Rollback Latency Complexity
Rolling Update Low (+25% surge default) Medium (all traffic reaches new pods eventually) Moderate (must roll backwards) Low (Kubernetes native)
Blue-Green High (200% capacity during cutover) High (instant all-or-nothing switch) Fast (instant router switch back) Medium (environment duplication)
Canary Analysis Moderate (+10–20% capacity) Minimal (staged 1% -> 5% -> 25% -> 100%) Fast (drain canary traffic immediately) High (requires progressive delivery controllers like Argo Rollouts / Flagger)

3. Graceful Pod Lifecycle and Health Checks

A frequent cause of deployment errors during Kubernetes rollouts is premature process termination or prematurely directing traffic to uninitialized workloads.

The Pod Termination Sequence

When a pod is terminated during a rolling update:

  1. The Pod is set to Terminating state and removed from Service endpoints.
  2. The preStop hook executes concurrently with endpoint propagation across kube-proxy nodes.
  3. If no preStop hook or sleep delay exists, the application receives SIGTERM before edge load balancers remove the pod IP, generating 502 Bad Gateway errors.
  4. The application stops accepting new requests, completes active in-flight requests within terminationGracePeriodSeconds, and exits.
  5. If the process does not terminate within the grace period, the kernel issues SIGKILL.
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-processing-service
  namespace: production
spec:
  replicas: 6
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 0   # Never drop below minimum target capacity
  template:
    spec:
      terminationGracePeriodSeconds: 60
      containers:
        - name: order-api
          image: ghcr.io/ladestack/order-service:v2.4.1
          lifecycle:
            preStop:
              exec:
                # Allow 15 seconds for iptables/ipvs and external ingress to update routing tables
                command: ["/bin/sh", "-c", "sleep 15"]
          ports:
            - containerPort: 8080
          readinessProbe:
            httpGet:
              path: /health/ready
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 5
            failureThreshold: 2
          livenessProbe:
            httpGet:
              path: /health/live
              port: 8080
            initialDelaySeconds: 15
            periodSeconds: 10
            failureThreshold: 3

Application-Level Signal Handling in Node.js / Go

Applications must intercept SIGTERM and drain active socket connections gracefully:

import http from 'http';

const server = http.createServer(app);
let isShuttingDown = false;

// Health check returns 503 as soon as SIGTERM is received
app.get('/health/ready', (req, res) => {
  if (isShuttingDown) {
    return res.status(503).json({ status: 'draining' });
  }
  res.status(200).json({ status: 'ready' });
});

process.on('SIGTERM', () => {
  console.log('SIGTERM received. Draining connections...');
  isShuttingDown = true;

  // Stop accepting new TCP connections
  server.close(() => {
    console.log('Closed all HTTP connections. Exiting process.');
    process.exit(0);
  });

  // Force shutdown after 40 seconds if connections refuse to close
  setTimeout(() => {
    console.error('Graceful shutdown timeout exceeded. Forcefully killing process.');
    process.exit(1);
  }, 40000);
});

4. Database Schema Migrations: The Expand-Contract Pattern

Deploying stateless containers is trivial compared to stateful relational database migrations. Running destructive schema migrations (e.g., dropping columns, renaming columns, changing data types) while old application instances are still processing requests instantly breaks production.

The Expand-Contract (Parallel Run) Pattern ensures zero database downtime across multiple deployment phases:

Step 1: Expand Phase
--------------------
Original State:  Table [users] -> (id, name)
Target State:    Split into (first_name, last_name)

Action:
1. Add nullable columns `first_name` and `last_name` to [users].
2. Deploy database migration.
3. Deploy Application v1.1: Reads `name`, writes to BOTH `name` AND `first_name`/`last_name`.
4. Run background asynchronous backfill for historical rows.

Step 2: Transition Phase
------------------------
Action:
1. Deploy Application v2.0: Reads from `first_name`/`last_name`, writes to both.
2. Verify all instances run v2.0 without errors.

Step 3: Contract Phase
----------------------
Action:
1. Deploy Application v2.1: Stops writing to legacy `name` column.
2. Drop legacy `name` column via database migration.

At no point in this sequence is a locking table migration applied that would fail either the older or newer running service version.


5. Automated Rollback and Canary Metric Analysis

A pipeline is only as reliable as its automated failure detection. Rather than relying on manual operator approvals, modern continuous delivery systems link automated canary analysis directly to real-time telemetry metrics.

Canary Rollout with Argo Rollouts and Prometheus Metrics

apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: order-service-rollout
spec:
  replicas: 10
  strategy:
    canary:
      analysis:
        templates:
          - templateName: success-rate-check
        args:
          - name: service-name
            value: order-service
      steps:
        - setWeight: 5
        - pause: { duration: 5m }
        - setWeight: 20
        - pause: { duration: 10m }
        - setWeight: 50
        - pause: { duration: 10m }
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
  name: success-rate-check
spec:
  metrics:
    - name: success-rate
      interval: 30s
      successCondition: result[0] >= 0.995   # 99.5% HTTP success rate floor
      failureLimit: 3                       # Abort and roll back on 3 consecutive failures
      provider:
        prometheus:
          address: http://prometheus.monitoring.svc:9090
          query: |
            sum(rate(http_requests_total{service="order-service", status!~"5.*"}[1m]))
            /
            sum(rate(http_requests_total{service="order-service"}[1m]))

If the canary pod generates unexpected 5xx responses or breaches latency thresholds during the 5-minute observation window, Argo Rollouts automatically restores 100% traffic to the stable blue version, preventing widespread user impact.


Summary Deployment Verification Checklist

  • Zero-downtime rolling update strategy configured with maxUnavailable: 0.
  • PreStop sleep hooks implemented to allow edge routing synchronization.
  • Application intercepts SIGTERM and drains active connections before exit.
  • Decoupled readiness probes prevent premature ingress traffic routing.
  • Expand-contract database pattern applied for all schema migrations.
  • Feature flags utilized to decouple code deployment from feature release.
  • Automated canary telemetry analysis configured with strict failure limits.
  • Automated rollback triggers established on error budget exhaustion.

More Engineering Articles