post

GitHub Actions mit AWS CDK

GitHub Actions und AWS CDK verbinden Codeänderung, Infrastruktur und Deployment zu einer reviewbaren Pipeline – und machen die Architekturentscheidung im Pull Request sichtbar.

≈ 9 Min. Lesezeit

GitHub Actions und AWS CDK verbinden Codeänderung, Infrastruktur und Deployment zu einer reviewbaren Pipeline. Die interessante Frage ist nicht, wie man die beiden Tools verdrahtet, sondern welche Entscheidung dadurch früher sichtbar wird.

Warum diese Kombination mehr ist als Automatisierung

GitHub Actions ist seit wenigen Wochen allgemein verfügbar, und die naheliegende Reaktion ist, damit den bekannten Ablauf nachzubauen: Test bauen, Artefakt schnüren, per Skript deployen. Das funktioniert, verschenkt aber den eigentlichen Hebel. Denn mit AWS CDK beschreibe ich meine Infrastruktur im selben TypeScript-Repository wie meine Anwendung – Lambda, API Gateway, IAM-Rollen, Tabellen. Damit wird Infrastruktur zu Code, der reviewt, versioniert und getestet werden kann wie jeder andere.

Der Punkt, um den es mir geht, entsteht erst im Zusammenspiel. Wenn Anwendungscode und Infrastrukturcode im selben Pull Request liegen und dieselbe Pipeline durchlaufen, dann ist eine Änderung nicht mehr „neuer Handler“ plus „jemand klickt später eine Rolle in der Konsole zusammen“. Sie ist eine einzige, nachvollziehbare Einheit. Die Codeänderung und ihre Betriebsfolge stehen nebeneinander, bevor irgendetwas produktiv wird. Genau das ist der architektonische Gewinn – nicht die Ersparnis an Klicks.

Ich schreibe das auf, weil ich diese Grenze in vielen Projekten falsch gezogen sehe. Infrastruktur lebt in einem Wiki, in Terminalbefehlen, in den Köpfen von zwei Leuten. Die Pipeline deployt den Code, aber niemand kann im Review sagen, welche Rechte, welche Ressourcen und welche Kosten eine Änderung nach sich zieht. CDK plus GitHub Actions macht diese Frage zu einem Artefakt, über das man diskutieren kann.

Drei Schichten, bevor ich an das Tool denke

Ich beschreibe solche Themen in drei Schichten: fachliche Absicht, technische Grenze und betriebliche Konsequenz. Die Absicht erklärt, warum ein System existiert. Die Grenze entscheidet, was sich unabhängig ändern lässt. Die Konsequenz zeigt sich bei Fehlern, Last, Rollback und Review.

Auf dieses Thema übertragen heißt das: Die Absicht ist ein wiederholbares, überprüfbares Deployment. Die Grenze verläuft zwischen Framework und Plattform – mein Code weiß nicht, ob er lokal oder in einer Lambda läuft, und der CDK-Stack ist die Stelle, an der diese Übersetzung passiert. Die Konsequenz ist alles, was nach dem grünen Häkchen kommt: Logs, Timeouts, Rechte, Kaltstarts, die Frage nach dem Rollback.

Erst danach lohnt sich die Tool-Frage. Vorher kläre ich: Welche Änderung soll lokal bleiben? Welche Daten sind führend? Wer darf ein Deployment auslösen? Was muss später nachvollziehbar sein? Wenn diese Fragen offen sind, verschiebt ein Tool sie nur an eine unbequemere Stelle.

Der cdk diff als Review-Artefakt

Der methodische Kern dieser Kombination ist für mich ein einziger Befehl: cdk diff. Er vergleicht den synthetisierten CloudFormation-Stand mit dem, was in AWS tatsächlich läuft, und zeigt die Differenz – neue Ressourcen, geänderte Rechte, gelöschte Tabellen. Wenn ich diesen Befehl in den Pull-Request-Lauf ziehe, bekommt jeder Review eine ehrliche Vorschau auf die Betriebsfolge.

Das ändert die Qualität eines Reviews spürbar. Ein Reviewer sieht nicht nur, dass ein Handler dazukommt, sondern dass dieser Handler eine neue IAM-Policy mit dynamodb:* mitbringt. Diese Zeile im Diff ist der Moment, in dem eine schlechte Entscheidung auffällt – vor dem Merge, nicht im nächsten Security-Audit. Ich verschiebe damit einen Fehler von „teuer und spät“ nach „billig und früh“, und das ist die konkrete Wirkung, um die es geht.

Der Diff deckt außerdem eine Klasse von Problemen auf, die sonst unsichtbar bleibt: Drift. Wenn jemand in der Konsole eine Ressource von Hand ändert, weicht der reale Stand vom Code ab. Ohne Diff merkt das niemand, bis ein Deployment die manuelle Änderung stillschweigend überschreibt und irgendetwas kaputtgeht, das „gestern noch lief“. Mit dem Diff im Pull Request wird diese Abweichung zum sichtbaren Signal – und zur Gelegenheit, die eigentliche Frage zu stellen: Warum wurde an der Konsole gearbeitet, und wie bringe ich diese Änderung zurück in den Code, wo sie hingehört. Der Diff ist damit nicht nur ein Review-Werkzeug, sondern eine leise Disziplinierung des ganzen Teams in Richtung „der Code ist die Wahrheit“.

