Einführung in das AWS Serverless Application Model

Einführung in das AWS Serverless Application Model

1. Überblick

Inour previous article haben wir bereits eine Serverless-Anwendung mit vollem Stapel in AWS implementiert, die API Gateway für REST-Endpunkte, AWS Lambda für Geschäftslogik sowie eine DynamoDB als Datenbank verwendet.

Die Bereitstellung besteht jedoch aus vielen manuellen Schritten, die mit zunehmender Komplexität und der Anzahl der Umgebungen unhandlich werden können.

In diesem Tutorial werden wir nun die Verwendung vonAWS Serverless Application Model (SAM), which enables a template-based description and automated deployment of serverless applications on AWS erläutern.

Im Detail werden wir uns die folgenden Themen ansehen:

  • Grundlagen des Serverless Application Model (SAM) sowie der zugrunde liegenden CloudFormation

  • Definition einer serverlosen Anwendung unter Verwendung der SAM-Vorlagensyntax

  • Automatisierte Bereitstellung der Anwendung mithilfe der CloudFormation-CLI

2. Grundlagen

Wie bereits inpreviously erläutert, können wir mit AWS mithilfe von API-Gateway, Lambda-Funktionen und DynamoDB vollständig serverlose Anwendungen implementieren. Zweifellos bietet dies bereits viele Vorteile hinsichtlich Leistung, Kosten und Skalierbarkeit.

Der Nachteil ist jedoch, dass wir im Moment viele manuelle Schritte in der AWS-Konsole benötigen, z. B. das Erstellen jeder Funktion, das Hochladen von Code, das Erstellen der DynamoDB-Tabelle, das Erstellen von IAM-Rollen, das Erstellen von APIs und API-Strukturen usw.

Bei komplexen Anwendungen und in mehreren Umgebungen wie Test, Staging und Produktion vervielfacht sich dieser Aufwand schnell.

Hier kommt CloudFormation für Anwendungen unter AWS im Allgemeinen und das Serverless Application Model (SAM) speziell für Anwendungen ohne Server ins Spiel.

2.1. AWS CloudFormation

CloudFormation ist ein AWS-Service für die automatische Bereitstellung von AWS-Infrastrukturressourcen. Ein Benutzer definiert alle erforderlichen Ressourcen in einem Entwurf (Vorlage genannt), und AWS kümmert sich um die Bereitstellung und Konfiguration.

Die folgenden Begriffe und Konzepte sind für das Verständnis von CloudFormation und SAM von wesentlicher Bedeutung:

A template is a description of an application, wie es zur Laufzeit strukturiert sein soll. Wir können eine Reihe erforderlicher Ressourcen definieren und festlegen, wie diese Ressourcen konfiguriert werden sollen. CloudFormation bietet eine gemeinsame Sprache zum Definieren von Vorlagen und unterstützt JSON und YAML als Format.

Resources are the building blocks in CloudFormation. Eine Ressource kann alles sein, wie ein RestApi, eine Phase eines RestApi, ein Stapeljob, eine DynamoDB-Tabelle, eine EC2-Instanz, eine Netzwerkschnittstelle, eine IAM-Rolle und vieles mehr. The official documentation listet derzeit etwa 300 Ressourcentypen für CloudFormation auf.

A stack is the instantiation of a template. CloudFormation kümmert sich um die Bereitstellung und Konfiguration des Stacks.

2.2. Serverloses Anwendungsmodell (SAM)

Wie so oft kann der Einsatz leistungsfähiger Tools sehr komplex und unhandlich werden, was auch für CloudFormation der Fall ist.

Aus diesem Grund hat Amazon das Serverless Application Model (SAM) eingeführt. SAM started with the claim to provide a clean and straightforward syntax for defining serverless applications. Currently, it has only three resource types, which are Lambda functions, DynamoDB tables, and APIs.

SAM basiert auf der CloudFormation-Vorlagensyntax, sodass wir unsere Vorlage mithilfe der einfachen SAM-Syntax definieren können. CloudFormation verarbeitet diese Vorlage dann weiter.

