Introduction au modèle d’application AWS Serverless

Introduction au modèle d'application AWS Serverless

1. Vue d'ensemble

Dansour previous article, nous avons déjà implémenté une application sans serveur à pile complète sur AWS, en utilisant API Gateway pour les points de terminaison REST, AWS Lambda pour la logique métier, ainsi qu'un DynamoDB en tant que base de données.

Toutefois, le déploiement comporte de nombreuses étapes manuelles, qui peuvent devenir difficiles à gérer avec la complexité croissante et le nombre d’environnements.

Dans ce didacticiel, nous allons maintenant expliquer comment utiliser lesAWS Serverless Application Model (SAM), which enables a template-based description and automated deployment of serverless applications on AWS.

En détail, nous examinerons les sujets suivants:

  • Principes de base du modèle d'application sans serveur (SAM), ainsi que de la CloudFormation sous-jacente

  • Définition d'une application sans serveur en utilisant la syntaxe du modèle SAM

  • Déploiement automatisé de l'application à l'aide de la CLI CloudFormation

2. Les bases

Comme indiqué danspreviously, AWS nous permet de mettre en œuvre des applications entièrement sans serveur, en utilisant API Gateway, les fonctions Lambda et DynamoDB. Sans aucun doute, cela offre déjà de nombreux avantages en termes de performances, de coût et d’évolutivité.

Cependant, l'inconvénient est que nous avons actuellement besoin de nombreuses étapes manuelles dans la console AWS, telles que la création de chaque fonction, le téléchargement de code, la création de la table DynamoDB, la création de rôles IAM, la création d'API et de sa structure, etc.

Pour les applications complexes et avec plusieurs environnements tels que le test, le transfert et la production, cet effort se multiplie rapidement.

C’est là que CloudFormation pour les applications sur AWS en général, et le modèle SAM (Serverless Application Model) spécifiquement pour les applications sans serveur, entrent en jeu.

2.1. AWS CloudFormation

CloudFormation est un service AWS pour le provisionnement automatique des ressources d'infrastructure AWS. Un utilisateur définit toutes les ressources requises dans un plan directeur (appelé modèle) et AWS se charge de l'approvisionnement et de la configuration.

Les termes et concepts suivants sont essentiels pour comprendre CloudFormation et SAM:

A template is a description of an application, comment il doit être structuré à l'exécution. Nous pouvons définir un ensemble de ressources requises, ainsi que la manière dont ces ressources doivent être configurées. CloudFormation fournit un langage commun pour la définition de modèles, prenant en charge JSON et YAML en tant que format.

Resources are the building blocks in CloudFormation. Une ressource peut être n'importe quoi, comme un RestApi, une étape d'un RestApi, un Batch Job, une table DynamoDB, une instance EC2, une interface réseau, un rôle IAM et bien d'autres. The official documentation répertorie actuellement environ 300 types de ressources pour CloudFormation.

A stack is the instantiation of a template. CloudFormation prend en charge l'approvisionnement et la configuration de la pile.

2.2. Modèle d'application sans serveur (SAM)

Comme souvent, l’utilisation d’outils puissants peut devenir très complexe et peu pratique, comme dans le cas de CloudFormation.

C'est pourquoi Amazon a introduit le modèle SAM (Serverless Application Model). 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 est basé sur la syntaxe du modèle CloudFormation. Nous pouvons donc définir notre modèle à l'aide de la syntaxe SAM simple. CloudFormation traitera ensuite ce modèle.

Plus de détails sont disponiblesat the official GitHub repository ainsi que dans lesAWS documentation.

3. Conditions préalables

Pour le didacticiel suivant, nous aurons besoin d'un compte AWS. A free tier account devrait être suffisant.

En plus de cela, nous avons besoin de l'AWS CLIinstalled.

Enfin, nous avons besoin d'un godet S3 dans notre région, qui peut être créé via l'interface de ligne de commande AWS à l'aide de la commande suivante:

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

Bien que le didacticiel utiliseexample-sam-bucket dans ce qui suit, sachez que les noms de compartiment doivent être uniques, vous devez donc choisir votre nom.

En tant qu'application de démonstration, nous utiliserons le code deUsing AWS Lambda with API Gateway.

