Zum Inhalt springen

Blue-Green-Deployments: Releases ohne Ausfallzeit

Blue-Green-Deployments nehmen Releases die Nervosität: zwei identische Produktionsumgebungen, sofortiges Umschalten des Traffics — so setzt du sie um.

3 Min. Lesezeit
Blue-Green-Deployment-Diagramm, das die Traffic-Umschaltung zwischen zwei identischen Umgebungen zeigt

Traditionelle Deployments haben einen beunruhigenden Moment: Die alte Version ist offline, die neue startet gerade, und du hoffst, dass nichts kaputtgeht. Blue-Green-Deployments beseitigen diese Lücke, indem zwei identische Produktionsumgebungen vorgehalten werden. Eine davon bedient jederzeit den Live-Traffic (blue), während die andere die neue Version ausführt (green). Sobald die neue Version verifiziert ist, schaltest du den Traffic um. Wenn etwas schiefgeht, schaltest du zurück.

Die Architektur

Die Grundidee ist einfach: Ein Router (Load Balancer, DNS oder Reverse Proxy) leitet den gesamten Traffic an eine Umgebung weiter. Die andere Umgebung läuft im Leerlauf und ist bereit, das neue Deployment zu empfangen.

nginxnginx
# ❌ Single environment — downtime during deployment
upstream api {
  server api-v1:3000;
  # During deployment: stop v1, start v2, hope for the best
}
 
# ✅ Blue-green with nginx — traffic switch via config reload
# blue is currently live
upstream api_blue {
  server api-blue-1:3000;
  server api-blue-2:3000;
  server api-blue-3:3000;
}
 
upstream api_green {
  server api-green-1:3000;
  server api-green-2:3000;
  server api-green-3:3000;
}
 
# Point to the active environment
upstream api_active {
  server api-blue-1:3000;
  server api-blue-2:3000;
  server api-blue-3:3000;
}
 
server {
  listen 80;
  location / {
    proxy_pass http://api_active;
  }
}

AWS-Implementierung mit ALB

Auf AWS bieten Application Load Balancers einen sauberen Blue-Green-Mechanismus durch das Umschalten von Target Groups.

tstypescript
import {
  ElasticLoadBalancingV2Client,
  ModifyListenerCommand,
  DescribeTargetHealthCommand,
} from "@aws-sdk/client-elastic-load-balancing-v2";
 
const elbClient = new ElasticLoadBalancingV2Client({});
 
async function switchTraffic(
  listenerArn: string,
  targetGroupArn: string
): Promise<void> {
  // Verify all targets in the new group are healthy
  const healthResponse = await elbClient.send(
    new DescribeTargetHealthCommand({ TargetGroupArn: targetGroupArn })
  );
 
  const unhealthy = healthResponse.TargetHealthDescriptions?.filter(
    (t) => t.TargetHealth?.State !== "healthy"
  );
 
  if (unhealthy && unhealthy.length > 0) {
    throw new Error(
      `Cannot switch: ${unhealthy.length} unhealthy targets in new group`
    );
  }
 
  // Switch the listener to the new target group
  await elbClient.send(
    new ModifyListenerCommand({
      ListenerArn: listenerArn,
      DefaultActions: [
        {
          Type: "forward",
          TargetGroupArn: targetGroupArn,
        },
      ],
    })
  );
 
  console.log(`Traffic switched to ${targetGroupArn}`);
}

Verifikation vor der Umschaltung

Traffic ohne Verifikation umzuschalten macht das ganze Konzept zunichte. Führe automatisierte Prüfungen gegen die Green-Umgebung aus, bevor du sie für Nutzer freigibst.

tstypescript
// ❌ Deploy and switch immediately
async function deploy() {
  await deployToGreen(newVersion);
  await switchTraffic(greenTargetGroup); // No verification!
}
 
// ✅ Deploy, verify, then switch
async function deploy() {
  await deployToGreen(newVersion);
 
  // Run smoke tests against the green environment directly
  const smokeTestResults = await runSmokeTests({
    baseUrl: "http://green-internal.example.com",
    tests: [
      { name: "health", method: "GET", path: "/health", expectedStatus: 200 },
      { name: "auth", method: "POST", path: "/api/auth/verify", expectedStatus: 200 },
      { name: "list-items", method: "GET", path: "/api/items?limit=1", expectedStatus: 200 },
    ],
    timeoutMs: 5000,
  });
 
  if (smokeTestResults.failures.length > 0) {
    console.error("Smoke tests failed:", smokeTestResults.failures);
    throw new Error("Aborting deployment: smoke tests failed on green environment");
  }
 
  // Verify response schema matches expectations
  const schemaValid = await validateResponseSchema({
    baseUrl: "http://green-internal.example.com",
    endpoints: ["/api/items", "/api/users/me"],
  });
 
  if (!schemaValid) {
    throw new Error("Aborting deployment: API schema validation failed");
  }
 
  await switchTraffic(greenTargetGroup);
  console.log("Deployment complete — traffic now on green");
}