Weitere Details sind inat the official GitHub repository sowie inAWS documentation verfügbar.

3. Voraussetzungen

Für das folgende Tutorial benötigen wir ein AWS-Konto. A free tier account sollte ausreichend sein.

Außerdem benötigen wir die AWS CLIinstalled.

Schließlich benötigen wir einen S3-Bucket in unserer Region, der über die AWS-CLI mit dem folgenden Befehl erstellt werden kann:

$>aws s3 mb s3://example-sam-bucket

Während das Lernprogramm im Folgendenexample-sam-bucket verwendet, beachten Sie, dass Bucket-Namen eindeutig sein müssen, sodass Sie Ihren Namen auswählen müssen.

Als Demo-Anwendung verwenden wir den Code vonUsing AWS Lambda with API Gateway.

4. Vorlage erstellen

In diesem Abschnitt erstellen wir unsere SAM-Vorlage.

Wir werden uns zunächst die Gesamtstruktur ansehen, bevor wir die einzelnen Ressourcen definieren.

4.1. Struktur der Vorlage

Schauen wir uns zunächst die Gesamtstruktur unserer Vorlage an:

AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'
Description: example Serverless Application Model example

Resources:
  PersonTable:
    Type: AWS::Serverless::SimpleTable
    Properties:
      # Define table properties here
  StorePersonFunction:
    Type: AWS::Serverless::Function
    Properties:
      # Define function properties here
  GetPersonByHTTPParamFunction:
    Type: AWS::Serverless::Function
    Properties:
      # Define function properties here
  MyApi:
    Type: AWS::Serverless::Api
    Properties:
      # Define API properties here

Wie wir sehen können, besteht die Vorlage aus einem Header und einem Body:

Der Header gibt die Version der CloudFormation-Vorlage (AWSTemplateFormatVersion) sowie die Version unserer SAM-Vorlage (Transform) an. Wir können auchDescription angeben.

Der Body besteht aus einer Reihe von Ressourcen: Jede Ressource hat einen Namen, eine RessourceType und eine MengeProperties.

Die SAM-Spezifikation unterstützt derzeit drei Typen:AWS::Serverless::Api,AWS::Serverless::Function sowieAWS::Serverless::SimpleTable.

Da wir unsereexample application bereitstellen möchten, müssen wir einSimpleTable, zweiFunctions sowie einApi in unserem Vorlagenkörper definieren.

4.2. DynamoDB-Tabellendefinition

Definieren wir jetzt unsere DynamoDB-Tabelle:

AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'
Description: example Serverless Application Model example

Resources:
  PersonTable:
    Type: AWS::Serverless::SimpleTable
    Properties:
      PrimaryKey:
          Name: id
          Type: Number
      TableName: Person

Wir müssen nur zwei Eigenschaften für unsereSimpleTable definieren: den Tabellennamen sowie einen Primärschlüssel, derid heißt und in unserem Fall den TypNumber hat.

Eine vollständige Liste der unterstützten Eigenschaften vonSimpleTablefinden Sie unterin the official specification.

Hinweis: Da wir nur mit dem Primärschlüssel auf die Tabelle zugreifen möchten, istAWS::Serverless::SimpleTable für uns ausreichend. Für komplexere Anforderungen kann stattdessen der native CloudFormation-TypAWS::DynamoDB::Table verwendet werden.

4.3. Definition der Lambda-Funktionen

Als nächstes definieren wir unsere zwei Funktionen:

AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'
Description: example Serverless Application Model example

