Programmieren statt Konfigurieren: Infrastruktur als TypeScript-Code

Seite 2: Wie das CDK mit 30 Zeilen Code zum Ziel kommt

Inhaltsverzeichnis

Um herauszufinden, wie sich das AWS CDK im Vergleich schlägt, sind zuerst einige Voraussetzungen zu erfüllen. Diese sind gut nachvollziehbar auf der Website des CDK Workshop aufgelistet, die auch generell als Einstieg in das CDK sehr zu empfehlen ist. Unter anderem muss das AWS CLI installiert und ein AWS-Account vorhanden sein, wobei auch ein kostenloser Account ausreicht. Als weitere Voraussetzung muss Node.js in einer aktuellen Version installiert sein.

Das CDK CLI lässt sich dann über den Node Package Manager npm installieren. Der nachfolgende Befehl weist npm an, die CDK-Programmbibliothek und das CLI global, also von überall verfügbar, zu installieren:

npm install -g aws-cdk

Als Nächstes sollte das Bootstrapping erfolgen. Dabei handelt es sich um das Erstellen von Zusatzdiensten, die das CDK für seine Arbeit benötigt. Dieses Bootstrap-Kommando bereitet den AWS-Account für die Nutzung des CDK vor:

cdk bootstrap

In einem beliebigen Ordner lässt sich dann das eigentliche CDK-Projekt in TypeScript erstellen. Dazu dient der init-Befehl, der das Grundgerüst einer CDK-Anwendung in einer Programmiersprache nach Wahl erstellt. Diese Codezeile erzeugt ein Grundgerüst für eine CDK-Anwendung in TypeScript:

cdk init sample-app --language typescript

Dreh- und Angelpunkt ist die *-stack.ts-Datei im Ordner lib (die Bezeichnung hängt vom Namen des Projektordners ab). Hier ist bereits ein Stack, also eine Gruppierung von Diensten und Infrastruktur, vordefiniert. Über den init-Befehl wurde hier bereits ein Beispiel-Stack mit einem Amazon SNS Topic und angeschlossener Amazon SQS Queue angelegt. Der Inhalt der Datei wird nun mit dem in Listing 1 gezeigten Code ersetzt. Das vollständige, lauffähige Projekt steht auf GitHub zur Verfügung

/*!
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: MIT-0
*/

import { Duration, Stack, StackProps, CfnOutput } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as ec2 from 'aws-cdk-lib/aws-ec2'
import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'
import * as loadbalancing from 'aws-cdk-lib/aws-elasticloadbalancingv2'
const fs = require('fs');

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

    const vpc = new ec2.Vpc(this, 'MyVPC')

    const asg = new autoscaling.AutoScalingGroup(this, 'MyASG', {
      vpc: vpc,
      instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE3, ec2.InstanceSize.MICRO),
      machineImage: ec2.MachineImage.latestAmazonLinux({ generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2022 }),
      vpcSubnets: vpc.selectSubnets({ subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }),
      minCapacity: 2
    })

    asg.addUserData(fs.readFileSync('scripts/install.sh', 'utf8'))

    const alb = new loadbalancing.ApplicationLoadBalancer(this, 'MyALB', {
      vpc: vpc,
      internetFacing: true
    })

    const listener = alb.addListener('HttpListener', {
      port: 80
    })

    listener.addTargets('Targets', {
      port: 80,
      targets: [asg]
    })

    listener.connections.allowDefaultPortFromAnyIpv4('Allow access to port 80 from the internet.')

    new CfnOutput(this, 'Hostname', { value: alb.loadBalancerDnsName })

  }
}

Listing 1: Etwa 30 Zeilen TypeScript-Code definieren einen Stack aus VPC, Auto Scaling Group und Application Load Balancer.

Der Name der Klasse (in Listing 1 WebAppStack) wird vom Namen des Projektordners abgeleitet und kann daher lokal anders lauten. Das Listing ist entsprechend anzupassen, damit es nicht zu Fehlern kommt. Zusätzlich wird im Projektordner noch ein neuer Ordner namens scripts angelegt, darin eine Datei mit dem Namen install.sh erstellt und diese mit dem Inhalt aus Listing 2 abgespeichert.

#!/bin/bash -xe

# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: MIT-0

yum update -y
yum install -y httpd
systemctl start httpd
systemctl enable httpd
usermod -a -G apache ec2-user
chown -R ec2-user:apache /var/www
chmod 2775 /var/www
find /var/www -type d -exec chmod 2775 {} \;
find /var/www -type f -exec chmod 0664 {} \;
echo '<html><head><title>Hello World!</title></head><body><p>Hello World!</p></body></html>' > /var/www/html/index.html

Listing 2: Ein Bash-Skript installiert den Apache-HTTP-Server, konfiguriert ihn als dauerhaft laufenden Dienst und fügt eine statische HTML-Seite hinzu.

Bei Listing 2 handelt es sich um Installationsskript, das beim ersten Start der EC2-Instanzen einen Webserver installiert und eine "Hello World!"-HTML-Seite hinterlegt. Beim Zugriff auf diesen lässt sich später nachvollziehen, dass die EC2-Instanzen wie gewünscht laufen und das Beispiel "Ende zu Ende" funktioniert. 

Um den Stack in der Cloud aufzusetzen, müssen nun nur noch zwei Dinge geschehen: Zunächst ist es nötig, den TypeScript-Code zu kompilieren. Dafür ist bereits ein passendes Skript hinterlegt. Das build-Kommando aktiviert das vorgegebene Skript zum Kompilieren des TypeScript-Codes zu JavaScript:

npm run build

Danach lässt sich der Stack per CDK CLI in der Cloud installieren. Das deploy-Kommando weist das CDK an, den in TypeScript definierten Stack in der Cloud aufzubauen:

cdk deploy

Im Verlauf wird das CDK CLI sicherheitsrelevante Änderungen auflisten und fragen, ob es die Änderungen anwenden soll. Nach der Bestätigung übergibt das CLI das synthetisierte CloudFormation-Template an den gleichnamigen Cloud-Service zur Umsetzung. Der Aufbau der einzelnen Komponenten lässt sich weiterhin auf der Kommandozeile verfolgen, bis das CLI die Fertigstellung meldet. Am Ende erscheint eine Variable Hostname als Ausgabe, die den DNS-Namen des Load Balancer darstellt. Sie lässt sich in den Browser eingeben, um die Test-Website anzuzeigen. Der Zugriff sollte per HTTP erfolgen, HTTPS wurde für dieses Beispiel nicht konfiguriert. Zu Beginn kann es dabei vorkommen, dass ein HTTP-Code den Fehler 502 zurückgibt, weil die EC2-Instanzen noch nicht bereit sind oder die Installation des Webservers noch nicht abgeschlossen ist. In diesem Fall sollte nach einigen Minuten ein erneuter Versuch erfolgen.

Das synthetisierte Template lässt sich im Order cdk.out als Datei mit der Endung .template.json inspizieren. Dieses Template ist aufgrund zusätzlicher Metadaten, die das CDK benötigt, mit rund 1000 Zeilen sogar noch länger als die zuvor erwähnten 300 Zeilen. Tatsächlich wurden aber nur etwa 30 Zeilen TypeScript-Code zu dem Projekt hinzugefügt. Das CDK hat das Template dann automatisch generiert. Das ist möglich, weil in die verwendeten Constructs viele sinnvolle Standardeinstellungen bereits eingebaut sind und die zusätzlichen Hilfsfunktionen typische Konfigurationsmuster abkürzen.