Logo
Jaime Elso

Arquitecto de Soluciones AWS

EN

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.

Diagrama de stacks

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.