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.