IaC-Lösungen für AWS Cloud 2020: Welche ist die richtige für Sie?

Während meiner Tätigkeit als Cloud Berater hatte ich die Gelegenheit mehrere Infrastruktur-as-Code (IAC) Ansätze für Amazon Web Service (AWS) hands-on einzusetzen. Lassen Sie mich meine Erkenntnisse mit Ihnen teilen.

Dieser Leitfaden konzentriert sich auf den Marktführer AWS, da dieser über die größte Vielfalt an IaC-Frameworks verfügt. Wenn Sie Azure- oder Google-Cloud oder eine Mischung aus allen Anbietern verwenden, können Sie diesen Artikel überspringen und mit dem Lernen von terraform beginnen.

Sie kennen vermutlich das User Interface von AWS zur Interaktion mit Cloud-Diensten, die sogenannte AWS „Konsole“. Diese ist sehr nützlich um eine visuelle Darstellung über die Dienste und deren Monitoring Daten zu erhalten.

Auch die manuelle Manipulation des Zustands der Dienste (Starten, Stoppen, Aktualisieren, Konfigurieren) ist möglich, wird aber bei Implementierungen größerer Anwendungen schnell zeitaufwändig und unüberschaubar.

Infrastruktur als Code(IaC) oder:

„…eine einzelne textbasierte Beschreibung einer vollständigen Cloud-Infrastruktur zu einem gegebenen Zeitpunkt“

beseitigt diese Probleme, indem es über ein Versionskontrollsystem wie GIT versionierbar ist und automatisch, ohne weiteren manuellen Aufwand über einen CI-Service wie CodePipeline, bereitgestellt werden kann. Die Open-Source-Community hat bereits mehrere Lösungen zur Verwaltung von IaC für Cloud-Ressourcen herausgebracht:

AWS CloudFormation

Das älteste und mittlerweile etablierteste IaC Framework für Amazon Web Services heißt CloudFormation. Dabei handelt es sich um eine deklarative Spezifikation, die Ihre gesamte Cloud-Infrastruktur in einer oder mehreren .yaml-Dateien beschreibt. Sie können so genannte „Stacks“ verwenden, um die Bereitstellung Ihrer Infrastruktur zu modularisieren, und Sie finden auf der Website von AWS verschiedene Vorlagen dieser Stacks für fast jeden von AWS angebotenen Dienst. Heutzutage ist es jedoch sehr selten, dass ein Entwicklungsingenieur ein „rohes“ CloudFormation-Skript von Hand schreibt, da es doch einige Nachteile gibt, die ich im Folgenden erläutern will.

Da eine Beispiel-CloudFormation-Datei für eine einfache Bereitstellung zu lang war, um sie hier einzufügen, möchte ich Ihnen einen Link zu einem github-Repository mit einem guten Beispiel zur Verfügung stellen

Vorteile

  • Weithin bekannte YAML- und JSON-Syntax wird unterstützt
  • Alle anderen IaC Wrapper von AWS basieren auf CloudFormation. Das Debuggen Ihres fehlgeschlagenen Deployments kann es erforderlich machen, dass Sie ab und zu einen Blick in eine CloudFormation-Datei werfen. Deswegen schadet es nicht deren Struktur zu kennen.

Nachteile

  • Viele ältere CloudFormation-Templates sind fehlerhaft, was darauf hindeutet, dass die AWS-Dienste und ihre Abhängigkeiten häufige Änderungen aufweisen.
  • CloudFormation ist nur für AWS und nicht für Multi-Cloud-Implementierungen geeignet.
  • Nachteil von Konfigurationssprachen wie YAML/JSON: Sie haben nur Definitionsanweisungen und Verschachtelungen zur Verfügung. Andererseits haben Sie die gesamte AWS-Landschaft mit reichhaltiger Semantik und Verknüpfungen zur Verfügung, die Sie nun in YAML „syntaktisch downsamplen“ müssen.
  • Vorlagen werden sehr schnell lang: Um verwandte Ressourcen miteinander zu verknüpfen, müssen die Anweisungen in verschiedenen Ebenen tief verschachtelt werden, was die Dateilänge vergrößert.