Ein kleiner Stack

Als Grundlage dient ein bewusst kleiner Stack – eine Lambda hinter einem API Gateway. Er soll keine vollständige Anwendung simulieren, sondern die Grenze zeigen.

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"),
      timeout: cdk.Duration.seconds(10)
    });

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

Interessant ist weniger, was hier steht, als was hier nicht steht. LambdaRestApi ist ein Construct, das im Hintergrund eine API, eine Integration und die passende Ausführungsrolle erzeugt. Das ist der Wert einer guten Abstraktion: Sie reduziert Wiederholung und kapselt vernünftige Defaults. Und das ist zugleich ihr Risiko – sie darf nicht verstecken, welche Ressourcen, Rechte und Kosten real entstehen. Der cdk diff ist genau die Kontrollinstanz, die das Versteckte wieder sichtbar macht.

Ein gutes Beispiel ist nicht das mit den meisten Features, sondern das, bei dem man die Entscheidung nach fünf Minuten diskutieren kann. Der timeout ist so ein Diskussionspunkt: zehn Sekunden sind eine Betriebsentscheidung, kein Detail, und sie gehört in den Code, nicht in eine mündliche Absprache.

Die Pipeline

Die Pipeline hat zwei Aufgaben, die ich sauber trenne. Im Pull Request zeigt sie den Diff, ohne etwas zu verändern. Auf main deployt sie. Diese Trennung ist keine Kosmetik – sie stellt sicher, dass die Vorschau folgenlos ist und das Deployment einen klaren Auslöser hat.

name: deploy

on:
  push:
    branches: [main]
  pull_request:

jobs:
  diff:
    if: github.event_name == 'pull_request'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v1
      - uses: actions/setup-node@v1
        with:
          node-version: "12"
      - run: npm ci
      - run: npm run build
      - run: npx cdk diff
        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

  deploy:
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v1
      - uses: actions/setup-node@v1
        with:
          node-version: "12"
      - run: npm ci
      - run: npm run build
      - 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

Die Credentials liegen als GitHub Secrets im Repository und werden über Umgebungsvariablen an die CDK-CLI gereicht. Das ist der pragmatische Weg, den ich heute gehe – und zugleich die Stelle mit der größten Verantwortung, dazu gleich mehr.

Der Ablauf als Skizze:

flowchart LR
  PR[Pull Request] --> Diff[cdk diff<br/>im Review sichtbar]
  Diff --> Merge[Merge nach main]
  Merge --> Build[Build + synth]
  Build --> Deploy[cdk deploy]
  Deploy --> CFN[CloudFormation<br/>Change Set]
  CFN --> Observe[Logs + Metriken]

Eine Umgebung, die sich reproduzieren lässt

Sobald der erste Stack läuft, kommt die Frage nach Staging und Produktion. Und hier zeigt sich, warum Infrastruktur als Code mehr ist als ein bequemeres Deployment. Weil mein Stack eine TypeScript-Klasse ist, kann ich ihn parametrisieren und zweimal instanziieren – dieselbe Definition, unterschiedliche Namen, unterschiedliche Konten. Staging ist dann per Konstruktion eine exakte Kopie von Produktion, nicht eine Umgebung, die sich über Monate langsam auseinanderentwickelt, bis niemand mehr weiß, welcher Unterschied Absicht war und welcher Zufall.

Das ist eine der methodischen Konsequenzen, die ich am höchsten schätze. Der klassische Bruch zwischen „funktioniert bei mir“ und „funktioniert in Produktion“ entsteht fast immer aus Umgebungsunterschieden, die niemand dokumentiert hat. Wenn die Umgebung selbst versioniert ist, verschwindet dieser Bruch nicht vollständig, aber er wird zu einer Frage, die ich im Repository beantworten kann, statt zu einem Rätsel, das ich unter Zeitdruck im Produktivsystem löse. Genau deshalb ordne ich CDK nicht unter „Deployment-Automatisierung“ ein, sondern unter „Reproduzierbarkeit als Architekturprinzip“.

Rechte, die nicht mit jeder Bequemlichkeit mitwachsen

Die verlockendste Abkürzung ist eine Deploy-Identität mit AdministratorAccess. Sie funktioniert immer, sie erklärt nie einen Fehler, und sie ist der Grund, warum ein kompromittiertes Repository zum kompromittierten Account wird. Der Trade-off ist real: Eine eng geschnittene Rolle bedeutet, dass ein Deployment gelegentlich an einer fehlenden Berechtigung scheitert und ich nachschärfen muss. Aber dieses Scheitern ist billig und lehrreich, während der stille Vollzugriff nur ein einziges Mal teuer wird.