Resources:
  StorePersonFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: com.example.lambda.apigateway.APIDemoHandler::handleRequest
      Runtime: java8
      Timeout: 15
      MemorySize: 512
      CodeUri: ../target/aws-lambda-0.1.0-SNAPSHOT.jar
      Policies: DynamoDBCrudPolicy
      Environment:
        Variables:
          TABLE_NAME: !Ref PersonTable
      Events:
        StoreApi:
          Type: Api
            Properties:
              Path: /persons
              Method: PUT
              RestApiId:
                Ref: MyApi
  GetPersonByHTTPParamFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: com.example.lambda.apigateway.APIDemoHandler::handleGetByParam
      Runtime: java8
      Timeout: 15
      MemorySize: 512
      CodeUri: ../target/aws-lambda-0.1.0-SNAPSHOT.jar
      Policies: DynamoDBReadPolicy
      Environment:
        Variables:
          TABLE_NAME: !Ref PersonTable
      Events:
        GetByPathApi:
          Type: Api
            Properties:
              Path: /persons/{id}
              Method: GET
              RestApiId:
                Ref: MyApi
        GetByQueryApi:
          Type: Api
            Properties:
              Path: /persons
              Method: GET
              RestApiId:
                Ref: MyApi

Wie wir sehen können, hat jede Funktion die gleichen Eigenschaften:

Handler defines the logic of the function. Da wir Java verwenden, ist dies der Klassenname einschließlich des Pakets in Verbindung mit dem Methodennamen.

Runtime defines how the function was implemented, in unserem Fall Java 8.

Timeout defines how long the execution of the code may take at most, bevor AWS die Ausführung beendet.

MemorySizedefines the size of the assigned memory in MB. Es ist wichtig zu wissen, dass AWS die CPU-Ressourcen proportional zuMemorySize zuweist. Bei einer CPU-intensiven Funktion muss möglicherweiseMemorySize erhöht werden, auch wenn die Funktion nicht so viel Speicher benötigt.

CodeUridefines the location of the function code. Es verweist derzeit auf den Zielordner in unserem lokalen Arbeitsbereich. Wenn wir unsere Funktion später mit CloudFormation hochladen, erhalten wir eine aktualisierte Datei mit einem Verweis auf ein S3-Objekt.

Policiescan hold a set of AWS-managed IAM policies or SAM-specific policy templates. Wir verwenden die SAM-spezifischen RichtlinienDynamoDBCrudPolicy fürStorePersonFunction undDynamoDBReadPolicy fürGetPersonByPathParamFunction undGetPersonByQueryParamFunction.

Environmentdefines environment properties at runtime. Wir verwenden eine Umgebungsvariable, um den Namen unserer DynamoDB-Tabelle zu speichern.

Eventscan hold a set of AWS events, which shall be able to trigger the function. In unserem Fall definieren wirEvent vom TypApi. Die eindeutige Kombination vonpath, HTTPMethod undRestApiId verknüpft die Funktion mit einer Methode unserer API, die wir im nächsten Abschnitt definieren werden.

Eine vollständige Liste der unterstützten Eigenschaften vonFunctionfinden Sie unterin the official specification.

4.4. API-Definition als Swagger-Datei

Nachdem Sie die DynamoDB-Tabelle und -Funktionen definiert haben, können Sie jetzt die API definieren.

Die erste Möglichkeit besteht darin, unsere API inline im Swagger-Format zu definieren:

AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'
Description: example Serverless Application Model example

Resources:
  MyApi:
    Type: AWS::Serverless::Api
    Properties:
      StageName: test
      EndpointConfiguration: REGIONAL
      DefinitionBody:
        swagger: "2.0"
        info:
          title: "TestAPI"
        paths:
          /persons:
            get:
              parameters:
              - name: "id"
                in: "query"
                required: true
                type: "string"
              x-amazon-apigateway-request-validator: "Validate query string parameters and\
                \ headers"
              x-amazon-apigateway-integration:
                uri:
                  Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${GetPersonByHTTPParamFunction.Arn}/invocations
                responses: {}
                httpMethod: "POST"
                type: "aws_proxy"
            put:
              x-amazon-apigateway-integration:
                uri:
                  Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${StorePersonFunction.Arn}/invocations
                responses: {}
                httpMethod: "POST"
                type: "aws_proxy"
          /persons/{id}:
            get:
              parameters:
              - name: "id"
                in: "path"
                required: true
                type: "string"
              responses: {}
              x-amazon-apigateway-integration:
                uri:
                  Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${GetPersonByHTTPParamFunction.Arn}/invocations
                responses: {}
                httpMethod: "POST"
                type: "aws_proxy"
        x-amazon-apigateway-request-validators:
          Validate query string parameters and headers:
            validateRequestParameters: true
            validateRequestBody: false