Im Gegensatz zu Programmiersprachen, in denen Sie eine klare Unterscheidung zwischen sprachspezifischer Syntax und Ihrer eigenen benutzerdefinierten Logik haben, verwenden CloudFormation-spezifische Anweisungen (d.h. „Parameter“, „Ref“) die gleiche Syntax wie Attribute, die als Definition für die jeweiligen Services dienen (d.h. „InstanceType“ für eine EC2-Instanz). Das macht es für einen menschlichen Leser sehr schwer, Muster zu unterscheiden und Strukturen zu finden.

Empfehlung:

Die Kernidee von CloudFormation ist immer noch sehr ordentlich, aber es gibt inzwischen eine Vielzahl anderer Lösungen, die in der Open-Source-Gemeinschaft etabliert und von AWS anerkannt sind und die Komplexität der Vorlagen für CloudFormation beseitigen. Am wichtigsten ist, dass die direkte Verwendung von CloudFormation Sicherheitsrisiken mit sich bringen könnte, einfach weil die Komplexität und Wiederholbarkeit von CloudFormation menschliches Versagen begünstigt. Daher ist es ein mutiger Schritt, im Jahr 2020 CloudFormation direkt für die Verwaltung Ihrer Cloud-Infrastruktur im Code zu verwenden, außer vielleicht zur Fehlersuche. Ziehen Sie in Betracht, stattdessen eine der folgenden Lösungen zu verwenden.

AWS-Amplify

Ein CLI-basiertes Framework zur Entwicklung von Cloud-nativen Apps. Dabei ist das Rollout von Cloud-Ressourcen nur eine der vielen Fähigkeiten unter dem Deckmantel der kompletten Back-to-Front Entwicklung einer (mobilen) Anwendung. Die aktuelle Definition Ihrer Infrastruktur und lokal angepassten Version wird in einem Backend/Ordner als Code und CloudFormation Files innerhalb Ihres App-Repositorys gespeichert. Ähnlich wie git vergleicht es die beiden Zustände, sobald Sie den cli-Befehl „amplify push“ drücken und stellt nur die Änderungen bereit.

Ein Beispiel-Workflow mit Amplify zum Hinzufügen einer Lambda-Funktion zu einem bestehenden Projekt sehen Sie im Folgenden. Die CLI ist eine narrensichere Möglichkeit, Dienste in der Cloud einzurichten und zu verbinden:

amplify add function
? Provide a friendly name for your resource to be used as a label for this category in the project: lambdafunction
? Provide the AWS Lambda function name: lambdafunction
? Choose the function template that you want to use: (Use arrow keys)
❯ Hello world function
  CRUD function for Amazon DynamoDB table (Integration with Amazon API Gateway and Amazon DynamoDB)
  Serverless express function (Integration with Amazon API Gateway)

Vorteile

  • Starke CLI: Sie können Ihre Backend-Ressourcen erstellen, löschen, aktualisieren, indem Sie einfache (Ja/Nein) Fragen beantworten. Das macht es sehr benutzerfreundlich, auch wenn Sie nicht viel über CloudFormation verstehen.
  • Integration mit AWS Cognito: Sie können ganz einfach einen Benutzer-Pool erstellen, Authentifizierung hinzufügen (mit OAuth), und wenn Sie sogar das dazugehörige Frontend mit Login/Register/Reset Screens von der Kommandozeile aus wählen.
  • Starke Integration mit DynamoDB + GraphQl API: Theoretisch können Sie die Mehrzahl der beiden Schichten (Api & Datenbank) in einer einzigen shema.graphql Datei definieren: Definieren Sie die Datentypen mit wenigen Direktiven und Tabellen, Api-Endpunkte und Resolver werden für Sie generiert. Auch grundlegende GraphQl-Abfragen/Mutationen/Ereignisse werden aus der CLI generiert, die Sie in Ihrem Frontend verwenden können.
  • Alle erstellten Dienste in amplify können „verbunden“ werden. Wenn Sie also z.B. eine Lambda-Funktion einsetzen wollen, die auf Ihren Benutzer-Pool zugreift, definieren Sie diese im Cli, die automatisch Berechtigungen hinzufügt und eine ENV-Variable erzeugt, die Sie in Ihrer Funktion verwenden können.
  • Hands-on-Dokumentation.

