Administrar arquitectura sin servidor multi-entorno usando AWS – una investigación

En 2PAx estamos en el proceso de migrar nuestra API REST de una aplicación monolítica tradicional a una arquitectura Serverless . Antes de dar este paso, queríamos saber cómo afectaría el desarrollo local y qué se requeriría para mantener nuestra estrategia de implementación actual que implica múltiples entornos. Este artículo es un resumen de nuestra investigación, incluidos los enfoques que probamos y los obstáculos que encontramos en el camino.

Nota: Utilizaremos el término "entorno" y no "escenario" cuando nos remitamos a nuestras configuraciones lógicas de desarrollo, preparación y producción para evitar confusiones con la denominación de los recursos de AWS.

Objetivo

Nuestra tubería contiene tres entornos diferentes: desarrollo, puesta en escena y producción. Las asignaciones de código fuente fusionadas en maestro deben implementarse automáticamente en el entorno de desarrollo, mientras que la transición a los otros dos entornos requiere aprobación manual. Esta estrategia fue fácil de lograr con el backend de aplicación única que usamos hasta ahora (un solo binario Go compilado) pero presenta algunos desafíos con una arquitectura sin servidor ya que ahora necesitamos implementar una cantidad de recursos diferentes.

Los tres entornos en nuestra cartera: desarrollo, puesta en escena y producción.

Aplicaciones web sin servidor en AWS

Antes de entrar en los detalles de la implementación, veamos brevemente los componentes implicados en una configuración típica sin servidor y qué conceptos proporciona AWS para manejar múltiples entornos.

Las solicitudes del cliente son enrutadas y validadas por AWS Gateway antes de ser manejadas por AWS Lambda.

En un nivel básico, una solicitud de cliente entrante se enrutará a través de API Gateway, una función de autorizador de Lambda y finalmente un controlador de punto final de Lambda.

API Gateway se encarga de validar los parámetros de solicitud y el cuerpo, el almacenamiento en caché, la limitación de velocidad, etc. y se puede configurar cómodamente utilizando una especificación OpenAPI, anteriormente conocida como Swagger. Esto reduce la cantidad de código repetitivo y ayuda a mantener la lógica real manejando la solicitud simple.

El manejador de punto final no debería tener que preocuparse por autenticar o autorizar al usuario, por lo que ambos son manejados por una función autorizante Lambda, actuando como middleware de autenticación (validando JSON Web Tokens en nuestro caso) y devolviendo una política de IAM. Finalmente, la solicitud validada y autorizada es manejada por otra función Lambda y su resultado correlacionado por API Gateway antes de devolver la respuesta al cliente.

A diferencia del ejemplo simplificado anterior, nuestra API presenta múltiples puntos finales. Nuestro enfoque actual es agrupar puntos finales estrechamente relacionados en el mismo controlador de punto final, similar a los pequeños manejadores de servicio, en lugar de usar una función Lambda por punto final. Dependiendo de sus requisitos o preferencias, puede terminar usando un enfoque diferente.

Toda la pila se define en una plantilla que utiliza SAM, abreviatura de Modelo de aplicación Serverless , una extensión de CloudFormation , que se implementa (por CodePipeline en nuestro caso) en los entornos de desarrollo, montaje y producción antes mencionados.

Pasarela API

AWS ofrece formas potentes para manejar diferentes versiones del mismo recurso API Gateway mediante el uso de 'etapas'. Esto es lo que la documentación tiene que decir:

Las etapas permiten un control robusto de la versión de su API. Por ejemplo, puede implementar una API en una etapa de prueba y una etapa prod, y usar la etapa de prueba como una compilación de prueba y usar la etapa prod como una compilación estable. Después de que las actualizaciones pasen la prueba, puede promocionar la etapa de prueba a la etapa prod. La promoción puede realizarse redistribuyendo la API a la etapa prod o actualizando un valor de variable de etapa desde el nombre de la etapa de prueba hasta el de prod.

Las variables de etapa mencionadas en la cita anterior le permiten usar la API con diferentes configuraciones para cada etapa. Por ejemplo, podría usar diferentes ARN de función Lambda o pasar valores de configuración a las funciones. De nuevo, la documentación oficial proporciona más detalles.

Lambda

Las funciones de Lambda tienen su propio concepto de control de versiones. Otro vistazo a la documentación oficial revela lo siguiente:

Los alias le permiten resumir el proceso de promoción de nuevas versiones de la función Lambda en producción a partir de la asignación de la versión de la función Lambda y su fuente de eventos.

Por el contrario, en lugar de especificar la función ARN, suponga que especifica un alias ARN en la configuración de notificación (por ejemplo, alias de PROD ARN). A medida que promueve nuevas versiones de su función Lambda en la producción, solo necesita actualizar el alias de PROD para apuntar a la última versión estable. No necesita actualizar la configuración de notificación en Amazon S3.