4. Création du modèle

Dans cette section, nous allons créer notre modèle SAM.

Nous allons d'abord examiner la structure globale, avant de définir les ressources individuelles.

4.1. Structure du modèle

Tout d'abord, examinons la structure globale de notre modèle:

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

Comme on peut le constater, le modèle consiste en un en-tête et un corps:

L'en-tête spécifie la version du modèle CloudFormation (AWSTemplateFormatVersion) ainsi que la version de notre modèle SAM (Transform). Nous pouvons également spécifier unDescription.

Le corps se compose d'un ensemble de ressources: chaque ressource a un nom, une ressourceType et un ensemble deProperties.

La spécification SAM prend actuellement en charge trois types:AWS::Serverless::Api,AWS::Serverless::Function ainsi queAWS::Serverless::SimpleTable.

Comme nous voulons déployer nosexample application, nous devons définir unSimpleTable, deuxFunctions, ainsi qu'unApi dans notre template-body.

4.2. Définition de la table DynamoDB

Définissons maintenant notre table DynamoDB:

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

Il suffit de définir deux propriétés pour nosSimpleTable: le nom de la table, ainsi qu'une clé primaire, qui s'appelleid et a le typeNumber dans notre cas.

Une liste complète des propriétésSimpleTable prises en charge peut être trouvéein the official specification.

Remarque: comme nous voulons uniquement accéder à la table en utilisant la clé primaire, lesAWS::Serverless::SimpleTable nous suffisent. Pour des exigences plus complexes, le type CloudFormation natifAWS::DynamoDB::Table peut être utilisé à la place.

4.3. Définition des fonctions Lambda

Ensuite, définissons nos deux fonctions:

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

Comme on peut le constater, chaque fonction a les mêmes propriétés:

Handler defines the logic of the function. Comme nous utilisons Java, c'est le nom de la classe incluant le package, en relation avec le nom de la méthode.

Runtime defines how the function was implemented, qui est Java 8 dans notre cas.

Timeout defines how long the execution of the code may take at most avant qu'AWS ne termine l'exécution.

MemorySizedefines the size of the assigned memory in MB. Il est important de savoir qu'AWS attribue les ressources CPU proportionnellement àMemorySize. Ainsi, dans le cas d’une fonction gourmande en ressources processeur, il peut être nécessaire d’augmenter lesMemorySize, même si la fonction n’a pas besoin de beaucoup de mémoire.

CodeUridefines the location of the function code. Il fait actuellement référence au dossier cible dans notre espace de travail local. Lorsque nous téléchargerons notre fonction ultérieurement à l'aide de CloudFormation, nous obtiendrons un fichier mis à jour avec une référence à un objet S3.

Policiescan hold a set of AWS-managed IAM policies or SAM-specific policy templates. Nous utilisons les politiques spécifiques SAMDynamoDBCrudPolicy pour lesStorePersonFunction etDynamoDBReadPolicy pour lesGetPersonByPathParamFunction etGetPersonByQueryParamFunction.

Environmentdefines environment properties at runtime. Nous utilisons une variable d'environnement pour contenir le nom de notre table DynamoDB.

Eventscan hold a set of AWS events, which shall be able to trigger the function. Dans notre cas, nous définissons unEvent de typeApi. La combinaison unique depath, d'un HTTPMethod et d'unRestApiId relie la fonction à une méthode de notre API, que nous définirons dans la section suivante.

Une liste complète des propriétésFunction prises en charge peut être trouvéein the official specification.

4.4. Définition d'API en tant que fichier Swagger

Après avoir défini la table et les fonctions DynamoDB, nous pouvons maintenant définir l’API.

La première possibilité consiste à définir notre API en ligne en utilisant le format Swagger:

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

NotreApi partage trois propriétés:StageNamedéfinit l'étape de l'API,EndpointConfiguration définit si l'API est régionale ou optimisée en périphérie, etDefinitionBody contient la structure réelle de l'API.

Dans lesDefinitionBody, nous définissons trois paramètres: laswagger version en“2.0”, lesinfo:title: en“TestAPI”, ainsi qu'un ensemble depaths .

