Blog

AWS Deployment Pipelines sind Architektur

Deployment Pipelines sind Architektur, weil sie bestimmen, wie häufig, sicher und reversibel ein System verändert wird.

≈ 8 Min. Lesezeit

Diesen Beitrag anhören (11 Min.)

MP3 herunterladen

Die Zeit von einem gemergten Commit bis zu dem Moment, in dem die Änderung in Produktion wirkt, liegt bei manchen Teams unter einer Viertelstunde und bei anderen bei einem Quartal – häufig bei vergleichbarem Anwendungscode. Diese Spanne steht in keinem Architekturdiagramm. Sie entsteht dort, wo eine Änderung ihren Weg nimmt: in den Schritten zwischen Commit und Laufzeitumgebung, in den Kontrollen, die sie passieren muss, und in dem, was übrig bleibt, wenn sie zurückgenommen werden soll. Der Weg dorthin gehört damit in dieselbe Entwurfsdiskussion wie die Wahl einer Datenbank – und braucht dieselbe Begründung.

Der konkrete Anlass für diesen Text ist eine kleine GitHub Action, die nichts weiter tut, als ein AWS-CDK-Projekt zu deployen. Ein paar Zeilen YAML. Und trotzdem legt genau dieser Baustein fest, welcher Fehler früh auffällt, welcher erst in der Produktion – und wer eine Änderung an der Infrastruktur überhaupt auslösen darf.

Die Pipeline legt die Änderbarkeit fest

Architektur beantwortet die Frage, was sich unabhängig voneinander ändern lässt. Genau das ist auch die Kernfrage einer Pipeline. Ein System, das nur im Quartals-Release deploybar ist, hat eine andere Architektur als eines, das dieselbe Codebasis zwanzigmal am Tag ausrollt – selbst wenn beide identischen Anwendungscode enthalten. Die Änderbarkeit steckt nicht allein im Modul-Schnitt, sondern im Weg von einem Commit bis in die Laufzeitumgebung.

Auf AWS wird dieser Weg besonders sichtbar, seit Infrastruktur selbst Code ist. Mit dem CDK beschreibe ich Lambda-Funktionen, API-Gateways, Tabellen und Rechte in TypeScript. cdk synth übersetzt das in ein CloudFormation-Template, cdk deploy bringt es in ein Konto. Damit verschiebt sich etwas Grundlegendes: Die Frage „wie sieht meine Infrastruktur aus?“ und die Frage „wie kommt eine Änderung dorthin?“ fallen zusammen. Beide liegen im selben Repository, durchlaufen denselben Review und dieselbe Pipeline. Das habe ich in Infrastruktur als Programmiersprache ausführlicher aufgeschrieben; hier interessiert mich der Schritt danach – was passiert, wenn dieser Code automatisch ausgerollt wird.

Ein minimaler Stack sieht so aus:

import * as cdk from "@aws-cdk/core";
import * as lambda from "@aws-cdk/aws-lambda";
import * as apigw from "@aws-cdk/aws-apigateway";

export class ApiStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const handler = new lambda.Function(this, "ApiHandler", {
      runtime: lambda.Runtime.NODEJS_12_X,
      handler: "index.handler",
      code: lambda.Code.fromAsset("dist/api"),
    });

    new apigw.LambdaRestApi(this, "Api", { handler });
  }
}

Das Beispiel ist bewusst klein. Es soll keine vollständige Anwendung zeigen, sondern die Grenze markieren: Alles, was hier steht, ist ab jetzt Teil des deploybaren Artefakts. Wenn ich die Runtime ändere, ein Recht erweitere oder das API umbaue, geht das denselben Weg wie eine Codeänderung. Die spannende Entscheidung ist nicht, wie viele Constructs ich schreibe, sondern welche Kontrolle zwischen diesem Code und der Produktion steht.

Die „CDK Action“ als kleinster ehrlicher Baustein

Der einfachste Weg, dieses Deployment zu automatisieren, ist eine GitHub Action, die auf jeden Push nach main reagiert:

name: deploy
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v1
        with:
          node-version: 12
      - run: npm ci
      - run: npm test
      - run: npx cdk deploy --require-approval never
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          AWS_DEFAULT_REGION: eu-central-1

Das sieht harmlos aus, trifft aber lauter Architekturentscheidungen. on: push heißt: Jeder Merge nach main ist ein Deployment – der Trunk ist die Auslieferung, nicht bloß ein Zwischenstand. Der Schritt npm test vor cdk deploy zieht eine Qualitätsgrenze: Was rot ist, geht nicht raus. --require-approval never verzichtet auf die interaktive Rückfrage des CDK bei sicherheitsrelevanten Änderungen – bequem, aber es verschiebt die Verantwortung für IAM-Änderungen vollständig in den Review davor. Und die zwei Secrets legen fest, mit welchen Rechten diese Pipeline im Konto agiert; in der Praxis ist das der Punkt, an dem die meiste unausgesprochene Macht steckt.