Con alias, obtienes un control detallado sobre la promoción de nuevas versiones de Lambda en etapas específicas. Una advertencia que vale la pena señalar: dado que las funciones de Lambda son inmutables una vez publicadas, es posible que desee ver cómo afecta esto a las configuraciones específicas del entorno, como las cadenas de bases de datos y otros parámetros, consulte estos artículos .

CloudFormation

CloudFormation es una herramienta de administración de infraestructura y especificaciones que lo acompañan que le permite definir un grupo de recursos de AWS agrupados en una 'pila'. Los recursos se definen en las plantillas escritas en JSON o YAML y la cloudformation herramienta permite que la infraestructura sea creado, modificado a través de conjuntos de cambios y destruido.

Además, si define una plantilla en la que las funciones de Lambda apuntan al código local, puede usar el comando del package para enviar su controlador local a S3 y generar una plantilla que apunte a ese URI cargado.

deploy comandos para implementar actualizaciones, que intentarán migrar su pila de destino a un estado que coincida con la plantilla que usted proporciona, incluida la última versión de sus controladores Lambda, que ahora apuntan a artefactos S3.

  • package analizará el archivo de la plantilla (yaml o json), buscará las funciones con un codeUri que apunte a un manejador, paquete del sistema de archivos local y lo cargará a S3, y luego codeUris una plantilla empaquetada donde el codeUris ahora artefactos S3.
  • deploy carga su plantilla empaquetada en CloudFormation, crea un conjunto de cambios y lo ejecuta.

Si todo va bien, su pila se actualizará según la especificación exacta de la plantilla empaquetada que proporcionó. En caso de que algo vaya mal CloudFormation revertirá todos los cambios y revertirá la pila al estado anterior.

Si está interesado en una herramienta similar que admita múltiples proveedores, consulte Terraform .

SAM

El modelo de aplicación Serverless es un intento de simplificar la definición de aplicaciones sin servidor extendiendo la especificación CloudFormation. Agrega tres nuevos tipos de recursos:

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

Ninguna de las anteriores son nuevas primitivas de AWS, sino envoltorios de recursos existentes de CloudFormation.

SAM tiene como objetivo abstraer la complejidad / verbosidad de tener que definir sus propias etapas, implementaciones, permisos, polos, etc. de la Pasarela API. Pero dado que es una nueva extensión, estas abstracciones pueden filtrarse cuando no se espera o, por el contrario, parece demasiado opaco cuando necesitas más control.

Afortunadamente, hay trabajo en curso para introducir una serie de características importantes que incluyen soporte de primera clase para autorizadores personalizados, CORS y planes de uso.

Entorno de desarrollo local

Para ejecutar y probar aplicaciones SAM localmente, awslabs lanzó sam local , una CLI que se puede usar para invocar funciones Lambda directamente o iniciar una puerta de enlace de API simulada local desde una plantilla SAM que invocará sus funciones para manejar las solicitudes entrantes. Lo hace ejecutando los controladores locales en contenedores Docker que imitan el entorno de ejecución real de Lambda. En caso de que se lo pregunte, también viene con soporte para el soporte oficial recientemente anunciado para Go on AWS Lambda.

La herramienta admite la recarga en caliente, aunque en el caso de Go aún necesita recompilar el binario usted mismo, y debe recordar orientar la configuración de Linux GOOS=linux .

Así es como inicia una puerta de enlace API local:

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

En el momento de escribir, sam local tiene algunas limitaciones, a saber:

  • falta soporte para los autores autorizados, con suerte eso cambiará después de que SAM presente soporte de primera clase para los autores.
  • error que impide que los archivos yaml externos de OpenAPI funcionen (aunque JSON parece funcionar de acuerdo con varios informes de usuario)

Pila individual frente a pila múltiple

Las personas todavía están descubriendo cómo usar mejor estas herramientas en el mundo real, como lo demuestran los problemas en los que los usuarios solicitan asesoramiento sobre modelado de infraestructura. Un ejemplo de un área contenciosa es la gestión de diferentes entornos, como desarrollo, puesta en escena, producción, etc.

Hay dos enfoques generales para este problema, utilizando una sola pila o varias pilas .

API Gateway y Lambda requieren diferentes configuraciones en configuraciones de pila única y múltiple.

La opción de pila única comparte sus funciones API Gateway y Lambda en todos los entornos, aprovechando etapas de API, variables de escenario y alias Lambda, mientras que el enfoque de varias pilas utiliza una pila por entorno, cada una con sus propios recursos, evitando la indirección de etapas API y Alias ??Lambda para separar entornos.

Inicialmente nos fijamos en el enfoque de pila única, que parecía más idiomático porque aprovechaba al máximo los conceptos que API Gateway y Lambda nos proporcionan.