Cons

  • Einige Startschwierigkeiten: Zum Beispiel gibt es zur Zeit keinen automatischen Benutzertyp, der innerhalb von Amplify erstellt wird. Es ist notwendig, benutzerdefinierte Resolver zu erstellen, um nicht jeden Benutzer in cognito und in Ihrem DynamoDB-Speicher zu duplizieren. Da die Benutzerhandhabung ein so grundlegendes Merkmal jeder App-Entwicklungsplattform ist, ist es eine böse Überraschung, dass dies nicht automatisch integriert wurde. Aber es gibt noch Hoffnung.
  • CLI überschreibt Ihre benutzerdefinierten Änderungen: Dies ist beabsichtigt, da sich Amplify stark auf seine CLI verlässt. Wenn Sie sich entscheiden, die CloudFormation-Vorlage einer Ressource für eine benutzerdefinierte Änderung zu bearbeiten, können Sie nach meinem Verständnis die CLI für diese Ressource anschließend nicht mehr verwenden, da sie Ihre Änderungen überschreibt.
  • Eine allgemeine Regel für IaC, kann aber nicht oft genug wiederholt werden: Verzichten Sie auch hier auf Remote-Drift (z.B. manuelles Löschen einer Lambda-Funktion über die Konsole, die über Amplify bereitgestellt wurde), da dies Ihren „amplify push“-Befehl außer Kraft setzt.

Im Allgemeinen sollten Sie zu diesem Zeitpunkt hoffen, dass Sie den Grund für das Scheitern einer der cli-Befehle herausfinden können, da Sie sonst im schlimmsten Fall das Projekt zurücksetzen und neu beginnen müssen. Lassen Sie uns hoffen, dass sich die Fehlerbehandlung der CLI hier verbessern wird.

Empfehlung

Wenn Sie ein kleines oder sogar ein Ein-Mann-Team sind, das schnell eine (mobile) App auf der Basis von (AWS-) Cloud-Ressourcen entwickelt und sich weniger mit DevOps beschäftigen will, ist Amplify eine sehr sichere Wahl. Ich persönlich finde es recht nützlich und benutze es nach wie vor für meine persönlichen Projekte. Ich bin mir jedoch nicht sicher, ob die Verwendung von AWS Amplify in Unternehmensumgebungen eine gute Idee ist, da Sie dazu ermutigt werden, zuerst die CLI zu verwenden, die wiederum nicht versionierbar/rückverfolgbar ist, mit Ausnahme von Änderungen in den CloudFormation Vorlagen.

Transform: AWS::Serverless-2016-10-31
Globals:
  Function:
    PermissionsBoundary: !Sub 'arn:${AWS::Partition}:iam::${AWS::AccountId}:policy/${AppId}-${AWS::Region}-PermissionsBoundary'

# Paramaters accessible in this template and your handlers
Parameters:
  AppId:
    Type: String
    Default: helloworld-app
  S3BuildBucket:
    Type: String
    Default: arn:aws:s3:::aws-sam-cli-managed-default-samclisourcebucket-13vlf6m4wtxxx

Resources:
  printHelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      # Path to folder of handlers
      CodeUri: ./src/handlers/
      # <fileName>.<mainFunction>
      Handler: printHelloWorld.printHelloWorldHandler
      Runtime: nodejs10.x
      MemorySize: 128
      Timeout: 60
      Description: A simple example includes a HTTP get method to get all items from a DynamoDB table.
      Policies:
        # Give Create/Read/Update/Delete Permissions to the SampleTable
        - DynamoDBCrudPolicy:
            TableName: !Ref HelloWorldTable
        - S3CrudPolicy:
            BucketName: !Ref S3BuildBucket
      Environment:
        Variables:
          # Make table name accessible as environment variable from function code during execution
          SAMPLE_TABLE: !Ref HelloWorldTable
      Events:
        # Here an Api endpoint is connected
        Api:
          Type: Api
          Properties:
            Path: /helloworld
            Method: GET
            
  # Creates a DynamoDB Table
  HelloWorldTable:
    Type: AWS::Serverless::SimpleTable
    TableName: HelloWorldTable
    Properties:
      PrimaryKey:
        Name: id
        Type: String
      ProvisionedThroughput:
        ReadCapacityUnits: 2
        WriteCapacityUnits: 2