Genau deshalb ist dieser kleine Baustein aufschlussreicher als jede große Referenzpipeline. Man kann ihn in fünf Minuten im Team durchgehen, und jede Zeile steht für eine bewusste oder unbewusste Entscheidung über Häufigkeit, Sicherheit und Zuständigkeit.

Der Review-Punkt liegt vor dem Deploy, nicht danach

Bei Anwendungscode reviewen wir den Diff. Bei Infrastruktur tun das viele nicht – und wundern sich, dass ein Merge nebenbei eine Security Group öffnet oder eine Tabelle ersetzt. Das CDK hat dafür ein gutes Werkzeug, das in einer Pipeline zum eigentlichen Review-Artefakt wird:

# what will actually change in the account
cdk diff ApiStack

cdk diff zeigt nicht meinen TypeScript-Diff, sondern den Unterschied zwischen dem, was im Konto steht, und dem, was ich deployen will – inklusive der IAM-Statements, die CloudFormation daraus ableitet. In einer sauberen Pipeline läuft dieser Schritt automatisch und hängt das Ergebnis an den Pull Request. Damit entscheidet ein Mensch über die Wirkung, nicht über den Quelltext. Das ist ein methodischer Unterschied mit direkter Betriebswirkung: Ein Reviewer, der sieht „diese Änderung fügt iam:PassRole hinzu“, stoppt sie, bevor sie im Konto liegt. Ein Reviewer, der nur stack.ts liest, übersieht sie oft.

So sieht der Weg als Ganzes aus:

flowchart LR
  Commit --> Build["Build & Test"]
  Build --> Synth["cdk synth<br/>CloudFormation-Template"]
  Synth --> Diff["cdk diff<br/>Review-Artefakt"]
  Diff --> Approve{"Freigabe?"}
  Approve -->|ja| Deploy["cdk deploy"]
  Approve -->|nein| Stop["Halt"]
  Deploy --> Observe["Metriken &<br/>Alarme"]
  Observe -->|Fehler| Rollback["Rollback auf<br/>vorherige Revision"]

Jeder Knoten in dieser Skizze ist eine Stellschraube. Wo die Freigabe sitzt, entscheidet über Tempo gegen Kontrolle. Ob nach Deploy überhaupt beobachtet wird, entscheidet, ob ein schlechtes Release auffällt, bevor der Kunde es meldet. Und ob es einen echten Rollback-Pfad gibt, entscheidet darüber, ob eine Änderung reversibel ist – oder nur hoffentlich richtig.

Dasselbe Artefakt durch mehrere Umgebungen

Sobald mehr als eine Umgebung im Spiel ist – Staging und Produktion, oft in getrennten AWS-Konten – wird die Pipeline zur eigentlichen Definition dessen, was „promoten“ heißt. Tragfähig ist das nur unter einer Bedingung: Dasselbe geprüfte Artefakt wandert durch die Stufen, nicht ein zweiter Build mit leicht anderen Zutaten. Beim CDK bedeutet das, dieselbe App mit unterschiedlichem Kontext zu synthetisieren.

const app = new cdk.App();

new ApiStack(app, "ApiStack-staging", {
  env: { account: "111111111111", region: "eu-central-1" },
});

new ApiStack(app, "ApiStack-prod", {
  env: { account: "222222222222", region: "eu-central-1" },
});

Damit steht die Umgebungs-Topologie im selben Repository wie der Code und durchläuft denselben Review. Der methodische Gewinn ist Vertrauen: Wenn Staging und Produktion aus einer Quelle synthetisiert werden, testet Staging tatsächlich das, was später in Produktion läuft – und nicht eine plausible Kopie, die an drei Stellen abweicht. Genau diese Abweichungen sind es, die sonst erst im teuersten Konto auffallen. Die Trennung in eigene Konten ist dabei kein Betriebsdetail, sondern eine Architekturgrenze: Ein Deploy nach Staging kann Produktion nicht versehentlich berühren, weil ihm schlicht die Rechte im anderen Konto fehlen.

Trade-offs, die man benennen muss