UnserApi hat drei Eigenschaften:StageNamedefiniert die Stufe der API,EndpointConfiguration definiert, ob die API regional oder kantenoptimiert ist, undDefinitionBody enthält die tatsächliche Struktur der API.

InDefinitionBody definieren wir drei Parameter: dieswagger -Version als“2.0”, dieinfo:title: als“TestAPI” sowie eine Menge vonpaths .

Wie wir sehen können, stellen diepaths die API-Struktur dar, die wir manuell definieren musstenbefore. Diepaths in Swagger entsprechen den Ressourcen in der AWS-Konsole. Auf diese Weise kann jedespath ein oder mehrere HTTP-Verben haben, die den Methoden in der AWS-Konsole entsprechen.

Jede Methode kann einen oder mehrere Parameter sowie einen Anforderungsprüfer haben.

Der aufregendste Teil ist das Attributx-amazon-apigateway-integration, eine AWS-spezifische Erweiterung von Swagger:

uri gibt an, welche Lambda-Funktion aufgerufen werden soll.

responses geben Regeln an, wie die von der Funktion zurückgegebenen Antworten transformiert werden sollen. Da wir die Lambda-Proxy-Integration verwenden, benötigen wir keine spezifische Regel.

type definiert, dass wir die Lambda-Proxy-Integration verwenden möchten, und daher müssen wirhttpMethod auf“POST” setzen, da dies die Lambda-Funktionen erwarten.

Eine vollständige Liste der unterstützten Eigenschaften vonApifinden Sie unterin the official specification.

4.5. Implizite API-Definition

Eine zweite Möglichkeit besteht darin, die API implizit in den Funktionsressourcen zu definieren:

AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'
Description: example Serverless Application Model Example with Implicit API Definition

Globals:
  Api:
    EndpointConfiguration: REGIONAL
    Name: "TestAPI"

Resources:
  StorePersonFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: com.example.lambda.apigateway.APIDemoHandler::handleRequest
      Runtime: java8
      Timeout: 15
      MemorySize: 512
      CodeUri: ../target/aws-lambda-0.1.0-SNAPSHOT.jar
      Policies:
        - DynamoDBCrudPolicy:
            TableName: !Ref PersonTable
      Environment:
        Variables:
          TABLE_NAME: !Ref PersonTable
      Events:
        StoreApi:
          Type: Api
          Properties:
            Path: /persons
            Method: PUT
  GetPersonByHTTPParamFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: com.example.lambda.apigateway.APIDemoHandler::handleGetByParam
      Runtime: java8
      Timeout: 15
      MemorySize: 512
      CodeUri: ../target/aws-lambda-0.1.0-SNAPSHOT.jar
      Policies:
        - DynamoDBReadPolicy:
            TableName: !Ref PersonTable
      Environment:
        Variables:
          TABLE_NAME: !Ref PersonTable
      Events:
        GetByPathApi:
          Type: Api
          Properties:
            Path: /persons/{id}
            Method: GET
        GetByQueryApi:
          Type: Api
          Properties:
            Path: /persons
            Method: GET

Wie wir sehen können, ist unsere Vorlage jetzt etwas anders: Es gibt keineAWS::Serverless::Api -Resource mehr.

CloudFormation verwendet jedoch dieEvents-Attribute vom TypApi als implizite Definition und erstellt trotzdem eine API. Sobald wir unsere Anwendung testen, werden wir feststellen, dass sie sich genauso verhält wie beim expliziten Definieren der API mit Swagger.

Außerdem gibt es einenGlobals Abschnitt, in dem wir den Namen unserer API definieren können, und dass unser Endpunkt regional sein soll.

Es gibt nur eine Einschränkung: Wenn Sie die API implizit definieren, können wir keinen Stufennamen festlegen. Aus diesem Grund erstellt AWS in jedem Fall eine Stufe mit dem NamenProd.

5. Bereitstellung und Test

Nachdem Sie die Vorlage erstellt haben, können Sie mit der Bereitstellung und dem Testen fortfahren.