Vorteile

  • Die Verbindung von Ressourcen ist einfach, ohne größere Verschachtelungen. Einige beliebte Richtlinien sind sogar vordefiniert (z.B.  DynamoDBCrudPolicy), wodurch die Berechtigungszuweisung einer Ressource auf eine Zeile reduziert wird.
  • Wie in Amplify werden Endpunkte automatisch als ENV-Variablen in Ihre Lambda-Handler injiziert, kein Herumhantieren mit Endpunkten in der config.
  • „Low-level“ CloudFormation von Anfang an, so dass benutzerdefinierte Erweiterungen mit CloudFormation möglich sind.

Nachteile

  • Nur nützlich für sehr einfache Anwendungen und einen Bedarf an CloudFormation, sobald die Anwendung wächst.

Empfehlung

Man kann SAM als einen Kompromiss zwischen Amplify und CloudFormation sehen. Es gibt praktisch keinen Overhead für Ihre Entwicklungsumgebung im Vergleich zu einem vollwertigen App Framework wie Amplify und gleichzeitig ist es wesentlich einfacher strukturiert als ein pures CloudFormation Skript. Wenn Sie an einer einfachen 3-Tier-Anwendung arbeiten und ohnehin nur Lambda, das API-Gateway und eine NoSQL-Datenbank verwenden würden, können Sie Ihre Anwendung getrost auf SAM aufbauen. Ich fand es auch sehr einfach, das Projekt von der REST-basierten API in SAM zu einer GraphQL-basierten API in Amplify zu migrieren, indem man einfach die Lambda-Handler in die entsprechenden Resolver-Funktionen für die GraphQL-API kopiert (falls nötig).

AWS CDK

Das AWS Cloud Development Kit (AWS CDK) ist ein Open-Source-Softwareentwicklungs-Framework, mit dem Sie Ihre Cloud-Anwendungsressourcen unter Verwendung vertrauter Programmiersprachen definieren können. 

import apigateway = require('@aws-cdk/aws-apigateway'); 
import dynamodb = require('@aws-cdk/aws-dynamodb');
import lambda = require('@aws-cdk/aws-lambda');
import cdk = require('@aws-cdk/core');

export class HelloWorldApp extends cdk.Stack {
  constructor(app: cdk.App, id: string) {
    super(app, id);

    const dynamoTable = new dynamodb.Table(this, 'items', {
      partitionKey: {
        name: 'id',
        type: dynamodb.AttributeType.STRING
      },
      tableName: 'HelloWorldTable',

      removalPolicy: cdk.RemovalPolicy.DESTROY, // NOT recommended for production code
    });

    const printHelloWorldFunction = new lambda.Function(this, 'printHelloWorldFunction', {
      code: new lambda.AssetCode('./src/handlers/'),
      // <Filename>
      handler: 'printHelloWorld',
      runtime: lambda.Runtime.NODEJS_10_X,
      environment: {
        TABLE_NAME: dynamoTable.tableName,
        PRIMARY_KEY: 'id'
      }
    });
    
    dynamoTable.grantReadWriteData(printHelloWorldFunction);

    const api = new apigateway.RestApi(this, 'helloWorldApi', {
      restApiName: 'HelloWorld Service'
    });

    const helloWorldEndpoint = api.root.addResource('helloWorld');
    const printHelloWorldIntegration = new apigateway.LambdaIntegration(printHelloWorldFunction);
    helloWorldEndpoint.addMethod('GET', printHelloWorldIntegration);
    addCorsOptions(helloWorldEndpoint);
  }
}

// An example how to include some custom CORS Definition programmatically
export function addCorsOptions(apiResource: apigateway.IResource) {
  apiResource.addMethod('OPTIONS', new apigateway.MockIntegration({
    integrationResponses: [{
      statusCode: '200',
      responseParameters: {
        'method.response.header.Access-Control-Allow-Headers': "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent'",
        'method.response.header.Access-Control-Allow-Origin': "'*'",
        'method.response.header.Access-Control-Allow-Credentials': "'false'",
        'method.response.header.Access-Control-Allow-Methods': "'OPTIONS,GET,PUT,POST,DELETE'",
      },
    }],
    passthroughBehavior: apigateway.PassthroughBehavior.NEVER,
    requestTemplates: {
      "application/json": "{\"statusCode\": 200}"
    },
  }), {
    methodResponses: [{
      statusCode: '200',
      responseParameters: {
        'method.response.header.Access-Control-Allow-Headers': true,
        'method.response.header.Access-Control-Allow-Methods': true,
        'method.response.header.Access-Control-Allow-Credentials': true,
        'method.response.header.Access-Control-Allow-Origin': true,
      },  
    }]
  })
}