Die interessante Arbeit an einer Pipeline besteht nicht darin, sie zum Laufen zu bringen, sondern darin, ihre Kompromisse offenzulegen. Drei davon tauchen regelmäßig auf:

  • Auto-Deploy auf main gegen manuelles Gate. Der automatische Weg verkürzt die Durchlaufzeit drastisch, verlangt aber ein belastbares Testnetz und gute Alarme. Das Gate gibt ein Sicherheitsgefühl, sammelt in der Praxis aber Änderungen zu großen, riskanten Batches an – der häufigste Grund für seltene, angstbesetzte Releases.
  • Ein Stack gegen viele Stacks. Ein großer Stack ist einfach zu deployen, aber jede Änderung berührt alles. Getrennte Stacks für API, Daten und statisches Frontend erlauben es, das Risiko klein zu halten und Teile unabhängig auszurollen – auf Kosten von mehr Koordination an den Schnittstellen.
  • Rollback per Redeploy gegen Vorwärts-Fix. CloudFormation kann bei einem fehlgeschlagenen Deploy automatisch zurückrollen. Das trägt aber nur, solange die Änderung nicht bereits Daten migriert oder ein Schema erweitert hat. Ab da ist „einfach zurück“ eine Illusion, und man braucht additive, rückwärtskompatible Schritte statt eines Undo-Knopfs.

Keine dieser Entscheidungen ist universell richtig. Aber sie gehören in ein Architektur-Gespräch und in ein kurzes ADR, nicht in eine YAML-Datei, die jemand vor einem Jahr committet hat und die seither niemand mehr hinterfragt.

Warum das ein CTO interessieren sollte

Für die technische Leitung übersetzt sich das alles in Zahlen, die seit „Accelerate“ ein etabliertes Vokabular haben. Vier Kennzahlen beschreiben die Leistungsfähigkeit einer Auslieferung ziemlich vollständig, und alle vier werden von der Pipeline bestimmt, nicht vom Anwendungscode:

  • Deployment-Häufigkeit – wie oft geht überhaupt etwas live. Ein Auto-Deploy auf main hebt sie um Größenordnungen gegenüber einem manuellen Quartals-Gate.
  • Lead Time – wie lange von „Code fertig“ bis „in Produktion“. cdk diff im PR plus automatischer Deploy drückt das von Tagen auf Minuten.
  • Change Failure Rate – welcher Anteil der Releases erzeugt einen Fehler. Der Test-Schritt und das Diff-Review vor dem Deploy senken sie, ohne die Häufigkeit zu opfern.
  • Time to Restore – wie schnell ist der Normalzustand wieder da. Ein echter, geübter Rollback-Pfad ist hier der Unterschied zwischen Minuten und einem verlorenen Nachmittag.

Das Wichtige an diesen vier: Sie machen unterschiedliche Seiten der Lieferfähigkeit sichtbar. Kleine Änderungen begrenzen den Umfang einer Prüfung und eines möglichen Fehlers; automatisierte Checks verkürzen die Rückmeldung; ein geübter Wiederherstellungsweg senkt die Zeit bis zur Reparatur. Ob eine Pipeline diese Wirkung tatsächlich hat, zeigen erst Lead Time for Changes, Change Failure Rate und Time to Restore Service im eigenen System. Sie ist damit keine Zugabe für die Betriebsseite, sondern Teil der Architektur.

Woran ich eine Pipeline prüfe

Belastbar ist ein Pipeline-Entwurf erst, wenn auch der Fehlerpfad beschrieben ist. Die Fragen, die dabei am meisten aufdecken, zielen genau dorthin. Was passiert, wenn cdk deploy mitten im Umbau abbricht – bleibt ein halber Zustand zurück? Ist der Deploy idempotent genug, dass ein zweiter Lauf ihn heilt? Wo liegt die Grenze, ab der ein Rollback nicht mehr genügt, weil bereits Daten migriert wurden? Und ganz banal: Wer darf diese Pipeline auslösen, und ist das nachvollziehbar protokolliert?

Am häufigsten begegnet mir dabei die Überbewertung des glücklichen Pfades. Im Demo-Konto läuft alles sauber. Interessant wird es bei Teilfehlern, Rechten, die knapp nicht ausreichen, Ressourcen, die CloudFormation ersetzen statt ändern will, und bei dem einen Deploy, der um 17 Uhr am Freitag hängenbleibt. Genau dort zeigt sich, ob das Pipeline-Modell trägt – und ob „reversibel“ ein echtes Versprechen ist oder nur ein Wort in einer Präsentation.

Fazit

Die Verlockung ist, eine Pipeline als Werkzeugfrage zu behandeln: welche Action, welcher Runner, welcher Provider. Der eigentliche Gehalt liegt woanders. Eine Pipeline kodiert, wie oft ein Team seine Meinung ändern darf, wie sicher es das tut und wie leicht es einen Fehler zurücknimmt. Das sind Architektureigenschaften, und sie entstehen nicht im Toolnamen, sondern in den paar bewussten Entscheidungen dahinter – Auto-Deploy oder Gate, ein Stack oder viele, Undo oder Vorwärts-Fix. Wer diese Entscheidungen benennt und ihre Wirkung misst, kann Engpässe gezielt verändern. Wer sie einer alten YAML-Datei überlässt, trifft sie trotzdem – nur unbewusst und meist zum ungünstigsten Zeitpunkt.

Weiterführende Quellen

Wie fandest du diesen Beitrag?

Kommentare