Java

Zero-Downtime Blue-Green Deployments with Istio, Kubernetes, and Spring Boot

Learn zero-downtime blue-green deployments with Istio, Kubernetes, and Spring Boot using production-ready configs and safer release strategies.

Zero-Downtime Blue-Green Deployments with Istio, Kubernetes, and Spring Boot

I’ve spent years watching deployments fail. Not because the code was bad, but because the handover between versions was sloppy. One second customers are happy, the next they see errors or stale data. That’s why I started digging into Blue-Green deployments with Istio and Kubernetes. If you’ve ever had a release go wrong, you know the stomach‑drop feeling. This article is my attempt to save you that pain.

Let’s walk through how to set up a Spring Boot microservice for true zero‑downtime deployments. We’ll use Kubernetes to run the pods and Istio to control traffic precisely. I’ll include the exact configuration files I’ve used in production.

Why not just rolling updates? Rolling updates gradually replace pods, but they share the same database schema and can cause short bursts of errors if the new version isn’t backward compatible. Blue‑Green keeps both environments isolated until you’re sure the new version works. Istio gives us a single switch to move traffic—no messy kubectl updates.


Preparing the Spring Boot Application

Before touching Kubernetes, your application must handle shutdown gracefully. Without this, a SIGTERM kills connections mid‑flight.

# application.yaml
server.shutdown: graceful
spring.lifecycle.timeout-per-shutdown-phase: 30s

I also add a custom readiness indicator that prevents traffic from reaching the pod until caches are warm and the database schema is validated.

@Component
public class DeploymentReadinessIndicator implements HealthIndicator {

    private final AtomicBoolean ready = new AtomicBoolean(false);

    @Override
    public Health health() {
        return ready.get()
            ? Health.up().withDetail("stage", "LIVE").build()
            : Health.down().withDetail("stage", "WARMING").build();
    }

    public void markReady() {
        ready.set(true);
    }
}

Why does this matter? Because a pod that responds to health checks but hasn’t finished warming up will serve slow or incorrect results. Your users feel that lag. The readiness probe in Kubernetes reads from /actuator/health/readiness; our custom indicator keeps it down until we say so.


Blue and Green Deployments

We’ll create two identical Kubernetes Deployments: one named order-service-blue, another order-service-green. Both are behind a single Kubernetes Service that uses a generic label app: order-service. Istio will handle the actual routing – not the Service.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service-blue
spec:
  replicas: 3
  selector:
    matchLabels:
      app: order-service
      version: blue
  template:
    metadata:
      labels:
        app: order-service
        version: blue
    spec:
      containers:
      - name: order-service
        image: myrepo/order-service:1.2
        readinessProbe:
          httpGet:
            path: /actuator/health/readiness
            port: 8080
          initialDelaySeconds: 10
        livenessProbe:
          httpGet:
            path: /actuator/health/liveness
            port: 8080
        env:
        - name: SPRING_PROFILES_ACTIVE
          value: "blue"

The green deployment is identical, only with version: green and an updated image tag.

Question: How does the new version get validated in isolation before seeing any traffic?
We use Istio’s DestinationRule and VirtualService to route a small percentage of requests to the green environment – often 0% initially.


Istio Traffic Management

Istio gives us an IngressGateway that catches all external requests. Then a VirtualService decides which subset of the service receives the traffic.

apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: order-service-destination
spec:
  host: order-service
  subsets:
  - name: blue
    labels:
      version: blue
  - name: green
    labels:
      version: green
---
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: order-service
spec:
  hosts:
  - order-service
  http:
  - route:
    - destination:
        host: order-service
        subset: blue
      weight: 100
    - destination:
        host: order-service
        subset: green
      weight: 0

At deployment time, I first deploy the green pods (using kubectl apply -f deployment-green.yaml). They start, run their readiness probes, and wait. No traffic hits them because the VirtualService sends everything to blue.

I then run automated smoke tests against the green pods using an internal Istio ServiceEntry or by curling the cluster IP with proper headers. Only when those tests pass do I change the VirtualService weight to 100% for green and 0% for blue.

# After promotion
    weight: 100  # green
    weight: 0    # blue

