Construindo aplicações Serverless com AWS SAM

Olá pessoal, tudo bem?

Hoje vamos falar sobre mais uma ferramenta que gosto muito e que facilita bastante a criação de aplicações serverless, o AWS Serverless Application Model (AWS SAM).

O que é o SAM?

O AWS SAM é um framework que oferece novos elementos para o CloudFormation específicos para serverless. Na realidade, ele é um superset do mesmo, pois estes novos componentes foram criados utilizando os transforms do CloudFormation.

Além destes novos elementos, o SAM fornece também uma CLI, utilizada para gerenciar todo o ciclo de vida das aplicações, desde a criação até o deploy.

O que há de novo no template?

Para ilustrar algumas diferenças, abaixo segue o exemplo de um template criado usando o SAM.

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Sample SAM Template for SamExample

Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function 
    Properties:
      CodeUri: hello_world/
      Handler: app.lambda_handler
      Runtime: python3.7
      Events:
        HelloWorld:
          Type: Api 
          Properties:
            Path: /hello
            Method: get

Outputs:
  HelloWorldApi:
    Description: "API Gateway endpoint URL for Prod stage for Hello World function"
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"
  HelloWorldFunction:
    Description: "Hello World Lambda Function ARN"
    Value: !GetAtt HelloWorldFunction.Arn
  HelloWorldFunctionIamRole:
    Description: "Implicit IAM Role created for Hello World function"
    Value: !GetAtt HelloWorldFunctionRole.Arn

O exemplo acima é o template padrão do AWS SAM, criado com o projeto. Por ser simples, facilita a visualização da nova estrutura.

Como podemos ver, a primeira diferença no SAM está na linha 2, o Transform que ele utiliza para gerar o template final do CloudFormation.

Na linha 7, vemos um novo tipo de resource, o AWS::Serverless::Function. Como veremos em mais detalhes a seguir, todos os tipos do SAM usam o namespace AWS::Serverless::*.

O restante é praticamente idêntico ao CloudFormation, suportando funções intrínsecas, importvalue, e as demais features do mesmo.

Para entendermos um pouco melhor sobre sua estrutura, abaixo falaremos um pouco sobre cada novo tipo fornecido pelo framework.

Function – AWS::Serverless::Function

HelloWorldFunction:
    Type: AWS::Serverless::Function 
    Properties:
      CodeUri: hello_world/
      Handler: app.lambda_handler
      Runtime: python3.7
      Events:
        HelloWorld:
          Type: Api 
          Properties:
            Path: /hello
            Method: get

Como o nome sugere, Function representa Lambda functions. As propriedades que aparecem no exemplo representam:

  • CodeUri: O local onde está o código da função Lambda;
  • Handler: Quem é o handler que será o ponto de entrada da Lambda;
  • Runtime: Qual a runtime da Lambda;
  • Events: Aqui podemos definir quem é o gatilho (ou gatilhos) que irá disparar a função. Atualmente o SAM suporta S3, SNS, DynamoDB, Kinesis, SQS, API, Schedule, CloudWatchEvents, CloudWatchLogs, IoTRules, Cognito e Alexa Skills.

No exemplo acima, a Lambda será disparada através de um API Gateway, criada durante o deploy, quando efetuarmos um GET no path /hello. Se tivermos mais de uma função no template, que tenha API como fonte de eventos, quando fizermos o deploy será criada apenas uma API Gateway contendo todos os endpoints relacionados a estes eventos.

API – AWS::Serverless::API

HelloWorldAPI:
  Type: AWS::Serverless::Api
  Properties:
    StageName: production
    DefinitionUri: mydefinition.yml
    EndpointConfiguration: REGIONAL
    Variables:
      ConnectionString: ProductionConnectionString

Se quisermos utilizar o Swagger para definirmos a estrutura da API Gateway, podemos usar o tipo API. No exemplo, as principais propriedades são:

  • StageName: Define o nome do estágio da API Gateway;
  • DefinitionUri: Uri com a definição do template do swagger;
  • EndpointConfiguration: Tipo do endpoint da API (REGIONAL, PRIVATE ou EDGE);
  • Variables: São as stage variables do estágio da API Gateway;

SimpleTable – AWS::Serverless::SimpleTable

HelloWorldTable:
  Type: AWS::Serverless::SimpleTable
  Properties:
    PrimaryKey:
      Name: productId
      Type: String
    ProvisionedThroughput:
      ReadCapacityUnits: 1
      WriteCapacityUnits: 1
    TableName: Products

O tipo SimpleTable, é uma versão simplificada de uma tabela do DynamoDB. Simplificada pois é possível definir apenas uma PrimaryKey, e nada mais. Inclusive, podemos remover todo o nodo Properties, que valores default serão usados para criar a tabela.

LayerVersion – AWS::Serverless::LayerVersion

HelloWorldLayer:
  Properties:
    LayerName: HelloWorldLayer
    ContentUri: 's3://<mybucket>/sam-example-layer.zip'
    RetentionPolicy: Delete
    Description: Sam example Layer Version
    LicenseInfo: 'License information'
    CompatibleRuntimes:
      - python2.7
      - python3.6
      - python3.7

LayerVersion representa uma layer que pode ser compartilhada entre as funções Lambda.

Neste componente, podemos evidenciar as propriedades:

  • ContentUri: Onde está localizado o conteúdo da layer;
  • CompatibleRuntimes: Runtimes que são compatíveis com esta layer;
  • RetentionPolicy: Apesar de estar definido como Delete no exemplo, deletando as versões anteriores da layer, o valor padrão é Retain, que faz com que à cada novo deploy, as versões anteriores permaneçam.

