Beliebte Suchanfragen

Cloud Native

DevOps

IT-Security

Agile Methoden

Java

//

CI/CD-Pipelines mit AWS CDK CodePipeline

17.7.2023 | 4 Minuten Lesezeit

Das Aufsetzen der CI/CD-Pipeline ist ein typischer Task in der Anfangszeit eines Projekts. Ist die Pipeline dann aufgesetzt, sind Änderungen nur noch selten notwendig. Dementsprechend wenig Routine entwickeln Programmierende im Umgang mit der Konfiguration einer Pipeline. Wenn dann noch besondere Anforderungen, wie Cross-Account-Deployments, ins Spiel kommen, wird die Konfiguration der Pipeline kompliziert.

Das AWS CDK bietet mit dem CodePipeline-Konstrukt eine einfache Möglichkeit, die CI/CD-Pipeline für ein CDK-basiertes Projekt aufzusetzen. Bevor wir ein paar Beispiele betrachten, werfen wir einen Blick auf die Konzepte der CodePipeline.

Die Struktur

Das CodePipeline-Konstrukt kapselt die drei AWS-Services CodePipeline, CodeBuild und CodeDeploy, aus denen eine Pipeline im AWS-Umfeld üblicherweise aufgebaut wird. Wie die Namen beschreiben, fokussieren sich die Services jeweils auf das Orchestrieren der Pipeline, das Bauen der Anwendung und das eigentliche Deployment.

Der Kern einer CodePipeline ist der synth-Schritt. Hier wird definiert, wie die Pipeline das CloudFormation-Template generieren soll, das im Anschluss für das Deployment genutzt wird. Alle Artefakte werden vollautomatisch in einem S3-Bucket abgelegt, der ebenfalls vom Konstrukt angelegt wird. Im Anschluss nutzt CodeDeploy das Template aus dem Bucket und startet das Deployment über CloudFormation. Mit Stages werden die Accounts und Regionen der Deployments konfiguriert.

Die Basics

1const pipeline = new CodePipeline(this, 'my-pipeline', {
2    pipelineName: 'my-pipeline',
3    selfMutation: true,
4    synth: new ShellStep('synth', {
5        input: CodePipelineSource.codeCommit(repository, 'main'),
6        commands: ['npm ci', 'npm run build', 'npx cdk synth'],
7    })
8});

Der Beispielcode zeigt die Pipeline für ein NPM-basiertes Projekt. Das CloudFormation-Template wird synthetisiert, indem zunächst die Dependencies installiert und anschließend der Code gebaut wird (Zeile 6). Das Artefakt wird anschließend automatisch in einem S3-Bucket archiviert.

Eine weitere Besonderheit der CodePipeline ist, dass ihr Code im selben Repository liegt wie der Anwendungscode. Über das Property selfMutation (Zeile 3) konfigurieren wir, dass sich Codeänderungen an der Pipeline selbst aktualisieren können. Hierzu ist es notwendig, die Pipeline in einen eigenen CDK-Stack auszulagern und diesen zunächst manuell zu deployen. Anschließend aktualisiert sich die Pipeline bei jeder Codeänderung selbst.

Nachdem unser CloudFormation-Template synthetisiert wurde, muss das eigentliche Deployment gestartet werden. Hier kommen die bereits erwähnten Stages ins Spiel. Das folgende Beispiel zeigt das Hinzufügen einer Deployment-Stage mit dem Namen “MyPipelineStage” zur Pipeline. Diese wird automatisch am Ende der Pipeline ausgeführt. Eine Stage wird dafür genutzt, die notwendigen Stacks einer Anwendung zu initialisieren und damit das Deployment anzustoßen. Im Beispiel ist dies "MyStack", der in den AWS-Account "111111111111" und die Region "eu-central-1" deployt werden soll.

1pipeline.addStage(new MyPipelineStage(this, 'dev'));
2
3[...]
4
5class MyPipelineStage extends Stage {
6    constructor(scope: Construct, id: string, props?: StageProps) {
7        super(scope, id, props);
8        new MyStack(this, 'my-stack', {
9            env: { account: '111111111111', region: 'eu-central-1' },
10        });
11    }
12}

Die Erweiterung