const app = new cdk.App();
new HelloWorldApp(app, 'HelloWorldApp');
app.synth();

Sie sehen, dass die Verwendung des CDK etwas mehr Boilerplate Code bedeutet, aber Sie können die Logik auf Ihre eigene gewünschte Weise strukturieren, wie mit der „addCorsOption“-Funktion.

Vorteile

  • Kann jede Komplexität und Anpassung des Aufbaus bewältigen und unterstützt jede Ressource im AWS Portfolio von Anfang an. Es ist praktisch für große Deployments, welche möglichst flexibel sein müssen.
  • Vergleichsweise leichter zu verstehen, da Sie von der Programmiersprache Ihrer Wahl profitieren können, besonders wenn Sie einen Programmierhintergrund haben.
  • Einige weitere Vorteile der Programmierung: Sie können auf die Code-Vervollständigung zugreifen, so dass Sie die Dokumentation nicht so oft durchblättern müssen, Sie können den Roll-Out in logische Teile durch Objektorientierung strukturieren und schließlich die Open-Source Community durch Kopieren praktischer Code-Schnipsel nutzen.

Nachteile

  • Für einfache Rollouts scheint das Schreiben einer Anwendung mit logischer Strukturierung, um dann wiederum eine grundlegende Cloud-Infrastruktur (d.h. eine zweistufige Anwendung) einzuführen, etwas übertrieben zu sein.
  • Die Anwendung entwickelt sich immer noch weiter und wird wahrscheinlich große Änderungen zwischen den Hauptversionen haben.

Empfehlung

AWS CDK eignet sich besonders gut für Teams mit Programmiererfahrung, Projekte mit einem Bedarf an einer hochgradig anpassbaren Infrastruktur, die die Entwicklung grundlegender Anwendungen erweitert, und fluktuierende Teams, die eine menschenfreundliche Beschreibung der Infrastruktur für eine schnelle Einarbeitung benötigen. Als Beispiel für diese Vielfalt gibt es bereits eine Open-Source-Erweiterung des CDK für kubernetes „cdk8s“, die mit wenigen Zeilen Code beliebige kubernetes-Konfigurationsdateien im .yaml-Format erzeugen kann. Für einfache Anwendungsbereitstellungen (alias „give me a database and Api“) könnte dies jedoch zu viel Aufwand sein. In einem solchen Fall sind Sie sind mit einer der obigen Lösungen (SAM und Amplify) und mehr Zeit für Entwicklung vermutlich besser dran.

Terraform

Terraform ist ein Werkzeug zum sicheren und effizienten Aufbau, Ändern und Versionieren von Infrastruktur. Terraform kann sowohl bestehende und beliebte Dienstanbieter als auch kundenspezifische Inhouse-Lösungen verwalten. Ein kurzes Beispiel dafür, wie man eine Lambda-Funktion mit einer dynamoDB-Tabelle ohne API-Gateway definiert:

provider "aws" {
   region = "us-east-1"
}

resource "aws_dynamodb_table" "HelloWorld" {
  name             = "HelloWorldTable"
  hash_key         = "id"
  billing_mode   = "PROVISIONED"
  read_capacity  = 5
  write_capacity = 5
  attribute {
    name = "id"
    type = "S"
  }
}

resource "aws_iam_role_policy" "lambda_policy" {
  name = "lambda_policy"
  role = aws_iam_role.role_for_LDC.id

  policy = file("policy.json")
}


resource "aws_iam_role" "role_for_LDC" {
  name = "myrole"
  # policy is stored externally 
  assume_role_policy = file("assume_role_policy.json")

}

