Quando comecei a desenvolver serviços na AWS, pensei que os recursos do CloudFormation poderiam cobrir todas as minhas necessidades. Eu estava errado.
Descobri rapidamente que os ambientes de produção são complexos, com vários casos extremos. Felizmente, o CloudFormation permite extensão por meio de recursos personalizados. Embora recursos personalizados possam ser úteis, a implementação inadequada pode resultar em falhas de pilha, problemas de exclusão e dores de cabeça significativas.
Nesta postagem do blog, exploraremos os recursos personalizados do CloudFormation, por que você precisa deles e seus diferentes tipos. Também definiremos as melhores práticas para implementá-los corretamente com o AWS CDK e exemplos de código Python usando Powertools for AWS , Pydantic e crhelper .
Índice
O caso de um recurso personalizado do CloudFormation
O CloudFormation pode ser útil quando seus requisitos de provisionamento envolvem lógica complexa ou fluxos de trabalho que não podem ser expressos com os tipos de recursos integrados do CloudFormation. - AWS Docs
Aqui estão alguns exemplos que me vêm à mente:
Adicionando um banco de dados a um cluster Aurora.
Criando um usuário administrador/teste do Cognito para um pool de usuários.
Criar uma entrada DNS Route53 ou criar um certificado em um domínio criado em uma conta AWS diferente.
Carregando um arquivo JSON como um painel de observabilidade para o DataDog
Você deseja acionar um provisionamento de recursos que leva muito tempo - talvez até uma hora.
Qualquer recurso que não seja da AWS que você deseja criar.
Scripts de pós-implantação
Para combater esses cenários, vi pessoas adicionarem um misterioso script 'post_deploy' ao seu pipeline de CI/CD que é executado após o estágio de implantação do CF e cria os recursos e configurações ausentes por meio de chamadas de API.
É perigoso . Se esse script falhar, você não poderá reverter automaticamente a implantação da pilha CF, pois ela já foi feita, deixando seu serviço em um estado instável.
Além disso, as pessoas esquecem que os recursos têm um ciclo de vida e lidam com a exclusão de objetos, mantendo muitos recursos órfãos quando a pilha é excluída.
Recurso personalizado - Uma pilha para governar todos eles
Do jeito que eu vejo, tudo o que você faz no pipeline no estágio de implantação, qualquer recurso que você adiciona ou reconfigura deve ser atualizado junto, pois há dependências e, se houver uma falha, o CloudFormation reverterá de forma confiável a implantação da pilha e protegerá sua produção de interrupções.
Nossa solução é enfatizar a importância de incluir TODOS os recursos e alterações de configuração, incluindo o tratamento de eventos do ciclo de vida (mais sobre isso abaixo), como parte da pilha do CloudFormation como um recurso personalizado .
No entanto, nem tudo são rosas e margaridas. Muitas pessoas ficam longe de recursos personalizados porque erros podem ser altamente irritantes - desde recursos personalizados falhando em excluir até esperar até uma hora para uma pilha falhar na implantação.
Fique tranquilo, você ficará bem se seguir os exemplos de código e as práticas recomendadas.
Vamos revisar os tipos de recursos personalizados.
Tipos de recursos personalizados do CloudFormation
É importante lembrar que cada recurso do CloudFormation tem eventos de ciclo de vida que precisa implementar. Os principais eventos incluem criação, atualização (devido a alterações de ID lógico ou configuração) e exclusão. Quando construímos nosso recurso personalizado, precisaremos definir seu comportamento em reação a esses eventos do CloudFormation.
Existem três tipos de recursos personalizados; vamos listá-los das opções mais simples até as mais personalizadas e complicadas:
Chamadas simples do AWS SDK - simples, menos código para escrever
Recurso apoiado pelo SNS - mais complicado
Recurso apoiado por Lambda - o mais complicado, mas o mais flexível
Vamos começar com o primeiro tipo.
Chamadas simples do AWS SDK
Esta é a maneira mais simples de implementar um recurso personalizado. No exemplo abaixo, queremos criar um usuário de teste do pool de usuários do Cognito logo após o pool de usuários ser criado.
O processo de criação e exclusão de um usuário é tão simples quanto fazer uma chamada para o AWS SDK. Você pode encontrar as etapas necessárias [ aqui ] e [ aqui ].
Vamos ver como podemos traduzir essas chamadas de API para um objeto CDK simples.
Definimos uma função CDK que recebe um objeto de pool de usuários do Cognito usado como parâmetros do SDK (seu ID e ARN).
Na linha 7, criamos uma nova instância 'AwsCustomResource'.
Na linha 10, passamos a definição da API para o processo de criação: o serviço boto SDK, o nome da API: ' adminCreateUser ' e seus parâmetros. Da mesma forma, podemos adicionar os manipuladores 'on_delete' e 'on_update'.
Nos bastidores, a AWS cria uma função Lambda singleton que manipula os eventos do ciclo de vida do CloudFormation para você - super simples e fácil!
Na linha 26, adicionamos uma dependência; esse recurso depende do pool de usuários criado antes de executar uma API.
Resumindo: se você puder mapear seus eventos de ciclo de vida para chamadas de API do AWS SDK, essa será a melhor e mais direta maneira de cobrir os recursos ausentes do CloudFormation com o mínimo de código.
Recurso personalizado com suporte do SNS
O segundo tipo é interessante.
Eu usaria esse recurso personalizado para disparar um provisionamento longo (até uma hora!) de forma desacoplada e assíncrona por meio de uma mensagem SNS. Dependendo de onde o tópico SNS reside, ele pode criar recursos ou configurações, mesmo em uma conta diferente.
Uma aplicação prática desse tipo de recurso personalizado é enviar todas as informações de criação de recursos personalizados para uma conta centralizada. Isso permite o rastreamento fácil de recursos exclusivos, aumentando a visibilidade organizacional.
Este é um caso de uso que descrevo em um artigo que escrevi com Bill Tarr da fábrica de SaaS da AWS para o site do blog de operações em nuvem da AWS. Espero que seja lançado em breve.
O repositório completo do GitHub pode ser encontrado aqui .
Fluxo de Eventos
Vamos rever o fluxo de criação de recursos personalizados abaixo. Observe que o padrão SNS para SQS para Lambda não é fornecido no CDK abaixo, presume-se que o proprietário do tópico SNS (talvez até mesmo em uma pilha CF diferente) cria esse padrão. No entanto, fornecerei o código da função Lambda, pois ele tem código específico relacionado à lógica de recursos personalizados.
Fluxo de eventos de criação de recursos personalizados:
Os parâmetros são enviados como um dicionário para o tópico do SNS. Você deve garantir que o tópico aceite mensagens da conta/organização de implantação.
O tópico SNS passa a mensagem para seu assinante, a fila SQS.
A fila SQS aciona a função Lambda com um lote de mensagens (o tamanho mínimo é 1).
A função Lambda:
A função Lambda analisa as mensagens e extrai o tipo de evento de recurso personalizado (criar/excluir/atualizar) e seus parâmetros, que aparecem na propriedade 'resource_properties' do SQS body massage. Observe que você receberá os parâmetros anteriores e atuais para um evento de atualização.
A função Lambda lida com o aspecto lógico do recurso personalizado, criando ou configurando recursos.
A função lambda envia uma solicitação POST para o caminho de URL S3 pré-assinado que faz parte do evento com o status correto: falha/sucesso e qualquer outra informação necessária. Clique aqui para um exemplo de evento 'create'.
O recurso personalizado é liberado do estado de espera e a implantação termina com sucesso ou falha (revertida).
Durante a implantação no estágio 1, o recurso personalizado entra em um estado de espera após enviar uma mensagem SNS. O receptor da mensagem precisa liberar o recurso de seu estado de espera . Se uma hora passar sem essa liberação (tempo limite padrão), a pilha falhará em um tempo limite e uma reversão ocorrerá. Se o receptor da mensagem enviar uma mensagem de falha de volta, a pilha falhará e uma reversão ocorrerá.
O receptor deve enviar uma solicitação HTTP POST com um corpo específico que marca o sucesso ou a falha para uma URL S3 pré-assinada gerada pelo recurso personalizado.
Os elementos 2-4 podem fazer parte de uma conta diferente da AWS, pertencente a uma equipe totalmente diferente em sua organização, e servir como uma orquestração de "caixa preta". Nesse caso, você apenas cria o recurso Custom, o que é relativamente fácil.
Código CDK de recurso personalizado
Vamos começar com a definição de recurso personalizado. O recurso personalizado envia ao tópico SNS uma mensagem com parâmetros predefinidos como o corpo da mensagem. Cada evento de ciclo de vida (criar, excluir, atualizar) enviará automaticamente atributos de mensagem SNS diferentes com as propriedades CDK que definimos. Em um evento de atualização, os parâmetros atuais e anteriores são enviados.
Nas linhas 9 a 18, definimos o recurso personalizado.
Na linha 12, fornecemos o ARN do tópico SNS como o destino da mensagem.
Na linha 13, definimos o tipo de recurso (ele aparecerá no console do CF) e deve começar com 'Custom::.'
Na linha 15, definimos o payload da mensagem SNS do dicionário que será enviada ao tópico. Podemos usar qualquer conjunto de chaves e valores que quisermos, desde que seu valor seja conhecido durante a implantação.
Código da função Lambda
Vamos rever o lado do receptor do fluxo e como ele manipula os eventos de recursos personalizados do CF. Usaremos a biblioteca ' cr_helper ' para manipular os eventos com uma combinação do utilitário Parser do Powertools para validação de entrada com 'pydantic'. 'cr_helper' roteará o evento correto para a função apropriada dentro do manipulador, gerenciará a resposta para a URL pré-assinada do S3 e manipulará erros (enviará uma resposta de falha para cada exceção não capturada).
O código abaixo foi retirado de um dos meus projetos de código aberto, que implementa produtos do Service Catalog e usa recursos personalizados e mensagens SNS. Além do código na pasta 'logic', que você pode substituir pela sua própria implementação, a maior parte do código é genérica.
Você pode ver o código completo aqui .
O fluxo é simples:
Inicialize a biblioteca auxiliar CR. Ela manipulará o roteamento para as funções do manipulador de eventos interno e, uma vez concluído, liberará o recurso personalizado de um estado de espera (veja 2c abaixo) com uma solicitação HTTP.
Iterar o lote de mensagens SQS e por mensagem SQS:
Rotear para a função interna correta de acordo com o corpo da mensagem SQS, o evento CF do recurso personalizado. Rotear eventos 'create' para minha função 'create_event', 'delete' para a função 'delete_event' e update' para 'update_event.'
Cada função 'x_event' analisa a entrada de acordo com os parâmetros esperados definidos no código CDK de acordo com os esquemas 'CloudFormationCustomResource' (linhas 5-7). Aproveitamos o Powertools para o utilitário do analisador AWS e passamos a carga útil para a camada lógica que cria exclusões ou atualiza recursos.
'cr_helper' envia uma solicitação HTTP POST para a URL pré-assinada com informações de sucesso ou falha. A falha é enviada quando os manipuladores de eventos internos geram uma exceção.
Na linha 13, importamos as funções lógicas do manipulador de eventos, que são responsáveis pela lógica do recurso. Substitua esta importação pela sua implementação. Eu segui uma prática recomendada do Lambda de escrever a função com camadas arquitetônicas. Clique aqui para saber mais.
Nas linhas 17-22, inicializamos o utilitário auxiliar 'cr_helper'.
Na linha 43, precisamos retornar um ID de recurso na função 'create_event'. É crucial garantir que ele seja único. Caso contrário, você não conseguirá criar vários recursos personalizados desse tipo na mesma conta.
Na linha 50, implementamos um fluxo de atualização. Isso pode acontecer quando o ID do recurso muda ou os parâmetros de entrada mudam. O evento CloudFormation conterá os parâmetros atuais e anteriores, então é possível encontrar as diferenças e fazer alterações no código lógico de acordo.
O ponto principal é que, se você precisar acionar uma provisão ou lógica em outra conta ou serviço (que pode pertencer a outra equipe), esta é uma ótima maneira de desacoplar essa lógica entre os serviços e permitir um processo longo, que pode durar até uma hora.
Recurso personalizado com suporte de Lambda
Neste caso, o recurso personalizado aciona uma função Lambda com um evento de ciclo de vida do CloudFormation para manipular. É benéfico em casos em que você deseja escrever todo o fluxo de provisionamento e mantê-lo no mesmo projeto; isso contrasta com o recurso personalizado anterior, em que você envia uma mensagem assíncrona para um tópico SNS e deixa outra pessoa manipular a lógica do recurso.
Vamos revisar o fluxo de criação de recursos personalizados no diagrama abaixo.
Fluxo de Eventos
Fluxo de evento de criação de recurso personalizado:
Os parâmetros são enviados como um dicionário como parte do evento para a função Lambda invocada.
A função Lambda analisa as mensagens, extrai o tipo de evento de recurso personalizado (criar/excluir/atualizar) e seus parâmetros que aparecem em 'resource_properties'. Observe que para um evento 'update' você receberá os parâmetros anteriores e atuais.
A função Lambda lida com o aspecto lógico do recurso personalizado, criando ou configurando recursos.
A função lambda envia uma solicitação POST para o caminho de URL S3 pré-assinado ('ResponseURL' no evento) que faz parte do evento com o status correto: falha/sucesso e qualquer outra informação necessária. Clique aqui para um exemplo de evento 'create'.
O recurso personalizado é liberado do estado de espera e a implantação termina com sucesso ou falha (revertida).
Você pode usar esse recurso para acionar um processo de provisionamento mais longo (até uma hora) acionando uma máquina de estado da Step Function na função Lambda, desde que você envie a URL pré-assinada do S3 para esse processo para que ele possa marcar o resultado.
Código CDK de recurso personalizado
Vamos revisar o código abaixo.
Nas linhas 10 a 16, criamos a função Lambda para manipular os eventos de recursos personalizados do CF.
Na linha 18, definimos um provedor, um sinônimo para um manipulador de eventos, e definimos nossa função lambda como o destino do evento de recurso personalizado.
Nas linhas 19-27, definimos o recurso personalizado e definimos o service_token como o token de serviço do provedor. Veja a definição do provedor aqui .
Nas linhas 24-25, definimos os parâmetros de entrada que queremos que o Lambda receba. Podemos passar quaisquer parâmetros que o Lambda possa usar durante o processo de provisionamento.
Na linha 27, definimos o tipo de recurso personalizado no console CF. Ele deve começar com 'Custom::.'
Código da função Lambda
Vamos rever o código da função abaixo. Ele será familiar ao exemplo anterior, sem a seção de iteração em lote do SQS, que é substituída por um manipulador de erro global nas linhas 19-23.
Definimos uma função para cada tipo de evento: criar, atualizar, excluir, e a biblioteca 'helper' sabe qual delas acionar com base nas propriedades do evento de entrada recebido.
O utilitário parser do Pydantic e do Powertools é usado como antes para analisar a entrada de cada evento. Essa entrada é então passada para qualquer função lógica que você escrever para manipular o evento: criar um recurso, enviar uma solicitação de API, excluir recursos, etc.
Como antes, precisamos retornar um ID de recurso na função 'create_event'. É crucial garantir que ele seja único; caso contrário, você não conseguirá criar vários recursos personalizados desse tipo na mesma conta.
Como no exemplo do SNS, as funções 'handle_delete', 'handle_create' e 'handle_update' são sua lógica de implementação.
Conclusão: se você precisar acionar um fluxo e gerenciá-lo inteiramente na mesma conta por meio do código de função Lambda, esta é uma ótima maneira de fazer isso e lidar com seus eventos de ciclo de vida.
Melhores práticas de recursos personalizados
Recursos personalizados são propensos a erros e você deve ter cuidado extra no seu código de tratamento de erros.
Não fazer isso pode resultar em recursos que o CF não pode excluir.
Aqui estão algumas dicas:
Use as ferramentas deste guia: 'cr_helper' e Powertools.
Leia os documentos especificados neste guia para ter certeza de que você entendeu os eventos de entrada e quando cada evento é enviado.
Entenda os tempos limite e certifique-se de configurar todos os recursos adequadamente — definição de tempo limite do Lambda, tempo limite do CR, etc.
Tente ser o mais flexível possível na implementação da lógica da função Lambda. Não falhe em todos os problemas. Por exemplo, se você precisar excluir um recurso via API e ele não estiver lá, você pode retornar um sucesso em vez de falhar.
Teste, teste e teste novamente, fluxos de criação, atualização e exclusão. Seja criativo e garanta a integração adequada e testes E2E para seu Lambda. Aprenda aqui na minha série de blogs de testes sobre testes sem servidor.
Defina a configuração de tempo limite de recurso personalizado. Agora, ele pode ser alterado para que você não precise esperar uma hora em caso de erro no seu código.
'cr_helper' também fornece um mecanismo auxiliar de polling para fluxos de criação mais longos — use-o quando necessário. Ainda não o usei. Veja o readme .
Por fim, escolha o recurso personalizado mais simples que faça sentido para você. Não exagere na engenharia e pense sobre a propriedade da equipe de recursos personalizados. Desacople quando possível com o mecanismo SNS se outra equipe manipular o fluxo de provisionamento. Nesse caso, é melhor fazer isso de forma centralizada.
Resumo
Este post abordou vários casos que os recursos nativos do CloudFormation não cobrem. Aprendemos sobre recursos personalizados e seus tipos, seus casos de uso e revisamos as melhores práticas gerais com CDK e código Python.
Comments