Comme on peut le voir, lespaths représentent la structure de l'API, que nous avons dû définir manuellementbefore. Lespaths dans Swagger sont équivalents aux ressources de la console AWS. Juste comme ça, chaquepath peut avoir un ou plusieurs verbes HTTP, qui sont équivalents aux méthodes de l'AWS Console.

Chaque méthode peut avoir un ou plusieurs paramètres ainsi qu'un validateur de requête.

La partie la plus intéressante est l'attributx-amazon-apigateway-integration, qui est une extension spécifique à AWS de Swagger:

uri spécifie quelle fonction Lambda doit être appelée.

responses spécifie les règles de transformation des réponses renvoyées par la fonction. Comme nous utilisons l'intégration du proxy Lambda, nous n'avons besoin d'aucune règle spécifique.

type définit que nous voulons utiliser l'intégration du proxy Lambda, et par conséquent, nous devons définirhttpMethod sur“POST”, car c'est ce à quoi s'attendent les fonctions Lambda.

Une liste complète des propriétésApi prises en charge peut être trouvéein the official specification.

4.5. Définition d'API implicite

Une deuxième option consiste à définir l'API implicitement dans les ressources Function:

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

Comme nous pouvons le voir, notre modèle est légèrement différent maintenant: il n'y a plus de ressourceAWS::Serverless::Api .

Cependant, CloudFormation prend les attributsEvents de typeApi comme une définition implicite et crée quand même une API. Dès que nous testons notre application, nous verrons qu'elle se comporte de la même manière que lors de la définition explicite de l'API à l'aide de Swagger.

De plus, il existe une sectionGlobals, où nous pouvons définir le nom de notre API, ainsi que le fait que notre point de terminaison doit être régional.

Une seule limitation survient: lors de la définition implicite de l'API, nous ne pouvons pas définir de nom d'étape. C'est pourquoi AWS créera dans tous les cas une étape appeléeProd.

5. Déploiement et test

Après avoir créé le modèle, nous pouvons maintenant procéder au déploiement et aux tests.

Pour cela, nous allons télécharger notre code de fonction dans S3 avant de déclencher le déploiement proprement dit.

Au final, nous pouvons tester notre application en utilisant n’importe quel client HTTP.

5.1. Téléchargement de code vers S3

Dans un premier temps, nous devons télécharger le code de fonction sur S3.

Nous pouvons le faire en appelant CloudFormation via l'AWS CLI:

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

Avec cette commande, nous déclenchons CloudFormation pour prendre le code de fonction spécifié dansCodeUri: et pour le télécharger sur S3. CloudFormation créera un fichierpackaged-template.yml, qui a le même contenu, sauf queCodeUri: pointe maintenant vers l'objet S3.

Jetons un œil à la sortie CLI:

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. Déploiement

Maintenant, nous pouvons déclencher le déploiement réel:

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

Comme notre pile a également besoin de rôles IAM (comme les rôles des fonctions pour accéder à notre table DynamoDB), nous devons explicitement le reconnaître en spécifiant les–capabilities parameter.

Et la sortie de la CLI devrait ressembler à:

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

5.3. Examen du déploiement

Après le déploiement, nous pouvons examiner le résultat:

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

CloudFormation listera toutes les ressources qui font partie de notre pile.

5.4. Test

Enfin, nous pouvons tester notre application en utilisant n’importe quel client HTTP.

Voyons quelques exemples de commandescURL que nous pouvons utiliser pour ces tests.

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. Nettoyer

Au final, nous pouvons nettoyer en supprimant la pile et toutes les ressources incluses:

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

6. Conclusion

Dans cet article, nous avons examiné le modèle SAM (AWS Serverless Application Model), qui permet une description basée sur des modèles et le déploiement automatisé d'applications sans serveur sur AWS.

En détail, nous avons abordé les sujets suivants:

  • Principes de base du modèle d'application sans serveur (SAM), ainsi que de CloudFormation sous-jacent

  • Définition d'une application sans serveur en utilisant la syntaxe du modèle SAM

  • Déploiement automatisé de l'application à l'aide de la CLI CloudFormation

Comme d'habitude, tout le code de cet article est disponible suron GitHub.