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.