Application – AWS::Serverless::Application

HelloWorldApplication:
  Type: AWS::Serverless::Application
  Properties:
    Location:
      ApplicationId: 'arn:aws:serverlessrepo:us-east-1:012345678901:applications/sam-application-example'
      SemanticVersion: 1.0.0
    Parameters:
      StringParameter: SAM Test
    TimeoutInMinutes: 10

Com o tipo Application, podemos aninhar outras aplicações à aplicação que estamos criando. Estas aplicações podem ser do AWS SAR (Serverless Application Repository) ou podem ser templates armazenados no S3. Este componente é bacana, pois facilita a reutilização de aplicações e componentes criadas por nós e outros desenvolvedores.

Sobre as propriedades mostradas no exemplo:

  • Location: Como citei anteriormente, pode ser do SAR ou do S3. No exemplo, apontamos para o SAR. No S3, teríamos algo como:
  • Parameters: Os parâmetros enviados à aplicação;
  • TimeoutInMinutes: Após a transformação, o tipo Application resulta em uma nested stack do CloudFormation. Este parâmetro indica o tempo que o CloudFormation irá esperar pela criação desta stack aninhada, antes de cancelar a operação.

Outros elementos importantes

Globals

Globals:
  Function:
    Timeout: 14
    handler: app.lambda_handler 

É possível definir um elemento Globals no template, onde definimos propriedades que são comuns a todas as funções Lambdas existentes no template.

No exemplo acima, estamos definindo o timeout e o caminho do handler de forma global.

Policies

Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function 
    Properties:
      CodeUri: hello_world/
      Handler: app.lambda_handler
      Runtime: python3.7
      Policies:
        - EC2DescribePolicy: {}
      Events:
        HelloWorld:
          Type: Api 
          Properties:
            Path: /hello
            Method: get

O AWS SAM possui diversos templates prontas de policies, para facilitar a construção das aplicações. Ela também permite a utilização de políticas gerenciadas e políticas inline (criadas no template), possibilitando bastante flexibilidade ao gerirmos o controle de permissões.

Para saber mais sobre estas templates, basta clicar aqui para acessar a documentação.

Criando um projeto

Falando um pouco agora sobre o SAM CLI, o primeiro passo é instalarmos o mesmo. A instalação pode ser ser feita seguindo está documentação: Instalando o AWS SAM CLI.

Em seguida, podemos criar um projeto com o comando:

sam init --runtime python3.7 --name sam-example

Basicamente, no comando acima ele irá criar um app chamado sam-example, usando o python3.7. É possível remover todos os argumentos do comando, executando apenas sam init, e neste caso ele criar uma aplicação chamada sam-app, usando nodejs10.* como runtime.

Estrutura inicial de uma aplicação

Sobre a estrutura inicial do projeto, vale comentar um pouco sobre:

  • events/*: Eventos de exemplo para serem usados em teste;
  • hello_world/*: Contém o handler e os arquivos relacionados à Lambda HelloWorld;
  • tests/*: Diretório para incluirmos testes;
  • template.yaml: Template do AWS SAM.

Compilando o projeto

Para compilarmos um projeto do SAM, executamos o comando:

sam build

Como produto da compilação, o SAM gera um diretório chamado .aws-sam no root do projeto. Ali ele copia o arquivo de template, e cria um diretório para a função incluindo todas as dependências para executá-la.

Artefatos da compilação do SAM

Testando as aplicações localmente

Os testes locais são feitos através do comando sam local. Nele temos algumas opções de execução:

  • start-lambda: Inicia a lambda localmente em um endpoint, permitindo que executemos ela através do SDK ou do CLI.
  • start-api: Simula uma API, permitindo que efetuemos testes usando HTTP;
  • invoke: Executa a lambda localmente, apenas uma vez.

Existe outra opção no comando local, chamada generate-event. Esta opção cria exemplos de evento das fontes suportadas pelo SAM (S3, API, Kinesis, etc), para usarmos nos testes.

Publicando a aplicação

Quando a aplicação estiver pronta para ser publicada, precisamos executar dois passos:

1) Empacotar a aplicação

Para empacotar a aplicação, utilizamos a instrução:

sam package --s3-bucket sam-example

A execução deste comando irá gerar um pacote, enviá-lo para o S3 e fazer uma cópia do template, substituindo o sourceUri da função pelo caminho criado no S3. O novo sourceUri ficará similar ao exemplo abaixo (linha 4):

Resources:
  HelloWorldFunction:
    Properties:
      CodeUri: s3://sam-example/80d971cd7ef24874575dd3f2563888c7
      Events:
        HelloWorld:
          Properties:
            Method: get
            Path: /hello
          Type: Api
      Handler: app.lambda_handler
      Runtime: python3.7
    Type: AWS::Serverless::Function

2) Publicar a aplicação

Por fim, para publicarmos a aplicação com o SAM, executamos o comando:

sam deploy --template-file <template-file-location>.yaml --stack-name sam-example

Obrigatoriamente precisamos informar ambos os argumentos:

  • template-file: Template gerado pelo comando package (com o sourceUri modificado);
  • stack-name: O nome da stack do CloudFormation que será criada/atualizada.

Conclusão

Acredito que o framework mais conhecido e utilizado para este tipo de aplicação seja o Serverless Framework. Contudo, para aqueles que forem trabalhar com AWS, o SAM também é uma excelente opção para iniciar um projeto. Ele possui algumas de features avançadas, como canary deployment (que não comentei neste post, mas você pode acessar aqui), e executa sobre o CloudFormation.

Um abraço, e até a próxima!


Deixe uma resposta