Gerenciando a arquitetura sem servidor multi-ambiente usando o AWS – uma investigação

No 2PAx, estamos migrando nossa API REST de um aplicativo monolítico tradicional para uma arquitetura sem servidor . Antes de dar esse passo, queríamos saber como isso afetaria o desenvolvimento local e o que seria necessário para manter nossa atual estratégia de implantação envolvendo vários ambientes. Este artigo é um resumo de nossa investigação, incluindo as abordagens que tentamos e os obstáculos que encontramos ao longo do caminho.

Nota: Usaremos o termo ‘ambiente’ e não ‘estágio’ ao nos referirmos a nossas configurações lógicas de desenvolvimento, preparação e produção, a fim de evitar confusão com a nomenclatura de recursos da AWS.

Objetivo

Nosso pipeline contém três ambientes diferentes – desenvolvimento, preparação e produção. As confirmações de código-fonte mescladas no mestre devem ser implementadas automaticamente no ambiente de desenvolvimento, enquanto a transição para os outros dois ambientes requer aprovação manual. Essa estratégia foi fácil de alcançar com o backend de aplicativo único que usamos até agora (um único binário Go compilado), mas apresenta alguns desafios com uma arquitetura sem servidor, pois agora precisamos implantar vários recursos diferentes.

Os três ambientes em nosso pipeline: desenvolvimento, encenação e produção.

Aplicativos da Web sem servidor na AWS

Antes de entrarmos nos detalhes da implementação, vamos examinar brevemente os componentes envolvidos em uma configuração típica sem servidor e quais conceitos a AWS fornece para lidar com vários ambientes.

As solicitações de clientes são roteadas e validadas pelo AWS Gateway antes de serem manipuladas pelo AWS Lambda.

Em um nível básico, uma solicitação de entrada do cliente será roteada por meio do API Gateway, de uma função de autoria do Lambda e, finalmente, de um manipulador de terminal do Lambda.

O API Gateway cuida da validação dos parâmetros e do corpo da solicitação, do armazenamento em cache, da limitação de taxa, etc., e pode ser convenientemente configurado usando uma especificação OpenAPI, anteriormente conhecida como Swagger. Isso reduz a quantidade de código clichê e ajuda a manter a lógica real manipulando a solicitação simples.

O manipulador do nó de extremidade não deve se preocupar em autenticar ou autorizar o usuário, e é por isso que ambos são manipulados por uma função do autor do Lambda, atuando como um middleware de autenticação (validando JSON Web Tokens no nosso caso) e retornando uma política do IAM. Finalmente, a solicitação validada e autorizada é manipulada por outra função do Lambda e seu resultado mapeado pelo Gateway de API antes de retornar a resposta ao cliente.

Ao contrário do exemplo simplificado acima, nossa API apresenta vários pontos de extremidade. Nossa abordagem atual é agrupar endpoints estreitamente relacionados no mesmo manipulador de endpoint, semelhante a manipuladores de serviços pequenos, em vez de usar uma função Lambda por endpoint. Dependendo de suas necessidades ou preferências, você pode acabar usando uma abordagem diferente.

A pilha inteira é definida em um modelo usando SAM, abreviação de Serverless Application Model , uma extensão do CloudFormation , que é implantada (por CodePipeline no nosso caso) nos ambientes de desenvolvimento, preparação e produção mencionados anteriormente.

Gateway de API

A AWS oferece maneiras poderosas de lidar com diferentes versões do mesmo recurso do Gateway de API por meio do uso de ‘estágios’. Veja o que a documentação tem a dizer:

Os estágios permitem um controle de versão robusto da sua API. Por exemplo, você pode implantar uma API em um estágio de teste e um estágio de prod e usar o estágio de teste como uma compilação de teste e usar o estágio de produção como uma compilação estável. Depois que as atualizações passarem no teste, você poderá promover o estágio de teste para o estágio prod. A promoção pode ser feita reimplementando a API no estágio prod ou atualizando um valor de variável de estágio do nome do estágio de teste para o de prod.

As variáveis ??de estágio mencionadas na citação acima permitem que você use a API com configurações diferentes para cada estágio. Por exemplo, você poderia usar diferentes ARNs da função Lambda ou passar valores de configuração para as funções. Mais uma vez a documentação oficial fornece mais detalhes.

Lambda

As próprias funções do Lambda têm seu próprio conceito de versionamento. Outra olhada na documentação oficial revela o seguinte:

Os aliases permitem que você abstraia o processo de promoção de novas versões de função do Lambda na produção a partir do mapeamento da versão da função do Lambda e sua origem de eventos.

Em contraste, em vez de especificar a função ARN, suponha que você especifique um ARN de alias na configuração de notificação (por exemplo, ARN de alias de PROD). À medida que você promove novas versões da sua função do Lambda em produção, você só precisa atualizar o alias do PROD para apontar para a versão estável mais recente. Você não precisa atualizar a configuração de notificação no Amazon S3.