Dazu laden wir unseren Funktionscode in S3 hoch, bevor wir die eigentliche Bereitstellung auslösen.

Am Ende können wir unsere Anwendung mit jedem HTTP-Client testen.

5.1. Code-Upload nach S3

In einem ersten Schritt müssen wir den Funktionscode nach S3 hochladen.

Dazu rufen wir CloudFormation über die AWS-CLI auf:

$> aws cloudformation package --template-file ./sam-templates/template.yml --s3-bucket example-sam-bucket --output-template-file ./sam-templates/packaged-template.yml

Mit diesem Befehl lösen wir CloudFormation aus, um den inCodeUri: angegebenen Funktionscode zu übernehmen und in S3 hochzuladen. CloudFormation erstellt einepackaged-template.yml-Datei mit demselben Inhalt, außer dassCodeUri: jetzt auf das S3-Objekt zeigt.

Werfen wir einen Blick auf die CLI-Ausgabe:

Uploading to 4b445c195c24d05d8a9eee4cd07f34d0 92702076 / 92702076.0 (100.00%)
Successfully packaged artifacts and wrote output template to file packaged-template.yml.
Execute the following command to deploy the packaged template
aws cloudformation deploy --template-file c:\zz_workspace\tutorials\aws-lambda\sam-templates\packaged-template.yml --stack-name 

5.2. Einsatz

Jetzt können wir die eigentliche Bereitstellung auslösen:

$> aws cloudformation deploy --template-file ./sam-templates/packaged-template.yml --stack-name example-sam-stack  --capabilities CAPABILITY_IAM

Da unser Stack auch IAM-Rollen benötigt (wie die Rollen der Funktionen für den Zugriff auf unsere DynamoDB-Tabelle), müssen wir dies durch Angabe der–capabilities parameter explizit bestätigen.

Und die CLI-Ausgabe sollte folgendermaßen aussehen:

Waiting for changeset to be created..
Waiting for stack create/update to complete
Successfully created/updated stack - example-sam-stack

5.3. Bereitstellungsüberprüfung

Nach der Bereitstellung können wir das Ergebnis überprüfen:

$> aws cloudformation describe-stack-resources --stack-name example-sam-stack

CloudFormation listet alle Ressourcen auf, die Teil unseres Stacks sind.

5.4. Test

Schließlich können wir unsere Anwendung mit jedem HTTP-Client testen.

Sehen wir uns einige Beispiele fürcURL-Befehle an, die wir für diese Tests verwenden können.

StorePersonFunction:

$> curl -X PUT 'https://0skaqfgdw4.execute-api.eu-central-1.amazonaws.com/test/persons' \
   -H 'content-type: application/json' \
   -d '{"id": 1, "name": "John Doe"}'

GetPersonByPathParamFunction:

$> curl -X GET 'https://0skaqfgdw4.execute-api.eu-central-1.amazonaws.com/test/persons/1' \
   -H 'content-type: application/json'

GetPersonByQueryParamFunction:

$> curl -X GET 'https://0skaqfgdw4.execute-api.eu-central-1.amazonaws.com/test/persons?id=1' \
   -H 'content-type: application/json'

5.5. Aufräumen

Am Ende können wir aufräumen, indem wir den Stack und alle enthaltenen Ressourcen entfernen:

aws cloudformation delete-stack --stack-name example-sam-stack

6. Fazit

In diesem Artikel haben wir uns mit dem AWS Serverless Application Model (SAM) befasst, das eine vorlagenbasierte Beschreibung und die automatisierte Bereitstellung von serverlosen Anwendungen unter AWS ermöglicht.

Im Detail haben wir folgende Themen besprochen:

  • Grundlagen des Serverless Application Model (SAM) sowie der zugrunde liegenden CloudFormation

  • Definition einer serverlosen Anwendung unter Verwendung der SAM-Vorlagensyntax

  • Automatisierte Bereitstellung der Anwendung mithilfe der CloudFormation-CLI

Wie üblich ist der gesamte Code für diesen Artikel überon GitHub verfügbar.