Der Anwendungscode wird von AWS CodeBuild gebaut. Dieser Service bietet unterschiedliche Umgebungen, in denen dieser Prozess ablaufen kann. Je nach Stack des Projekts muss eine dazu passende Umgebung ausgewählt werden. Das folgende Beispiel zeigt die Konfiguration für ein Build-Environment, in dem Node.js in Version 18 verfügbar ist. Weitere Möglichkeiten zeigt AWS in dieser Übersicht.

1synthCodeBuildDefaults: {
2    buildEnvironment: { buildImage: LinuxBuildImage.STANDARD_7_0 },
3    partialBuildSpec: BuildSpec.fromObject({
4        phases: {
5            install: {
6                'runtime-versions': { nodejs: '18' },
7            },
8        },
9    }),
10 }

Falls das Projekt Lambda-Funktionen enthält, die mit dem NodejsFunction-Konstrukt umgesetzt werden, übernimmt das CDK das Kompilieren des Codes. In unserer Pipeline reicht es, das dockerEnabledForSynth-Property zu setzen. Dadurch kann die CodePipeline den Typescript-Code zu JavaScript transpilieren.

In manchen Fällen möchten wir einen Schritt manuell bestätigen und die Automatisierung der Pipeline umgehen. Beispielsweise soll das Deployment auf die Produktionsumgebung erst nach einem Test der Änderungen auf der Entwicklungsumgebung stattfinden. Mithilfe eines ManualApprovalSteps können wir die Pipeline auf die Bestätigung eines Users warten lassen, bevor es mit der nächsten Stage weitergeht.

1const devDeploymentStage = pipeline.addStage(new DevPipelineStage(this, 'dev'));
2devDeploymentStage.addPost(new ManualApprovalStep('approval'));
3const prodDeploymentStage = pipeline.addStage(new ProdPipelineStage(this, 'prod'));

Das Cross-Account-Deployment

Gerade wenn diverse Abteilungen und Teams im AWS-Umfeld unterwegs sind, hat sich ein Deployment über Account-Grenzen etabliert. Beispielsweise werden die Entwicklungs- und Produktionsumgebungen in unterschiedlichen AWS-Accounts umgesetzt. Die Pipeline kann jedoch zentral in einem eigenen Account laufen. Auch das Deployment in mehrere Regionen lässt sich leicht durch weitere Stages umsetzen.

Hierzu muss in der Pipeline zunächst das Property crossAccountKeys gesetzt werden, um die KMS-Keys zu generieren. Diese Schlüssel werden genutzt, um die Pipeline-Artefakte zu verschlüsseln. Dem anderen Account muss erlaubt werden, den Schlüssel zu nutzen, um die Artefakte wieder zu entschlüsseln. Alle Ressourcen und Berechtigungen übernimmt das CDK-Konstrukt für uns.

1new CodePipeline(this, 'my-pipeline', {
2    crossAccountKeys: true,
3    [...]
4})
5
6pipeline.addStage(new PipelineStage(this, 'dev'), {
7    env: { account: '111111111111', region: 'eu-central-1' },
8});
9pipeline.addStage(new PipelineStage(this, 'prod'), {
10    env: { account: '222222222222, region: 'us-east-1' },
11});

Das Fazit

Mit dem CodePipeline-Konstrukt erlaubt es das CDK unkompliziert die Pipeline für unser Projekt aufzusetzen. Besonders bei komplexeren Use Cases ist weniger Verständnis der AWS-Plattform notwendig, um Ergebnisse zu erzielen. Ein Beispiel sind hier die notwendigen Rollen und Policies, um Cross-Account-Deployments umzusetzen.

Durch die einfache API des Konstrukts können wir schnell Erfolge erzielen und uns auf die Implementierung von fachlichen Anforderungen konzentrieren. Setzen wir ein neues Projekt um, können wir die Konfiguration der CI/CD-Pipeline zukünftig dem CDK überlassen.

Beitrag teilen

Gefällt mir

2

//

Weitere Artikel in diesem Themenbereich

Entdecke spannende weiterführende Themen und lass dich von der codecentric Welt inspirieren.

//

Gemeinsam bessere Projekte umsetzen.

Wir helfen deinem Unternehmen.

Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.

Hilf uns, noch besser zu werden.

Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.