Com aliases, você ganha um controle refinado sobre a promoção de novas versões do Lambda para estágios específicos. Uma ressalva que vale a pena notar – como as funções do Lambda são imutáveis ??depois de publicadas, você pode querer verificar como isso afeta as configurações específicas do ambiente, como cadeias de banco de dados e outros parâmetros, consulte esses artigos .

CloudFormation

O CloudFormation é uma ferramenta de gerenciamento de infraestrutura e uma especificação que permite definir um grupo de recursos da AWS agrupados em uma ‘pilha’. Os recursos são definidos em modelos escritos em json ou yaml e a ferramenta cloudformation permite que a infraestrutura seja criada, modificada por meio de conjuntos de mudanças e destruída.

Além disso, se você definir um modelo no qual as funções do Lambda apontam para o código local, você poderá usar o comando package para enviar o manipulador local para o S3 e gerar um modelo que aponte para o URI carregado.

Os comandos deploy para executar atualizações, que tentarão migrar sua pilha de destino para um estado que corresponda ao modelo fornecido, incluindo a versão mais recente dos manipuladores do Lambda que agora apontam para os artefatos do S3.

  • package irá analisar o arquivo de modelo (yaml ou json), encontrar as funções com um codeUri que aponta para um manipulador de sistema de arquivos local, pacote e enviá-los para o S3 e, em seguida, codeUris um modelo empacotado onde o codeUris agora aponta para artefatos S3.
  • deploy carrega seu modelo empacotado para o CloudFormation, cria um conjunto de mudanças e o executa.

Se tudo correr bem, sua pilha será atualizada para a especificação exata do modelo que você forneceu. Caso algo dê errado, o CloudFormation irá reverter todas as alterações e reverter a pilha para o estado anterior.

Se você estiver interessado em uma ferramenta semelhante que suporte múltiplos provedores, veja Terraform .

SAM

O Modelo de Aplicativo sem Servidor é uma tentativa de simplificar a definição de aplicativos sem servidor estendendo a especificação do CloudFormation. Adiciona três novos tipos de recursos:

  • AWS::Serverless::Function
  • AWS::Serverless::Api
  • AWS::Serverless::SimpleTable

Nenhuma das opções acima são novas primitivas da AWS, e sim wrappers em torno de recursos existentes do CloudFormation.

O SAM tem como objetivo abstrair a complexidade / verbosidade de ter que definir seus próprios estágios, implementações, permissões, postes, etc. Mas, como é uma nova extensão, essas abstrações podem vazar quando você não espera, ou, inversamente, parece muito opaco quando você precisa de mais controle.

Felizmente, há trabalho em andamento para introduzir uma série de recursos importantes, incluindo suporte de primeira classe para autorizadores personalizados, CORS e planos de uso.

Ambiente de desenvolvimento local

Para executar e testar aplicativos baseados em SAM localmente, o awslabs lançou sam local , uma CLI que pode ser usada para chamar diretamente as funções do Lambda ou iniciar um gateway de API simulado local a partir de um modelo de SAM que invocará suas funções para manipular solicitações de entrada. Ele faz isso executando seus manipuladores locais em contêineres do Docker que imitam o ambiente de execução real do Lambda. Caso você esteja se perguntando, ele também vem com suporte para o suporte oficial recentemente anunciado para o Go no AWS Lambda.

A ferramenta suporta o recarregamento a quente, embora no caso do Go você ainda precise recompilar o binário e deve se lembrar de direcionar o GOOS=linux .

Aqui está como você inicia um gateway de API local:

 sam local start-api --template api-template.yaml

No momento da escrita, sam local vem com algumas limitações, a saber:

  • faltando suporte para autorizadores personalizados, espero que isso mude depois que o SAM apresentar suporte de primeira classe para os autores.
  • erro impedindo que arquivos externos de yaml do OpenAPI funcionem (embora o JSON pareça estar funcionando de acordo com vários relatórios de usuários)

Pilha única vs pilha multi

As pessoas ainda estão descobrindo como usar melhor essas ferramentas no mundo real, conforme evidenciado pelos problemas em que os usuários solicitam consultoria de modelagem de infraestrutura. Um exemplo de área contenciosa é o gerenciamento de diferentes ambientes, como desenvolvimento, encenação, produção etc.

Existem duas abordagens gerais para esse problema, usando uma única pilha ou várias pilhas .

O API Gateway e o Lambda exigem configurações diferentes em configurações de pilha única e múltipla.

A opção de pilha única compartilha suas funções API Gateway e Lambda em todos os ambientes, aproveitando estágios de API, variáveis ??de palco e aliases do Lambda, enquanto a abordagem de pilha múltipla usa uma pilha por ambiente, cada um com seus próprios recursos, ignorando a indireção dos estágios da API Aliases de lambda para separar ambientes.