resource "aws_lambda_function" "myLambda" {

  function_name = "func"
  s3_bucket     = "mybucket98745"
  s3_key        = "index.zip"
  role          = aws_iam_role.role_for_LDC.arn
  handler       = "printHelloWorldFunction.printHelloWorld"
  runtime       = "nodejs12.x"
}

Vorteile

  • Unabhängig von AWS, kann Terraform also für oder mit anderen Cloud-Anbietern in Multi-Cloud-Implementierungen verwendet werden.
  • Man könnte es schon jetzt als Standard für Multi-Cloud-Implementierungen bezeichnen

Nachteile

  • Eher flache Lernkurve für den praktischen Einsatz (Faustregel: Wenn es ein Zertifikat dafür gibt, kann man es nicht einfach an einem Wochenende lernen).
  • Terraform muss ständig angepasst werden, da sich auch die API von AWS, Azure etc. ändern. Dies kann zu einer kleinen Verzögerung führen, in der Ihre Roll-Outs fehlschlagen können, bis terraform eine nächste Version herausbringt.
  • Im Vergleich zu dedizierten AWS-Lösungen keine implizite Codegenerierung. Während z.B. passende Zugriffsrichtlinien on-the-fly in Amplify und teilweise in SAM generiert werden, müssen Sie dies hier explizit eingeben.

Empfehlung

Terraform eignet sich als die Einstiegslösung für Multi-Cloud-Roll-outs neben den AWS. Wenn Sie bereits ein Projekt planen, das die App-Entwicklung ausweiten könnte und auf mehreren Cloud-Anbietern basiert, gibt es praktisch keine Alternative zu terraform. Vielleicht sind Sie nicht allzu begeistert davon, eine weitere Syntax (HCL) zu lernen, aber wer weiß, vielleicht haben Sie bereits andere HashiCorp-Produkte verwendet oder werden sie in Zukunft verwenden.

TL/DR

Die genannten Dienstleistungen haben alle ihre individuellen Vor- und Nachteile und bieten in verschiedenen Szenarien einen Mehrwert. Lassen Sie uns in Erinnerung rufen, dass es gerade in der IT-Welt kein „perfektes Framework/Paradigma“ gibt: nur gute Lösungen, die den goldenen Mittelweg zwischen der Abstraktion von sich wiederholenden/komplexen Dingen und der Detaillierung von Dinge die Sie gerne und häufig anpassen wollen.

Um es zusammenzufassen:

  • CloudFormation ist nach wie vor eine nicht zu verachtende Lösung um Infrastruktur als Code bereit zu stellen. Jedoch kann die Komplexität und Menge der benötigten Ausdrücke schnell überhand nehmen. Falls Sie sich hier nicht gut auskennen sind die folgenden Lösungen eine besserer Wahl.
  • Wenn Sie eine produktionsreife, benutzerbasierte Anwendung mit Authentifizierung, Benutzer-Pool, benutzerdefiniertem Backend, benutzerbasiertem Blob-Speicher, noSQL Storage (u.a.) entwickeln und ein kleines agiles Team haben, entscheiden Sie sich für AWS Amplify.
  • Wenn Sie schnell eine einfache Anwendung entwickeln, z.B. für ein Forschungsprojekt, die nur die einfachsten Grundbausteine benötigt: Datenbank, benutzerdefinierte Backend/Funktionen und eine API, benutzen Sie AWS SAM.
  • Wenn Sie einen komplexen Einsatz auf AWS in Aussicht haben, der nicht durch die oben genannten Lösungen abgedeckt ist und gleichzeitig von den Vorteilen Ihrer bevorzugten Programmiersprache profitieren möchten, ist AWS CDK eine gute Wahl.
  • Wenn Sie sich mit einem CloudProvider (in diesem Artikel AWS) nicht sicher sind und Ihre Infrastruktur auf mehreren Cloud-Providern bereitstellen möchten, benutzen Sie Terraform.

Quellen

https://docs.microsoft.com/en-us/azure/devops/learn/what-is-infrastructure-as-code

https://zach-gollwitzer.medium.com/imperative-vs-declarative-programming-procedural-functional-and-oop-b03a53ba745c

https://www.bayesian.ninja/2019/11/aws-cloudformation-pros-and-cons.html

https://www.trustradius.com/products/terraform/reviews?qs=pros-and-cons