Desafortunadamente en la práctica, esto no pareció funcionar tan bien. El soporte de etapas múltiples en SAM aún no está claro y es peculiar , parece difícil administrar varias etapas de API Gateway y alias de Lambda en una sola plantilla. También nos dimos cuenta de que sus recursos de AWS estaban extremadamente acoplados en todos los entornos, no simplemente replicados.

Esto nos llevó a investigar más, ver más allá de la documentación oficial.

Nos encontramos con esta charla de Chris Munns, un defensor del desarrollador de AWS Serverless, que recomienda usar una sola pila si eres un equipo pequeño y estás empezando y una pila múltiple si tienes una configuración más compleja con múltiples equipos, estricta requisitos de permisos o simplemente prefieren una mejor separación de recursos.

El ingeniero de Lambda, Jacob Fuss, por otro lado, es más directo en su respaldo de la pila múltiple:

No te recomiendo (ni a nadie) que uses alias para varios entornos. La mayor preocupación que tengo con esto es que corres el riesgo de impactar a prod con un cambio de prueba o desarrollo. Está creando un radio de explosión más grande en caso de que algo salga mal con una implementación. Mi otra preocupación sería la seguridad de tus funciones. Es posible que deba agregar credenciales o políticas específicas para dev o test que se puedan replicar en prod. Mi sugerencia es dividir dev, test y prod en pilas de CloudFormation separadas para que cada uno de los entornos esté aislado entre sí. Entonces solo tiene que administrar la plantilla de CloudFormation y puede implementarla a través de su sistema de CI / CD en un nivel de entorno. Todavía solo administrará una función Lambda (a través de SAM), pero esta configuración reducirá el radio de explosión para las implementaciones y aislará las funciones y los recursos de su entorno.

Al final, decidimos optar por un enfoque de múltiples niveles, uno por entorno, administrado a través de una única plantilla.

La clave para un enfoque de pila múltiple es reutilizar la misma plantilla SAM y confiar en los parámetros de la plantilla para apuntar a cada entorno. Esto garantiza que las pilas se vean exactamente iguales en todos los entornos.

El inconveniente principal evidente es la desconexión entre las versiones de Lambda en diferentes entornos. Por ejemplo, aunque el mismo código exacto se podría ejecutar en las etapas de preparación y producción, los Lambdas reales serán recursos diferentes en AWS, con diferentes versiones. Solo sabemos que están ejecutando el mismo código porque utilizamos la misma plantilla empaquetada para implementar en ambos entornos, y esa plantilla apuntó a los mismos artefactos en S3, y creó versiones Lambda con el mismo código en ambas pilas.

Además, la plantilla puede llegar a ser más compleja en caso de que deseemos variar las propiedades de los recursos por entorno, por ejemplo, tener una instancia RDS de tamaño diferente.

Poniendolo todo junto

Después de haber decidido un enfoque de pila múltiple, nuestra configuración actual de CI se mantuvo bastante sencilla: usamos CodePipeline para realizar automáticamente la última confirmación en maestro, ejecutar pruebas unitarias, compilar controladores e implementar las nuevas versiones en la pila de desarrollo antes de aprobar manualmente las implementaciones para la producción y puesta en escena pilas.

La etapa de compilación usa CodeBuild para probar y construir los manejadores, y para ejecutar el comando aws cloudformation package formación en la nube, que genera una plantilla empaquetada:

 paquete de formato de nube aws --template-file stack-template.yaml --s3-bucket <s3-bucket> --output-template-file packaged-template.yaml 

La plantilla empaquetada se pasa luego a las siguientes etapas donde la integración de CloudFormation la utiliza para implementarla en cada entorno, proporcionando el parámetro StageName través parameter-overrides :

 aws cloudformation deploy --template-file packaged-template.yaml --stack-name <StackDev | StackStaging | StackProd> --capabilities CAPABILITY_NAMED_IAM --parameter-overrides StageName = <Dev | Staging | Prod> 

Conclusión

Después de pasar un tiempo familiarizándonos con más conceptos de AWS de los que hubiéramos deseado, intentando un enfoque de pila única con SAM y examinando numerosos problemas de GitHub, publicaciones de blog y videos, finalmente descubrimos que el enfoque de múltiples niveles era la mejor manera de hacerlo. nosotros para alcanzar nuestro objetivo multi-ambiente. Esperamos que este informe ayude a cualquier persona que tenga preguntas similares. Mientras tanto, vigilaremos los problemas mencionados anteriormente.

Lo que no cubrimos

  • Pruebas de integración a través de una pila dedicada, como parte de nuestra canalización CI.
  • Cambios de tráfico para implementaciones a través de alias de Lambda. Tenga en cuenta que los alias siguen siendo una buena forma de controlar las implementaciones dentro de un entorno, pero no tanto por la separación del entorno.