Inicialmente, analisamos a abordagem de pilha única, que parecia mais idiomática porque fazia uso total dos conceitos que o API Gateway e o Lambda nos fornecem.

Infelizmente, na prática, isso não parece funcionar tão bem. O suporte a vários estágios no SAM ainda não está claro e é peculiar , parece difícil gerenciar vários estágios do API Gateway e aliases do Lambda em um único modelo. Também percebemos que seus recursos da AWS estavam extremamente acoplados em vários ambientes, e não simplesmente replicados.

Isso nos levou a fazer mais pesquisas, olhando além da documentação oficial.

Nós nos deparamos com essa palestra de Chris Munns, um defensor do desenvolvedor do AWS Serverless, que recomenda usar uma única pilha se você for uma equipe pequena e estiver apenas começando e uma pilha múltipla se tiver uma configuração mais complexa com várias equipes permissões ou simplesmente preferir uma melhor separação de recursos.

O engenheiro de Lambda Jacob Fuss, por outro lado, é mais direto em seu endosso de multi stack:

Eu não recomendo que você (ou qualquer pessoa) use aliases para vários ambientes. A maior preocupação que tenho com isso é que você está correndo o risco de impactar o prod com uma mudança para testar ou dev. Você está criando um raio de explosão maior caso algo dê errado com uma implantação. Minha outra preocupação seria em torno da segurança de suas funções. Você pode ter que adicionar credenciais ou políticas específicas para dev ou testar quais serão ou poderiam ser replicadas em prod. Minha sugestão é dividir dev, test e prod em pilhas separadas do CloudFormation para que cada ambiente seja isolado um para o outro. Você só precisa gerenciar o modelo do CloudFormation e implantá-lo através do seu sistema de CI / CD em um nível de ambiente. Você ainda estará gerenciando apenas uma função do Lambda (por meio do SAM), mas essa configuração reduzirá o raio de explosão das implantações e isolará as funções e os recursos do ambiente diferente.

No final, decidimos optar por uma abordagem multi stack, uma por ambiente, gerenciada por um único modelo.

A chave para uma abordagem de pilha múltipla é reutilizar o mesmo modelo de SAM e contar com parâmetros de modelo para segmentar cada ambiente. Isso garante que as pilhas tenham exatamente a mesma aparência em todos os ambientes.

A principal desvantagem aparente é a desconexão entre as versões do Lambda em diferentes ambientes. Por exemplo, mesmo que o mesmo código exato possa ser executado nas pilhas de preparação e de produção, os Lambdas reais serão recursos diferentes no AWS, com versões diferentes. Sabemos apenas que eles estão executando o mesmo código porque usamos o mesmo modelo empacotado para implantar em ambos os ambientes, e esse modelo apontava para os mesmos artefatos no S3 e, assim, criamos versões do Lambda com o mesmo código nas duas pilhas.

Além disso, o modelo pode acabar se tornando mais complexo caso desejemos variar as propriedades do recurso por ambiente, por exemplo, ter uma instância RDS de tamanho diferente.

Colocando tudo junto

Tendo decidido sobre uma abordagem multi stack nossa configuração de CI atual permaneceu bastante simples: usamos CodePipeline para assumir automaticamente a última confirmação em master, executar testes unitários, manipuladores de compilação e implantar as novas versões na pilha de desenvolvimento antes de aprovar manualmente implantações e produção pilhas.

O estágio de construção usa o CodeBuild para testar e construir os manipuladores e para executar o comando aws cloudformation package , que gera um modelo empacotado:

 aws cloudformation package --template-file stack-template.yaml --s3-bucket <s3-bucket> --output-template-file empacotado-template.yaml

O modelo empacotado é então passado para os próximos estágios, onde é usado pela integração do CloudFormation para implantar em cada ambiente, fornecendo o parâmetro StageName via parameter-overrides :

 aws cloudformation implantar --template-file empacotado-template.yaml --stack-name <StackDev | StackStaging | StackProd> --capabilities CAPABILITY_NAMED_IAM --parameter-overrides StageName = <Dev | Preparação | Prod>

Conclusão

Depois de passar algum tempo nos familiarizando com mais conceitos da AWS do que jamais desejamos, tentando uma abordagem de pilha única com o SAM e navegando em vários problemas, postagens de blog e vídeos do GitHub, descobrimos que a abordagem de pilha múltipla era a melhor maneira de nós para alcançar nossa meta multi-ambiente. Esperamos que este artigo ajude alguém a enfrentar questões semelhantes. Enquanto isso, vamos ficar de olho nos problemas mencionados acima.

O que nós não cobrimos

  • Teste de integração através de uma pilha dedicada, como parte do nosso pipeline CI.
  • Troca de tráfego para implantações através de aliases do Lambda. Observe que os aliases ainda são uma boa maneira de controlar as implantações em um ambiente, não apenas para a separação de ambientes.

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *