Spike investigativo — AWS Lambda

Cássio Paixão
SkyHub Labs
7 min readMay 19, 2017

--

Na SkyHub lidamos com muitas integrações de APIs, tanto com serviços externos quanto entre nossos próprios microsserviços. Ao discutir sobre algumas rotinas que desejamos extrair para microsserviços, cogitamos disponibilizar algumas delas pelo AWS Lambda. Ainda que soubéssemos dos objetivos macro do serviço, faltava conhecimento mais sólido para avaliar a viabilidade de seu uso. Fiquei então responsável por esse spike.

Como a investigação foi bem pautada em conseguir responder algumas perguntas à equipe, seguirei a mesma estrutura neste artigo. Começando pela pergunta de praxe:

O que é o AWS Lambda?

O AWS Lambda é um serviço FaaS (Function as a Service), cujo objetivo é permitir que o desenvolvedor foque no desenvolvimento de uma função específica, deixando para o provedor do serviço a tarefa de gerenciar os recursos necessários à sua execução.

Cada função Lambda é executada independentemente em um ambiente criado pelo serviço. Esse ambiente pode ser um já ativo ou recém-criado. Não temos controle sobre isso, logo, não confie no valor de variáveis que podem ter conteúdo diferente em execuções contíguas.

Como escrever e colocar uma função no ar?

O código da função deve ser escrito em C#, Node.js (Edge 4.3, 4.3 ou 6.10), Java 8 ou Python 2.7. O código deve ser inserido em um arquivo .ZIP com todas as suas dependências, e então enviado à AWS.

Há 3 formas de enviar nova versão de uma função:

  1. Edição direto na tela de configuração da função (para Python e Node.js);
  2. Envio de arquivo .ZIP pela tela de configuração da função; ou
  3. Especificação de arquivo .ZIP disponível no S3.

Done! Deploy realizado! :D

Ok. Not so easy… É necessário definir um IAM role sob o qual a função será executada (o qual define que serviços essa função poderá acessar, por exemplo). Durante o deploy, é possível definir também variáveis de ambiente, cujos valores podem ser protegidos por uma chave AWS KMS.

Ah! Em tempo: pelo AWS CLI (Command Line Interface) é possível realizar tanto deploy quanto demais configurações que serão citadas nesse artigo. A própria documentação sugere algumas opções de integração para automatizar o deploy. A abordagem de envio, seleção e cliques foi seguida apenas pelo fim de investigação do serviço.

Como disparar a execução de uma função?

Uma função pode ter um ou mais triggers, desde eventos em serviços da AWS a eventos disparados por uma aplicação própria. Dentre outros serviços da AWS que podem servir de triggers, temos mensagens no SQS, API Gateway, S3 e DynamoDB. Como nosso interesse era processar requisições de alguns endpoints no Lambda, focarei no uso do serviço API Gateway como trigger para uma função.

API Gateway como trigger

Ao criar um endpoint no serviço API Gateway, basta selecionar que a request será tratada por uma função AWS Lambda, e então selecionar a função criada. Para que a função trate toda a requisição à API, é necessário marcar a opção LAMBDA_PROXY. Desse modo, a função terá acesso a detalhes da request (como headers e query_string), bem como poderá especificar headers, statusCode e conteúdo de resposta.

Habilitada a opção LAMBDA_PROXY, um dos parâmetros passados à função Lambda será um hash com dados da request. Segue a estrutura desse hash:

{
"resource": "Resource path",
"path": "Path parameter",
"httpMethod": "Incoming request's method name"
"headers": {Incoming request headers}
"queryStringParameters": {query string parameters }
"pathParameters": {path parameters}
"stageVariables": {Applicable stage variables}
"requestContext": {Request context, including authorizer-returned key-value pairs}
"body": "A JSON string of the request payload."
"isBase64Encoded": "A boolean flag to indicate if the applicable request payload is Base64-encode"
}

A função Lambda deve retornar também um hash, especificando a resposta à requisição feita ao endpoint. Este hash deve seguir a seguinte estrutura:

{
"statusCode": httpStatusCode,
"headers": { "headerName": "headerValue", ... },
"body": "..."
}

Como lidar com diferentes versões de uma função?

Não há versionamento de código no AWS Lambda, terá de usar Git, SVN, CVS ou qualquer outro aparato com esse foco para gerenciar versionamento de código. O que o AWS Lambda oferece é um aparato para gerenciar versões de deploy. Nesse contexto, um deploy seria uma “tupla” (código, configurações) salva. Novo código? Deploy criado! Alterou variáveis de ambiente? Deploy criado! Mudou o IAM role? Deploy criado!

Com base nos deploys, é possível definir versões, que irão apontar para um deploy. A versão $LATEST está sempre disponível, e aponta para o último deploy criado. Quando se cria uma versão, ela irá apontar para o deploy que $LATEST aponta no momento.

É possível também definir apontadores (ou alias) para as versões, o que ajuda bastante para definir que versões devem estar em dev, staging ou produção. Tentando colocar todas essas entidades num “diagrama”:

             ╭------------------------- versão 1 (alias prod)
| ╭------------- versão 2
| | ╭------- versao 3 (alias staging)
| | | ╭- $LATEST
| | | |
(1)---(2)---(3)---(4)---(5)---(6)---(7)

No caso representado acima, foram realizados 7 deploys. Após o 3º deploy, foi criada uma versão, e outras mais após o 5º e 6º deploys. Foram então criados dois alias: prod, apontando para a versão 1 (número sequencial, não é possível alterar); e staging, apontando para a versão 3.

Vale lembrar que esse recurso apenas gerencia VERSÕES. No caso acima, temos acesso apenas aos deploys (3), (5), (6) e (7). Caso precise consultar alguma configuração que tenha sido realizada no deploy (2), ou código que estava presente no deploy (4), terá de recorrer a outras ferramentas que tenham mantido tracking desses dados.

Utilizando esse sistema de versões e alias, conseguimos fazer um rollback sem grandes mudanças, bastando alterar o alias prod para a última versão que executava com sucesso.

Nota: Ao configurar a função em um endpoint no API Gateway, é necessário definir qual versão (ou alias) da função será disparada. Por isso o rollback fica tão melzinho na chupeta. :)

Como garantir que a função está “no ar”?

Tanto métricas quanto logs de execução podem ser facilmente monitorados pelo AWS CloudWatch. As métricas disponíveis são: Invocations, Errors, Dead Letter Error (DLE), Duration, Throttles e Iterator Age. Também pelo CloudWatch é possível configurar alarmes com base nas métricas.

Para definir bons limites para os alarmes, vale analisar previamente qual a taxa de erros aceitável e quantas execuções são esperadas por período de tempo, visto que a métrica de erros é um número absoluto, e não percentual. Ainda sobre erros, vale atentar para a possibilidade de configurar uma Dead Letter Queue: quando ativada, o AWS Lambda adiciona uma notificação no SQS ou SNS (o que preferir) sempre que houver um erro na execução da função. Essa notificação irá conter todo o payload da chamada, permitindo futuras investigações dos erros.

Bem, configurada uma DLQ, é mais um serviço... mais um ponto para falhas… e aí já vale monitorar também a métrica DLEs (Dead Letter Errors), quantidade de falhas ao enviar payload do erro para a DLQ. Para quem estiver curioso, segue alguns casos em que isso pode ocorrer: uso excessivo de memória, erros de permissão e timeout.

Ernesto Marquez comenta bem sobre o monitoramento dessas métricas em artigo da Concurrency Labs. É um ótimo complemento à documentação oficial das mesmas.

Quanto custa deixar essas funções executando?

Duas variáveis influenciam o custo de execuções no AWS Lambda:

  • Memória alocada para execução da função (definido nas configurações)
  • Tempo de execução das funções (tempo de cada execução é arredondado para cima, garantindo precisão de 100ms)

Os primeiros 400.000 GB-seconds por mês são gratuitos, suficientes para aproximadamente 1 milhão de execuções de 300ms (com 128MB de memória alocada), ou 800 mil execuções de 100ms (com 512MB).

Sabendo que a cobrança ocorre na medida GB-seconds (produto memória alocada * tempo de execução), e considerando que diversas execuções de uma função trazem o summary abaixo, o que poderia ser feito para redução de custo?

Duration: 702.16 ms Billed Duration: 800 ms Memory Size: 512 MB Max Memory Used: 15 MB

A primeira ação poderia ser reduzir a quantidade de memória alocada para 128 MB (lembre-se que o custo é por memória alocada, não por quantidade de memória utilizada). E uma ação que pode vir a ser priorizada é uma tentativa de otimizar a execução: os 2.16ms de tempo excedente sobre 700ms é responsável pela cobrança de 100ms a mais. Se o tempo médio for reduzido para abaixo de 700ms, têm-se 12,5% de economia ;)

Quais as restrições para criar uma função?

Ok. Essa pergunta foi um pouco forçada. Mas não encontrei um meio melhor de expressar a minha pulga atrás da orelha: “Tudo certo até agora. Mas… é um mundo sem lei? Pagando os GB-seconds posso fazer o que quiser na minha função?”

Bem… quase. O serviço gerencia a alocação dos recursos para que a função seja executada como se espera, mas impõe alguns limites:

  • Timeout máximo: 300s
  • Uso de disco (/tmp): 512MB
  • Execuções simultâneas: 400
  • Tamanho máximo do payload de entrada: 6MB
  • Tamanho máximo do payload de retorno: 6MB
  • Tamanho máximo do arquivo .ZIP: 50MB
  • Tamanho máximo dos arquivos descompactados: 250MB

Quanto ao tamanho máximo dos payloads: cuidado ao lidar com recebimento de anexos ou geração de relatórios, manter arquivos no Amazon S3 ou outro serviço pode se tornar necessário. Quanto ao tamanho máximo dos arquivos, vale lembrar que deve ser contabilizado sua aplicação mais as bibliotecas que ela usa. Em alguns casos, esses limites podem vir a parecer bem curtos.

E agora?

A investigação realizada contribuiu muito para nosso entendimento sobre a ferramenta. Deu base para começar a planejar a adoção da mesma em um próximo microsserviço a ser desenvolvido, ocasião na qual provavelmente dúvidas mais direcionadas ainda surgirão.

Você também está considerando o uso do AWS Lambda? Se sim, que outras dúvidas surgiram enquanto o avaliava? Use o campo de comentários… e seguimos com essa investigação de modo colaborativo ;)

--

--