Überlegungen zur Datenbank

Der schwierigste Teil von Blue-Green-Deployments ist die Datenbank. Beide Umgebungen müssen mit denselben Daten arbeiten, was bedeutet, dass Datenbankänderungen abwärtskompatibel sein müssen.

tstypescript
// ❌ Breaking migration — blue can't work with green's schema
// Migration: RENAME COLUMN users.name TO users.full_name
// Blue environment immediately breaks because it still queries "name"
 
// ✅ Two-phase migration — compatible with both versions
// Phase 1: Add new column (deploy with green)
// Migration: ALTER TABLE users ADD COLUMN full_name VARCHAR(255);
// Backfill: UPDATE users SET full_name = name WHERE full_name IS NULL;
// Green reads from full_name, Blue continues reading from name
 
// Phase 2: Remove old column (only after blue is decommissioned)
// Migration: ALTER TABLE users DROP COLUMN name;
// This runs only after confirming no environment reads "name"
 
interface MigrationPhase {
  step: number;
  migration: string;
  compatible_versions: string[];
  safe_to_rollback: boolean;
}
 
const migrationPlan: MigrationPhase[] = [
  {
    step: 1,
    migration: "ALTER TABLE users ADD COLUMN full_name VARCHAR(255)",
    compatible_versions: ["v1.2.0", "v1.3.0"],
    safe_to_rollback: true,
  },
  {
    step: 2,
    migration: "UPDATE users SET full_name = name WHERE full_name IS NULL",
    compatible_versions: ["v1.2.0", "v1.3.0"],
    safe_to_rollback: true,
  },
  {
    step: 3,
    migration: "ALTER TABLE users DROP COLUMN name",
    compatible_versions: ["v1.3.0"],  // Only after v1.2.0 is gone
    safe_to_rollback: false,
  },
];

Rollback-Strategie

Der Hauptvorteil von Blue-Green ist das sofortige Rollback. Wenn nach der Umschaltung etwas schiefgeht, leitest du den Traffic einfach zurück an die vorherige Umgebung.

shbash
# Current state: green is live (v1.3.0), blue still has v1.2.0
# Problem detected in v1.3.0
 
# Instant rollback — switch back to blue
aws elbv2 modify-listener \
  --listener-arn "$LISTENER_ARN" \
  --default-actions "Type=forward,TargetGroupArn=$BLUE_TARGET_GROUP"
 
# Rollback complete in seconds — no redeployment needed
# Blue is still running v1.2.0 exactly as it was
 
# Investigate, fix, and redeploy to green when ready

Kostenüberlegungen

Zwei identische Umgebungen verdoppeln die Infrastrukturkosten — allerdings nur vorübergehend. Nach der Verifikation kannst du die inaktive Umgebung auf Mindestkapazität herunterskalieren und vor dem nächsten Deployment wieder hochskalieren.

Die wichtigsten Erkenntnisse

  1. Blue-Green beseitigt Deployment-Ausfallzeiten — der Traffic wird sofort zwischen zwei identischen Umgebungen umgeschaltet
  2. Verifiziere immer vor der Umschaltung — führe Smoke Tests und Schema-Validierung gegen die Green-Umgebung aus
  3. Datenbankmigrationen müssen abwärtskompatibel sein — beide Umgebungen teilen sich während der Übergangsphase dieselbe Datenbank
  4. Rollbacks sind sofort möglich — leite den Traffic zurück an die vorherige Umgebung, ohne neu zu deployen
  5. Nutze Zwei-Phasen-Migrationen — füge neue Strukturen zuerst hinzu und entferne alte erst, wenn die alte Version abgeschaltet ist
  6. Halte Kosten im Griff, indem du inaktive Umgebungen herunterskalierst — du brauchst nicht auf beiden Umgebungen gleichzeitig volle Kapazität
Wilfredo Rujel

Wilfredo Rujel

Full-Stack-Softwareentwickler

Diesen Beitrag teilenX