Cuando comencé a desarrollar servicios en AWS, pensé que los recursos de CloudFormation podrían cubrir todas mis necesidades. Estaba equivocado.
Descubrí rápidamente que los entornos de producción son complejos y presentan numerosos casos extremos. Afortunadamente, CloudFormation permite la ampliación mediante recursos personalizados. Si bien los recursos personalizados pueden ser útiles, una implementación incorrecta puede generar fallas en la pila, problemas de eliminación y dolores de cabeza importantes.
En esta publicación del blog, exploraremos los recursos personalizados de CloudFormation, por qué los necesita y sus diferentes tipos. También definiremos las prácticas recomendadas para implementarlos correctamente con AWS CDK y ejemplos de código Python usando Powertools for AWS , Pydantic y crhelper .
Tabla de contenido
El caso de un recurso personalizado de CloudFormation
CloudFormation puede ser útil cuando sus requisitos de aprovisionamiento involucran lógica compleja o flujos de trabajo que no se pueden expresar con los tipos de recursos integrados de CloudFormation. - Documentación de AWS
Me vienen a la mente algunos ejemplos:
Agregar una base de datos a un clúster Aurora.
Creación de un usuario administrador/de prueba de Cognito para un grupo de usuarios.
Creación de una entrada DNS de Route53 o creación de un certificado en un dominio creado en una cuenta de AWS diferente.
Subir un archivo JSON como panel de observación a DataDog
Desea activar un aprovisionamiento de recursos que requiere mucho tiempo (quizás hasta una hora).
Cualquier recurso que no sea de AWS que desee crear.
Scripts posteriores a la implementación
Para contrarrestar estos escenarios, he visto personas que agregan un misterioso script 'post_deploy' a su canalización de CI/CD que se ejecuta después de la etapa de implementación de CF y crea los recursos y configuraciones faltantes a través de llamadas API.
Es peligroso . Si ese script falla, no podrá revertir automáticamente la implementación de la pila CF, ya que ya se realizó y el servicio quedará en un estado inestable.
Además, la gente olvida que los recursos tienen un ciclo de vida y manejan la eliminación de objetos, por lo que quedan muchos recursos huérfanos cuando se elimina la pila.
Recurso personalizado: una pila para gobernarlos a todos
En mi opinión, todo lo que haces en la canalización en la etapa de implementación, cualquier recurso que agregues o reconfigures, debe actualizarse en conjunto, ya que hay dependencias, y si hay una falla, CloudFormation revertirá de manera confiable la implementación de la pila y protegerá tu producción para que no se interrumpa.
Nuestra solución es enfatizar la importancia de incluir TODOS los recursos y cambios de configuración, incluido el manejo de eventos del ciclo de vida (más sobre esto a continuación), como parte de la pila de CloudFormation como un recurso personalizado .
Sin embargo, no todo es color de rosa. Muchas personas evitan los recursos personalizados porque los errores pueden ser muy molestos, desde que los recursos personalizados no se eliminan hasta que hay que esperar hasta una hora para que falle la implementación de una pila.
Tenga la seguridad de que estará bien si sigue los ejemplos de código y las mejores prácticas.
Repasemos los tipos de recursos personalizados.
Tipos de recursos personalizados de CloudFormation
Es importante recordar que cada recurso de CloudFormation tiene eventos de ciclo de vida que debe implementar. Los eventos principales incluyen la creación, la actualización (debido a cambios de configuración o de ID lógicos) y la eliminación. Cuando creamos nuestro recurso personalizado, necesitaremos definir su comportamiento en reacción a estos eventos de CloudFormation.
Hay tres tipos de recursos personalizados; vamos a enumerarlos desde los más simples hasta las opciones más personalizadas y complicadas:
Llamadas simples al SDK de AWS: sencillas, menos código para escribir
Recurso respaldado por redes sociales: más complicado
Recurso respaldado por Lambda: el más complicado pero el más flexible
Empecemos con el primer tipo.
Llamadas simples al SDK de AWS
Esta es la forma más sencilla de implementar un recurso personalizado. En el siguiente ejemplo, queremos crear un usuario de prueba del grupo de usuarios de Cognito inmediatamente después de que se crea el grupo de usuarios.
El proceso de creación y eliminación de un usuario es tan sencillo como realizar una llamada al SDK de AWS. Puede encontrar los pasos necesarios [ aquí ] y [ aquí ].
Veamos cómo podemos traducir estas llamadas API a un objeto CDK simple.
Definimos una función CDK que recibe un objeto del grupo de usuarios de Cognito utilizado como parámetros del SDK (su ID y ARN).
En la línea 7, creamos una nueva instancia 'AwsCustomResource'.
En la línea 10, pasamos la definición de la API para el proceso de creación: el servicio SDK de boto, el nombre de la API: ' adminCreateUser ' y sus parámetros. De manera similar, podemos agregar los controladores 'on_delete' y 'on_update'.
Detrás de escena, AWS crea una función Lambda singleton que maneja los eventos del ciclo de vida de CloudFormation por usted: ¡súper simple y fácil!
En la línea 26, agregamos una dependencia; este recurso depende del grupo de usuarios creado antes de ejecutar una API.
En resumen: si puede asignar sus eventos de ciclo de vida a las llamadas API del SDK de AWS, es la mejor y más sencilla manera de cubrir las capacidades faltantes de CloudFormation con un código mínimo.
Recurso personalizado respaldado por redes sociales
El segundo tipo es interesante.
Usaría este recurso personalizado para activar un aprovisionamiento prolongado (¡de hasta una hora!) de manera desacoplada y asincrónica a través de un mensaje de SNS. Según dónde resida el tema de SNS, puede crear recursos o configuraciones, incluso en una cuenta diferente.
Una aplicación práctica de este tipo de recurso personalizado es enviar toda la información de creación de recursos personalizados a una cuenta centralizada. Esto permite un seguimiento sencillo de los recursos únicos, lo que mejora la visibilidad de la organización.
Este es un caso de uso que describo en un artículo que escribí con Bill Tarr de la fábrica de SaaS de AWS para el sitio web del blog de operaciones en la nube de AWS. Espero que se publique pronto.
El repositorio completo de GitHub se puede encontrar aquí .
Flujo de eventos
Repasemos el flujo de creación de recursos personalizados a continuación. Tenga en cuenta que el patrón de SNS a SQS a Lambda no se proporciona en el CDK a continuación; se supone que el propietario del tema de SNS (quizás incluso en una pila CF diferente) crea este patrón. Sin embargo, proporcionaré el código de la función Lambda, ya que tiene un código relacionado con la lógica de recursos personalizados específicos.
Flujo de evento de creación de recursos personalizados:
Los parámetros se envían como un diccionario al tema de SNS. Debe asegurarse de que el tema acepte mensajes de la cuenta u organización que realiza la implementación.
El tema SNS pasa el mensaje a su suscriptor, la cola SQS.
La cola SQS activa la función Lambda con un lote de mensajes (el tamaño mínimo es 1).
La función Lambda:
La función Lambda analiza los mensajes y extrae el tipo de evento de recurso personalizado (crear/eliminar/actualizar) y sus parámetros, que aparecen en la propiedad 'resource_properties' de SQS body massage. Tenga en cuenta que se le proporcionarán los parámetros anteriores y actuales para un evento de actualización.
La función Lambda maneja el aspecto lógico del recurso personalizado, creando o configurando recursos.
La función lambda envía una solicitud POST a la ruta URL de S3 firmada previamente que forma parte del evento con el estado correcto: error/éxito y cualquier otra información requerida. Haga clic aquí para ver un ejemplo de evento "crear".
El recurso personalizado se libera de su estado de espera, la implementación finaliza con éxito o error (revertido).
Durante la implementación en la etapa 1, el recurso personalizado entra en un estado de espera después de enviar un mensaje SNS. El receptor del mensaje debe liberar el recurso de su estado de espera . Si transcurre una hora sin que se produzca esta liberación (tiempo de espera predeterminado), la pila falla en un tiempo de espera y se produce una reversión. Si el receptor del mensaje envía un mensaje de error, la pila falla y se produce una reversión.
El receptor debe enviar una solicitud HTTP POST con un cuerpo específico que marca el éxito o el fracaso a una URL S3 previamente firmada que genera el recurso personalizado.
Los elementos 2 a 4 pueden ser parte de una cuenta de AWS diferente que pertenezca a un equipo completamente diferente de su organización y que sirvan como una orquestación de "caja negra". En ese caso, solo debe crear el recurso personalizado, lo cual es relativamente fácil.
Código CDK de recursos personalizados
Comencemos con la definición de recurso personalizado. El recurso personalizado envía al tema SNS un mensaje con parámetros predefinidos como cuerpo del mensaje. Cada evento del ciclo de vida (crear, eliminar, actualizar) enviará automáticamente atributos de mensaje SNS diferentes con las propiedades CDK que definimos. En un evento de actualización, se envían tanto los parámetros actuales como los anteriores.
En las líneas 9-18, definimos el recurso personalizado.
En la línea 12, proporcionamos el ARN del tema SNS como destino del mensaje.
En la línea 13, definimos el tipo de recurso (aparecerá en la consola CF) y debe comenzar con 'Custom::.'
En la línea 15, definimos la carga útil del mensaje SNS del diccionario que se enviará al tema. Podemos utilizar cualquier conjunto de claves y valores que queramos siempre que se conozca su valor durante la implementación.
Código de la función Lambda
Repasemos el lado del receptor del flujo y cómo maneja los eventos de recursos personalizados de CF. Usaremos la biblioteca ' cr_helper ' para manejar los eventos con una combinación de la utilidad Parser de Powertools para la validación de entrada con 'pydantic'. 'cr_helper' enrutará el evento correcto a la función apropiada dentro del controlador, administrará la respuesta a la URL prefirmada de S3 y manejará los errores (enviará una respuesta de error por cada excepción no detectada).
El código que aparece a continuación se ha extraído de uno de mis proyectos de código abierto, que implementa productos de Service Catalog y utiliza recursos personalizados y mensajes de SNS. Aparte del código que se encuentra en la carpeta "logic", que puede reemplazar con su propia implementación, la mayor parte del código es genérico.
Puedes ver el código completo aquí .
El flujo es simple:
Inicialice la biblioteca auxiliar CR. Se encargará del enrutamiento a las funciones del controlador de eventos internos y, una vez completado, liberará el recurso personalizado de un estado de espera (consulte el punto 2c a continuación) con una solicitud HTTP.
Iterar el lote de mensajes SQS y por cada mensaje SQS:
Dirija a la función interna correcta según el cuerpo del mensaje de SQS, el evento CF de recurso personalizado. Dirija los eventos 'crear' a mi función 'create_event', 'eliminar' a la función 'delete_event' y 'actualizar' a 'update_event'.
Cada función 'x_event' analiza la entrada según los parámetros esperados definidos en el código CDK según los esquemas 'CloudFormationCustomResource' (líneas 5 a 7). Aprovechamos Powertools para la utilidad de análisis de AWS y pasamos la carga útil a la capa lógica que crea, elimina o actualiza recursos.
'cr_helper' envía una solicitud HTTP POST a la URL firmada previamente con información sobre el éxito o el fracaso. El fracaso se envía cuando los controladores de eventos internos generan una excepción.
En la línea 13, importamos las funciones lógicas del controlador de eventos, que están a cargo de la lógica de recursos. Reemplace esta importación con su implementación. Seguí una práctica recomendada de Lambda de escribir la función con capas arquitectónicas. Haga clic aquí para obtener más información.
En las líneas 17 a 22, inicializamos la utilidad auxiliar 'cr_helper'.
En la línea 43, debemos devolver un ID de recurso en la función 'create_event'. Es fundamental asegurarse de que sea único. De lo contrario, no podrá crear varios recursos personalizados de este tipo en la misma cuenta.
En la línea 50, implementamos un flujo de actualización. Esto puede suceder cuando cambia el ID del recurso o los parámetros de entrada. El evento CloudFormation contendrá los parámetros actuales y anteriores, por lo que es posible encontrar las diferencias y realizar cambios en el código lógico en consecuencia.
La conclusión es que si necesita activar una disposición o lógica en otra cuenta o servicio (que podría pertenecer a otro equipo), esta es una excelente manera de disociar esta lógica entre los servicios y permitir un proceso largo, que puede durar hasta una hora.
Recurso personalizado respaldado por Lambda
En este caso, el recurso personalizado activa una función Lambda con un evento del ciclo de vida de CloudFormation para manejar. Es beneficioso en los casos en los que desea escribir usted mismo todo el flujo de aprovisionamiento y mantenerlo en el mismo proyecto; esto contrasta con el recurso personalizado anterior, en el que envía un mensaje asincrónico a un tema de SNS y deja que otra persona maneje la lógica del recurso.
Repasemos el flujo de creación de recursos personalizados en el diagrama a continuación.
Flujo de eventos
Flujo de evento de creación de recursos personalizados:
Los parámetros se envían como un diccionario como parte del evento a la función Lambda invocada.
La función Lambda analiza los mensajes, extrae el tipo de evento de recurso personalizado (crear/eliminar/actualizar) y sus parámetros que aparecen en 'resource_properties'. Tenga en cuenta que, para un evento de 'actualización', se le proporcionarán los parámetros anteriores y actuales.
La función Lambda maneja el aspecto lógico del recurso personalizado, creando o configurando recursos.
La función lambda envía una solicitud POST a la ruta URL de S3 firmada previamente ('ResponseURL' en el evento) que forma parte del evento con el estado correcto: error/éxito y cualquier otra información requerida. Haga clic aquí para ver un ejemplo de evento 'crear'.
El recurso personalizado se libera de su estado de espera, la implementación finaliza con éxito o error (revertido).
Puede usar este recurso para activar un proceso de aprovisionamiento más largo (hasta una hora) activando una máquina de estado de Step Function en la función Lambda, siempre que envíe la URL firmada previamente por S3 a ese proceso para que pueda marcar el resultado en su lugar.
Código CDK de recursos personalizados
Repasemos el código a continuación.
En las líneas 10 a 16, construimos la función Lambda para manejar los eventos de recursos personalizados de CF.
En la línea 18, definimos un proveedor, un sinónimo de un controlador de eventos, y establecemos nuestra función lambda como el objetivo del evento de recurso personalizado.
En las líneas 19 a 27, definimos el recurso personalizado y establecemos el service_token como el token de servicio del proveedor. Consulta la definición del proveedor aquí .
En las líneas 24 y 25, definimos los parámetros de entrada que queremos que reciba Lambda. Podemos pasar cualquier parámetro que Lambda pueda usar durante el proceso de aprovisionamiento.
En la línea 27, configuramos el tipo de recurso personalizado en la consola CF. Debe comenzar con 'Custom::.'
Código de la función Lambda
Repasemos el código de la función que aparece a continuación. Resultará similar al del ejemplo anterior, sin la sección de iteración por lotes de SQS, que se reemplaza con un controlador de errores global en las líneas 19 a 23.
Definimos una función para cada tipo de evento: crear, actualizar, eliminar, y la biblioteca "auxiliar" sabe cuál activar en función de las propiedades del evento de entrada entrante.
La utilidad de análisis de Pydantic y Powertools se utiliza como antes para analizar la entrada de cada evento. Esta entrada se pasa luego a cualquier función lógica que escriba para gestionar el evento: crear un recurso, enviar una solicitud de API, eliminar recursos, etc.
Como antes, necesitamos devolver un ID de recurso en la función 'create_event'. Es fundamental asegurarse de que sea único; de lo contrario, no podrá crear varios recursos personalizados de este tipo en la misma cuenta.
Al igual que en el ejemplo de SNS, las funciones 'handle_delete', 'handle_create' y 'handle_update' son su lógica de implementación.
En resumen: si necesita activar un flujo y administrarlo completamente en la misma cuenta a través del código de función Lambda, esta es una excelente manera de hacerlo y manejar sus eventos de ciclo de vida.
Mejores prácticas para recursos personalizados
Los recursos personalizados son propensos a errores y debes tener especial cuidado con tu código de manejo de errores.
Si no lo hace, es posible que haya recursos que CF no pueda eliminar.
A continuación se ofrecen algunos consejos:
Utilice las herramientas de esta guía: 'cr_helper' y Powertools.
Lea los documentos especificados en esta guía para asegurarse de comprender los eventos de entrada y cuándo se envía cada evento.
Comprenda los tiempos de espera y asegúrese de configurar todos los recursos en consecuencia: definición de tiempo de espera de Lambda, tiempo de espera de CR, etc.
Intente ser lo más flexible posible en la implementación de la lógica de la función Lambda. No falle en todos los casos. Por ejemplo, si necesita eliminar un recurso a través de la API y no está allí, puede devolver un resultado exitoso en lugar de un error.
Pruebe, pruebe y vuelva a probar, flujos de creación, actualización y eliminación. Sea creativo y asegúrese de que la integración y las pruebas E2E sean adecuadas para su Lambda. Aprenda aquí en mi serie de blogs sobre pruebas sin servidor.
Establezca la configuración de tiempo de espera de recursos personalizada. Ahora se puede cambiar para que no tenga que esperar una hora en caso de que se produzca un error en el código.
'cr_helper' también proporciona un mecanismo de sondeo auxiliar para flujos de creación más largos: úselo cuando sea necesario. Todavía no lo he usado. Consulte el archivo README .
Por último, elija el recurso personalizado más simple que tenga sentido para usted. No realice una ingeniería excesiva y piense en la propiedad del equipo de recursos personalizados. Desvincúlelo, cuando sea posible, del mecanismo SNS si otro equipo maneja el flujo de aprovisionamiento. En ese caso, es mejor hacerlo de manera centralizada.
Resumen
En esta publicación, se abordaron varios casos que los recursos nativos de CloudFormation no cubren. Aprendimos sobre los recursos personalizados y sus tipos, sus casos de uso y revisamos las mejores prácticas generales con CDK y código Python.