Introdução ao modelo de aplicativo sem servidor da AWS

Introdução ao modelo de aplicativo sem servidor da AWS

1. Visão geral

Emour previous article, já implementamos um aplicativo full stack serverless no AWS, usando API Gateway para endpoints REST, AWS Lambda para lógica de negócios, bem como um DynamoDB como banco de dados.

No entanto, a implantação consiste em várias etapas manuais, que podem se tornar impraticáveis ​​com a crescente complexidade e o número de ambientes.

Neste tutorial agora, discutiremos como usar oAWS Serverless Application Model (SAM), which enables a template-based description and automated deployment of serverless applications on AWS.

Em detalhes, daremos uma olhada nos seguintes tópicos:

  • Noções básicas do SAM (Serverless Application Model), bem como do CloudFormation subjacente

  • Definição de um aplicativo sem servidor, usando a sintaxe do modelo SAM

  • Implantação automatizada do aplicativo, usando a CLI CloudFormation

2. Fundamentos

Conforme discutidopreviously, a AWS nos permite implementar aplicativos totalmente sem servidor, usando API Gateway, funções Lambda e DynamoDB. Sem dúvida, isso já oferece muitas vantagens em desempenho, custo e escalabilidade.

No entanto, a desvantagem é que precisamos de várias etapas manuais no console da AWS no momento, como criar cada função, fazer upload de código, criar a tabela DynamoDB, criar funções do IAM, criar API e estrutura de API etc.

Para aplicativos complexos e com vários ambientes, como teste, preparo e produção, esse esforço se multiplica rapidamente.

É aqui que o CloudFormation para aplicativos na AWS em geral e o SAM (Serverless Application Model) especificamente para aplicativos sem servidor entram em cena.

2.1. AWS CloudFormation

CloudFormation é um serviço da AWS para o provisionamento automático de recursos de infraestrutura da AWS. Um usuário define todos os recursos necessários em um blueprint (chamado modelo), e a AWS cuida do provisionamento e da configuração.

Os seguintes termos e conceitos são essenciais para entender o CloudFormation e o SAM:

A template is a description of an application, como deve ser estruturado em tempo de execução. Podemos definir um conjunto de recursos necessários, bem como a forma como esses recursos devem ser configurados. O CloudFormation fornece uma linguagem comum para definir modelos, suportando JSON e YAML como um formato.

Resources are the building blocks in CloudFormation. Um recurso pode ser qualquer coisa, como RestApi, Stage of a RestApi, Batch Job, tabela DynamoDB, instância EC2, interface de rede, função IAM e muito mais. The official documentation lista atualmente cerca de 300 tipos de recursos para CloudFormation.

A stack is the instantiation of a template. CloudFormation cuida do provisionamento e configuração da pilha.

2.2. Modelo de aplicativo sem servidor (SAM)

Como tantas vezes, o uso de ferramentas poderosas pode se tornar muito complexo e impraticável, o que também é o caso do CloudFormation.

Por isso, a Amazon introduziu o 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.

O SAM é baseado na sintaxe do modelo CloudFormation, para que possamos definir nosso modelo usando a sintaxe simples do SAM, e o CloudFormation processará ainda mais esse modelo.

Mais detalhes estão disponíveisat the official GitHub repository, bem como emAWS documentation.

3. Pré-requisitos

Para o tutorial a seguir, precisaremos de uma conta AWS. A free tier account deve ser suficiente.

Além disso, precisamos ter o AWS CLIinstalled.

Por fim, precisamos de um Bucket S3 em nossa região, que pode ser criado via AWS CLI com o seguinte comando:

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

Embora o tutorial useexample-sam-bucket a seguir, esteja ciente de que os nomes dos intervalos devem ser exclusivos, portanto, você deve escolher seu nome.

Como um aplicativo de demonstração, usaremos o código deUsing AWS Lambda with API Gateway.

4. Criação do modelo

Nesta seção, criaremos nosso modelo SAM.

Vamos primeiro dar uma olhada na estrutura geral, antes de definir os recursos individuais.

4.1. Estrutura do Template

Primeiro, vamos dar uma olhada na estrutura geral do nosso modelo:

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

Como podemos ver, o modelo consiste em um cabeçalho e um corpo:

O cabeçalho especifica a versão do modelo CloudFormation (AWSTemplateFormatVersion), bem como a versão do nosso modelo SAM (Transform). Também podemos especificar umDescription.

O corpo consiste em um conjunto de recursos: cada recurso possui um nome, um recursoType e um conjunto deProperties.

A especificação SAM suporta atualmente três tipos:AWS::Serverless::Api,AWS::Serverless::Function, bem comoAWS::Serverless::SimpleTable.

Como queremos implantar nossoexample application, temos que definir umSimpleTable, doisFunctions, bem como umApi em nosso corpo-modelo.

4.2. Definição da Tabela DynamoDB

Vamos definir nossa tabela DynamoDB agora:

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

Precisamos apenas definir duas propriedades para nossoSimpleTable: o nome da tabela, bem como uma chave primária, que é chamadaide tem o tipoNumber no nosso caso.

Uma lista completa de propriedadesSimpleTable suportadas pode ser encontradain the official specification.

Nota: Como só queremos acessar a tabela usando a chave primária, oAWS::Serverless::SimpleTable é suficiente para nós. Para requisitos mais complexos, o tipo de CloudFormation nativoAWS::DynamoDB::Table pode ser usado.

4.3. Definição das Funções Lambda

A seguir, vamos definir nossas duas funções:

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

Como podemos ver, cada função tem as mesmas propriedades:

Handler defines the logic of the function. Como estamos usando Java, é o nome da classe incluindo o pacote, em conexão com o nome do método.