Serverless verschiebt Betriebsarbeit, es entfernt sie nicht. Rechte, Logs, Kaltstarts, Timeouts, Kosten und Rollback-Fähigkeit müssen weiterhin explizit entworfen werden. Der Unterschied ist, dass ich sie mit CDK im selben Review entwerfe, in dem ich auch den Handler schreibe – statt sie auf „machen wir später“ zu vertagen. „Später“ ist in der Praxis der Name für „gar nicht“.

Rollback und der Irrtum des glücklichen Pfades

Der zweite Denkfehler, den ich oft sehe, ist die Überbewertung des glücklichen Pfades. Viele Pipelines sehen sauber aus, solange jedes Deployment gelingt. Interessant wird es bei Teilfehlern: Eine Ressource wird erstellt, die nächste scheitert, und der Stack steht auf halber Strecke.

Hier hilft, dass CDK auf CloudFormation aufsetzt. Ein fehlgeschlagenes Deployment rollt auf den letzten konsistenten Stand zurück, und dieses Verhalten ist Teil der Plattform, nicht meines Skripts. Das ist ein starker Grund, unter CDK zu bleiben, statt einzelne Ressourcen mit imperativen CLI-Aufrufen zu verdrahten: Ich bekomme eine definierte Transaktionsgrenze geschenkt. Trotzdem muss ich sie kennen. Ein Deployment, das Daten migriert, ist nicht automatisch rückbaubar, nur weil der Stack es ist – die Frage nach Idempotenz und Wiederholbarkeit bleibt meine.

Was sich dadurch messbar ändert

Wenn diese Teile zusammenspielen, ändert sich die Arbeit spürbar, und zwar in einer Richtung, die ich einem technischen Entscheider erklären kann. Der Zyklus von „Idee“ zu „läuft produktiv“ wird kürzer, weil kein manueller Übergabeschritt mehr zwischen Code und Infrastruktur liegt. Die Reibung im Team sinkt, weil niemand mehr Deployment-Wissen als persönliches Herrschaftswissen hält – es steht im Repository. Und die Qualität steigt, weil der cdk diff einen Fehler dorthin verschiebt, wo er billig ist: in den Pull Request.

Konkret sieht das so aus: In einem Team ohne diese Konstruktion dauert eine Infrastrukturänderung so lange, wie die verfügbare Person mit AWS-Zugang braucht – plus die Zeit, bis jemand merkt, dass die Änderung in Staging und Produktion unterschiedlich ausgefallen ist. Mit der Pipeline ist dieselbe Änderung ein Pull Request, den zwei Kolleg

lesen können, ohne selbst Konsolenzugriff zu haben. Der Engpass „eine Person mit Rechten“ verschwindet, und mit ihm die stille Warteschlange, die niemand je auf einem Board sieht. Das ist Developer Productivity im wörtlichen Sinn: nicht schneller tippen, sondern weniger Übergaben, weniger Rückfragen, weniger Zustände, in denen jemand auf jemand anderen wartet.

Das ist der Bogen von der Mechanik zur Wirkung. GitHub Actions und CDK sind austauschbare Werkzeuge. Die wiederverwendbare Entscheidung dahinter – Infrastruktur als reviewbaren Teil jeder Änderung zu behandeln – ist es nicht.

Worauf ich im Review prüfe

Für den Review dieser Konstruktion stelle ich drei Fragen. Zeigt der Pull Request die Betriebsfolge, also den Diff, oder nur den Anwendungscode? Ist die Deploy-Identität so eng geschnitten, dass ich einen Rechtefehler erklären kann, statt ihn wegzukonfigurieren? Und ist klar, was bei einem halb durchgelaufenen Deployment passiert?

Wenn eine dieser Fragen offen bleibt, fehlt der Pipeline noch etwas – ein sichtbarer Diff, eine schärfere Rolle oder ein geklärter Rollback-Pfad. Ein Construct sollte Wiederholung reduzieren und gute Defaults kapseln, aber nie verschleiern, welche Ressourcen, Rechte und Kosten entstehen. Genau an dieser Kante zwischen bequemer Abstraktion und ehrlicher Sichtbarkeit entscheidet sich, ob die Kombination trägt.

Weiterführende Quellen

Die Quellen helfen beim Gegenprüfen von Plattformverhalten. Die eigentliche Aussage bleibt die Architekturentscheidung im konkreten Beispiel.

Schluss

Der Beitrag ist dann stark, wenn er nicht nur zeigt, wie man GitHub Actions und CDK verdrahtet, sondern warum die Grenze zwischen Anwendung und Plattform genau in den Pull Request gehört. Dort entsteht der Nutzen für Entwickler, Architekten und technische Entscheider – nicht im Toolnamen, sondern in der wiederverwendbaren Entscheidung, Infrastruktur so ernst zu nehmen wie den Code, der auf ihr läuft.

Kommentare