The switch happens instantaneously. Existing connections on blue finish gracefully because blue’s pods still exist; they won’t receive new requests. After a minute, I delete the blue Deployment.


Database Migrations – The Tricky Part

Blue‑Green deployments often break database compatibility. If the new version changes the schema, the old version (blue) may stop working if it reads or writes altered tables.

My approach: use Flyway with forward‑only, backward‑compatible migrations. For example, instead of dropping a column, mark it as deprecated and leave it for two releases. New code writes to both old and new columns. Blue (old) ignores the new column.

I run migrations before deploying the green pods. That way both environments can run side‑by‑side until the switch.

-- V2__add_new_column.sql
ALTER TABLE orders ADD COLUMN status_v2 VARCHAR(20);  -- not used by old code
UPDATE orders SET status_v2 = status;  -- copy data

Question: What if the migration fails?
The green pods will fail their readiness checks (because Spring Boot will fail to start if Flyway detects a broken migration). The blue pods continue serving traffic unaffected.


Automating the Whole Thing with GitHub Actions

Here’s a simplified pipeline I’ve used. It builds the Docker image, runs integration tests, deploys green, runs smoke tests, and then promotes.

name: Blue-Green Deploy
on:
  push:
    branches: [main]
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - name: Build and push Docker image
      run: |
        docker build -t myrepo/order-service:${{ github.sha }} .
        docker push myrepo/order-service:${{ github.sha }}
    - name: Deploy Green
      run: |
        kubectl set image deployment/order-service-green order-service=myrepo/order-service:${{ github.sha }}
        kubectl rollout status deployment/order-service-green
    - name: Smoke Tests
      run: |
        curl --fail -H "X-Canary: green" http://istio-ingress/health
    - name: Promote Green
      run: |
        kubectl apply -f k8s/istio/virtual-service-green-100.yaml
    - name: Scale down Blue
      run: |
        kubectl scale deployment order-service-blue --replicas=0

A few things to notice: The smoke test uses a header X-Canary: green that I configured in the VirtualService to route specifically to green for test purposes. That gives me a fully isolated endpoint to validate without affecting real users.


Common Mistakes I’ve Made

  • Session state: If your app uses in‑memory sessions, switching from blue to green drops all user sessions. Use a distributed session store (Redis) or sticky sessions via Istio’s cookie‑based affinity.
  • Cache invalidation: Green starts with an empty cache. That first burst of traffic after promotion can be slow. Pre‑warm caches via a startup hook or use a shared cache like Redis.
  • Monitoring blind spots: Make sure your alerts cover “error budget” and not just “pod status”. A 5% error rate after a switch is still measured in seconds of user pain.

Conclusion

Blue‑Green with Istio isn’t just about new features – it’s about keeping users happy while you improve the system. The tools are straightforward, but the discipline of testing the new environment in isolation is what saves you. I’ve seen teams skip the smoke test once and then spend three hours rolling back. Don’t be that team.

If you found this useful, I’d appreciate a like, a share, or a comment about your own deployment war stories. What’s the one thing you always check before cutting traffic to a new version? Let’s learn from each other.


As a best-selling author, I invite you to explore my books on Amazon. Don’t forget to follow me on Medium and show your support. Thank you! Your support means the world!


101 Books

101 Books is an AI-driven publishing company co-founded by author Aarav Joshi. By leveraging advanced AI technology, we keep our publishing costs incredibly low—some books are priced as low as $4—making quality knowledge accessible to everyone.

Check out our book Golang Clean Code available on Amazon.

Stay tuned for updates and exciting news. When shopping for books, search for Aarav Joshi to find more of our titles. Use the provided link to enjoy special discounts!


📘 Checkout my latest ebook for free on my channel!
Be sure to like, share, comment, and subscribe to the channel!


Our Creations

Be sure to check out our creations:

Investor Central | Investor Central Spanish | Investor Central German | Smart Living | Epochs & Echoes | Puzzling Mysteries | Hindutva | Elite Dev | JS Schools


We are on Medium

Tech Koala Insights | Epochs & Echoes World | Investor Central Medium | Puzzling Mysteries Medium | Science & Epochs Medium | Modern Hindutva

// Similar Posts

Keep Reading