Despliegue de servicios con AWS CloudFormation
En mi post anterior implementé los recursos necesarios en AWS para construir un backend serverless para mi formulario de contacto. Llevé a cabo todo el proceso de desplegar los servicios manualmente desde la consola web de AWS. Ahora, para seguir aplicando buenas prácticas, reproduzco ese despliegue con CloudFormation para desplegar infraestructura como código.
Infraestructura como Código
La Infraestructura como Código (IaC) es la práctica de definir y desplegar infraestructura de TI mediante ficheros de configuración legibles por máquina, en lugar de procesos manuales. Con IaC, la infraestructura puede tratarse como código: se puede versionar, probar y desplegar igual que el software.
Una de las principales ventajas de usar IaC es que permite a los equipos desplegar y gestionar la infraestructura de forma más eficiente y consistente. Al usar sistemas de control de versiones, los equipos pueden rastrear y gestionar los cambios de la infraestructura a lo largo del tiempo. También permite pruebas y despliegues automatizados, lo que reduce el riesgo de error humano y aumenta la velocidad y fiabilidad de los despliegues. Además, IaC favorece la transparencia de la infraestructura, ya que ofrece una visión clara de qué infraestructura está en uso y cómo está configurada.
AWS CloudFormation
AWS CloudFormation es un servicio de AWS que permite definir la infraestructura como código mediante plantillas escritas en YAML o JSON. Las plantillas contienen una descripción de los recursos que se van a crear —como instancias EC2, balanceadores de carga o bases de datos—, así como las configuraciones necesarias y las dependencias entre ellos.
Cuando se crea, actualiza o elimina un stack de CloudFormation, el servicio lee la plantilla y la usa para aprovisionar y configurar los recursos correspondientes. CloudFormation se encarga de todas las acciones necesarias, como crear, actualizar o eliminar recursos, en el orden correcto, y garantiza que queden correctamente configurados y conectados. CloudFormation ofrece funcionalidades como protección frente a rollback, etiquetado de recursos y detección de drift, que simplifican todavía más la gestión de los recursos de infraestructura.
JSON o YAML
A la hora de definir una plantilla de CloudFormation se puede usar tanto JSON como YAML. Sin embargo, en mi opinión, es mejor usar YAML porque es más fácil de leer y escribir que JSON. YAML usa menos caracteres y no necesita llaves ni comillas, lo que lo hace visualmente menos ruidoso. Además, tiene una estructura más flexible y menos estricta que JSON, lo que facilita escribirlo y lo hace más intuitivo de entender. YAML también admite comentarios, lo que facilita documentarlo y mantenerlo. Por estas razones, uso YAML para definir mi plantilla de CloudFormation.
MyStacks
Entre los servicios que he desplegado, algunos son generales para MyWebsite y otros son específicos del servicio ContactForm. Al definir la infraestructura como código, es importante estructurar y separar bien mis stacks de infraestructura para que puedan interactuar entre sí, y para poder definir infraestructura de forma independiente para cada proyecto.
En este caso tengo el proyecto MyWebsite y un servicio adicional, ContactForm. En el futuro, MyWebsite podría tener otros servicios independientes de ContactForm, así que defino un Stack independiente para los recursos de MyWebsite que compartirán todos los servicios que despliegue después. Creo un segundo Stack que recibe como parámetro de entrada el ApiId proporcionado por el primer Stack, para poder referenciarlo desde este segundo Stack.
Stack de MyWebsite
En este stack se despliega toda la infraestructura necesaria para MyWebsite. Por ahora solo consiste en el servicio API Gateway, pero en el futuro este stack se usará para desplegar más servicios, como la infraestructura necesaria para el hosting.
El primer paso es definir la API HTTP. Uso el tipo de recurso AWS::ApiGatewayV2::Api para definir las configuraciones necesarias, como el CORS o el protocolo. Además, deshabilito la URL de entrada por defecto de la API, ya que se usará un dominio personalizado.
Api:
Type: AWS::ApiGatewayV2::Api
Properties:
Name: jaimeelso
Description: API that manages all the backend calls from MyWebsite
ProtocolType: HTTP
RouteSelectionExpression: $request.method $request.path
DisableExecuteApiEndpoint: true
CorsConfiguration:
AllowCredentials: false
AllowHeaders:
- content-type
AllowMethods:
- POST
- OPTIONS
AllowOrigins:
- https://jaimeelso.com
MaxAge: 0
Con el API Gateway ya configurado, hace falta crear un Stage. Uso AWS::ApiGatewayV2::Stage para crear el stage por defecto y configurarlo con despliegue automático. Además, limito la gestión de peticiones a solo 1 petición por segundo y 1 petición concurrente, suficiente para el tráfico actual de la web.
ApiStage:
Type: AWS::ApiGatewayV2::Stage
Properties:
ApiId: !Ref Api
StageName: default
Description: Default stage
AutoDeploy: true
DefaultRouteSettings:
DetailedMetricsEnabled: false
ThrottlingBurstLimit: 1
ThrottlingRateLimit: 1.0
Una vez listo el stack principal, hace falta exportar el valor del API ID. Es necesario porque el stack de ContactForm definirá servicios que necesitan referenciar este valor en algunas configuraciones. Uso la sección Outputs de mi stack para exportar el ID. Estas variables exportadas deben ser únicas en cada región, así que es buena práctica usar NombreDelStack-NombreDeLaVariable como nombre de variable. Uso el pseudoparámetro AWS::StackName para obtener el nombre del stack dentro de mi plantilla.
Outputs:
ApiId:
Value: !Ref Api
Description: The ID of the AWS API Gateway that manages the backend on the web jaimeelso.com.
Export:
Name: !Sub "${AWS::StackName}-ApiId"
Stack de ContactForm
Para que el stack sea lo más flexible posible y se adapte a despliegues futuros, uso la sección Parameters para incluir parámetros de entrada que mi plantilla pueda usar. Incluyo la dirección de correo para suscribirme a mi topic de SNS, la clave privada de la API de reCaptcha, el S3 Bucket, la key del objeto donde está el código de mi función lambda y, por último, el nombre del stack principal donde he definido el API Gateway.
Parameters:
SubscriptionEndpoint:
Type: String
Description: The email address to which contact form messages will be sent
RecaptchaSecretKey:
Type: String
Description: The secret key to call de reCaptcha API from the backend
LambdaS3Bucket:
Type: String
Description: Bucket name where Lambda code is place
LambdaS3Key:
Type: String
Description: Bucket key where Lambda code is place
ApiStackName:
Type: String
Description: API Stack name
A partir de aquí, estos parámetros se pueden usar para definir recursos en este Stack. En él defino el topic y la suscripción de SNS, la función lambda, los secretos de SecretsManager, y la ruta e implementación del API Gateway que redirige las peticiones a la función lambda. También defino el rol de ejecución de la lambda y la política de permisos asociada.
Como la plantilla contiene mucho código, el fichero completo está disponible en Github para quien quiera consultarlo.
Conclusión
Al definir mi infraestructura como código en plantillas de CloudFormation, tengo mayor control sobre la infraestructura desplegada en AWS, lo que simplifica el mantenimiento y las actualizaciones futuras. También facilita migrar mi entorno a otra región o cuenta de AWS si lo necesito en el futuro.
Por ahora, mis plantillas se guardan en un bucket S3 privado y los despliegues se hacen manualmente desde la consola de CloudFormation. La implementación de un pipeline para automatizar el despliegue y las actualizaciones de estos stacks llegará más adelante. Escribiré un post dedicado a implementar CI/CD para todos los servicios de AWS que necesiten despliegues automatizados.
¡Gracias por leer! Nos vemos en el próximo.