Runtime defines how the function was implemented, que é Java 8 em nosso caso.

Timeout defines how long the execution of the code may take at most antes de AWS encerrar a execução.

MemorySizedefines the size of the assigned memory in MB. É importante saber que a AWS atribui recursos de CPU proporcionalmente aMemorySize. Portanto, no caso de uma função com uso intensivo de CPU, pode ser necessário aumentarMemorySize, mesmo se a função não precisar de tanta memória.

CodeUridefines the location of the function code. Atualmente faz referência à pasta de destino em nosso espaço de trabalho local. Quando carregamos nossa função posteriormente usando CloudFormation, obteremos um arquivo atualizado com uma referência a um objeto S3.

Policiescan hold a set of AWS-managed IAM policies or SAM-specific policy templates. Usamos as políticas específicas do SAMDynamoDBCrudPolicy paraStorePersonFunctioneDynamoDBReadPolicy paraGetPersonByPathParamFunctioneGetPersonByQueryParamFunction.

Environmentdefines environment properties at runtime. Usamos uma variável de ambiente para armazenar o nome de nossa tabela DynamoDB.

Eventscan hold a set of AWS events, which shall be able to trigger the function. Em nosso caso, definimos umEvent do tipoApi. A combinação única depath, um HTTPMethod e umRestApiId vincula a função a um método de nossa API, que definiremos na próxima seção.

Uma lista completa de propriedadesFunction suportadas pode ser encontradain the official specification.

4.4. Definição de API como arquivo Swagger

Depois de definir a tabela e as funções do DynamoDB, agora podemos definir a API.

A primeira possibilidade é definir nossa API inline usando o formato 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

NossoApi  tem três propriedades:StageNamedefine o estágio da API,EndpointConfiguration define se a API é regional ou otimizada por borda eDefinitionBody não contém a estrutura real da API.

EmDefinitionBody, definimos três parâmetros: a versãoswagger  como“2.0”, ainfo:title: como“TestAPI”, bem como um conjunto depaths .

Como podemos ver, opaths representa a estrutura da API, que tivemos que definir manualmentebefore. Ospaths no Swagger são equivalentes aos recursos no Console da AWS. Assim, cadapath pode ter um ou mais verbos HTTP, que são equivalentes aos métodos no console da AWS.

Cada método pode ter um ou mais parâmetros, bem como um validador de solicitação.

A parte mais interessante é o atributox-amazon-apigateway-integration, que é uma extensão específica da AWS para Swagger:

uri especifica qual função Lambda deve ser chamada.

responses especifica regras de como transformar as respostas retornadas pela função. Como estamos usando a Integração Lambda Proxy, não precisamos de nenhuma regra específica.

type define que queremos usar Integração Lambda Proxy e, portanto, temos que definirhttpMethod para“POST”, pois é isso que as funções Lambda esperam.

Uma lista completa de propriedadesApi suportadas pode ser encontradain the official specification.

4.5. Definição de API implícita

Uma segunda opção é definir a API implicitamente nos recursos da função:

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

Como podemos ver, nosso modelo é um pouco diferente agora: Não há maisAWS::Serverless::Api resource.

No entanto, CloudFormation pega os atributosEvents do tipoApi como uma definição implícita e cria uma API de qualquer maneira. Assim que testarmos nosso aplicativo, veremos que ele se comporta da mesma forma que ao definir a API explicitamente usando Swagger.

Além disso, existe uma seçãoGlobals, onde podemos definir o nome de nossa API, bem como que nosso endpoint deve ser regional.

Apenas uma limitação ocorre: ao definir a API implicitamente, não podemos definir um nome de estágio. É por isso que a AWS criará um estágio chamadoProd em qualquer caso.

5. Implantação e Teste

Depois de criar o modelo, agora podemos prosseguir com a implantação e o teste.

Para isso, faremos upload de nosso código de função para S3 antes de acionar a implantação real.

No final, podemos testar nosso aplicativo usando qualquer cliente HTTP.

5.1. Upload de código para S3

Em uma primeira etapa, precisamos fazer o upload do código da função para o S3.

Podemos fazer isso chamando CloudFormation por meio da CLI da AWS:

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

Com este comando, acionamos o CloudFormation para obter o código de função especificado emCodeUri:e carregá-lo para S3. O CloudFormation criará um arquivopackaged-template.yml, que tem o mesmo conteúdo, exceto queCodeUri: agora aponta para o objeto S3.

Vamos dar uma olhada na saída da 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. Desdobramento, desenvolvimento

Agora, podemos acionar a implantação real:

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

Como nossa pilha também precisa de papéis IAM (como os papéis das funções para acessar nossa tabela do DynamoDB), devemos reconhecer explicitamente isso especificando–capabilities parameter.

E a saída da CLI deve se parecer com:

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

5.3. Análise de implantação

Após a implantação, podemos revisar o resultado:

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

O CloudFormation listará todos os recursos que fazem parte da nossa pilha.

5.4. Test

Finalmente, podemos testar nosso aplicativo usando qualquer cliente HTTP.

Vamos ver alguns exemplos de comandoscURL que podemos usar para esses testes.

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

No final, podemos limpar removendo a pilha e todos os recursos incluídos:

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

6. Conclusão

Neste artigo, vimos o AWS Serverless Application Model (SAM), que permite uma descrição baseada em modelo e implantação automatizada de aplicativos sem servidor na AWS.

Em detalhes, discutimos os seguintes tópicos:

  • Noções básicas do SAM (Serverless Application Model), bem como o CloudFormation subjacente

  • Definição de um aplicativo sem servidor, usando a sintaxe do modelo SAM

  • Implantação automatizada do aplicativo, usando a CLI CloudFormation

Como de costume, todo o código deste artigo está disponível emon GitHub.