Ir al contenido

Cierre, costos y limpieza

Objetivo de este módulo: cerrar bien. Entender el costo, saber qué mirar cuando algo falle, y no dejar nada corriendo.

Recapitulando, en un día:

Una SPA sin framework

Reactividad con signals, router propio sobre la History API, y un puente al DOM de cien líneas que escribiste tú. Doce kilobytes de JavaScript.

Infraestructura como código

Bucket privado con OAC, CloudFront con TLS, certificado que se renueva solo, y dos entornos con estado separado. Todo reproducible desde cero.

Despliegue sin credenciales

OIDC en lugar de claves de acceso. Roles de mínimo privilegio, acotados a un bucket y una distribución exactos.

Releases auditables

Versión inmutable por tag, aprobación humana para producción, y reversión en un minuto sin recompilar.

Y sin una sola instancia que administrar.

Con tráfico de taller, el costo mensual de este montaje está en el orden de centavos. Vale la pena entender de dónde sale cada partida, porque la intuición suele estar mal calibrada.

Partida Cómo se cobra En este taller
Almacenamiento S3 por GB al mes despreciable, el sitio pesa kilobytes
Peticiones S3 por millón despreciable, CloudFront absorbe casi todo
Transferencia de CloudFront por GB servido la partida principal
Peticiones de CloudFront por millón despreciable con tráfico bajo
Certificado de ACM gratis para CloudFront cero
Invalidaciones primeras 1000 rutas al mes gratis cero, invalidamos dos por despliegue
Estado en S3 por GB y peticiones céntimos

Compáralo con lo que costaría una instancia pequeña corriendo veinticuatro horas al día para servir los mismos archivos estáticos, más su volumen, más el balanceador si quieres alta disponibilidad.

No montamos observabilidad en este taller, pero conviene saber qué existe y dónde mirar cuando algo falle.

Los logs de acceso de CloudFront se pueden enviar a S3 o a CloudWatch. Es lo que responde «cuántos usuarios vieron el error» y «desde qué países».

Las métricas de CloudFront en CloudWatch dan tasa de error 4xx y 5xx, y la tasa de acierto de caché. Esa última es el indicador de si tu estrategia de Cache-Control está funcionando: si es baja, algo se está pidiendo al origen que no debería.

CloudTrail registra quién hizo qué en la cuenta. Es donde aparecen los role-session-name de tus despliegues, y por eso vale la pena que sean descriptivos.

El resumen de cada ejecución en GitHub Actions, que los workflows escriben con GITHUB_STEP_SUMMARY. Versión, commit y URL, sin abrir los logs.

Síntoma Causa habitual
403 con XML al recargar una ruta falta el custom_error_response en CloudFront
El despliegue pasa pero no ves los cambios el index.html se está cacheando
Not authorized to perform sts:AssumeRoleWithWebIdentity falta id-token: write, o el nombre del entorno no coincide con el claim sub
El release por tag falla al autenticar el tag no coincide con refs/tags/v*, o se disparó desde una rama
Bucles de redirección con dominio propio hay un proxy/CDN intermedio delante de CloudFront; con Route 53 usa alias directo
Cannot find native binding se mezclaron imágenes de Docker con libc distintas
El bucket responde 200 directo falta el bloqueo de acceso público, revísalo de inmediato

Hazlo en este orden. Los entornos primero, el bootstrap al final, porque contiene el bucket de estado del que dependen los demás.

  1. Vacía los buckets del sitio. Terraform no puede destruir un bucket con objetos:

    Ventana de terminal
    cd taskflow-p01-infra
    make shell
    Ventana de terminal
    aws s3 rm "s3://taskflow-p01-prod-123456789012" --recursive
    aws s3 rm "s3://taskflow-p01-dev-123456789012" --recursive
    exit
  2. Destruye producción y desarrollo:

    Ventana de terminal
    make destroy ENV=prod
    make destroy ENV=dev

    CloudFront tarda en deshabilitar y eliminar una distribución. Es normal que este paso se lleve varios minutos.

  3. Con dns_mode = "route53", Terraform creó los registros DNS (validación del certificado y alias del subdominio), así que el destroy de prod ya los eliminó por ti. No hay que borrar nada a mano en un panel externo.

  4. Destruye el bootstrap. Antes hay que vaciar el bucket de estado, que tiene force_destroy = false a propósito:

    Ventana de terminal
    make shell
    Ventana de terminal
    aws s3 rm "s3://taskflow-p01-tfstate-123456789012" --recursive
    exit
    Ventana de terminal
    cd bootstrap
    terraform destroy
Ventana de terminal
make shell
Ventana de terminal
aws s3 ls | grep taskflow
aws cloudfront list-distributions --query "DistributionList.Items[].Comment" --output text
aws iam list-roles --query "Roles[?starts_with(RoleName, 'taskflow')].RoleName" --output text
aws acm list-certificates --region us-east-1 --query "CertificateSummaryList[].DomainName" --output text

Los cuatro deberían salir vacíos de tus recursos. Revisa además la consola de facturación en un par de días: es la comprobación definitiva.

Tres ideas que sirven más allá de este taller.

Una SPA no necesita un servidor. Si tu aplicación compila a archivos estáticos, ponerla en una instancia te da trabajo de operación sin darte nada a cambio. La API es otra discusión.

La política de seguridad va en la infraestructura, no en el pipeline. Un archivo YAML lo cambia cualquiera con permiso de escritura. Una condición en la política de confianza de un rol de IAM, no.

Los artefactos inmutables son lo que hace posible revertir. Assets con hash en el nombre, un index.html como interruptor, y nada de --delete. Esa combinación convierte una reversión en un minuto en lugar de en una reconstrucción.

El workshop 02 le da backend a esta misma SPA: API Gateway con HTTP API, Lambda en TypeScript y DynamoDB. El estado deja de vivir en localStorage y se reemplaza el efecto de persistencia por llamadas a la API, sin tocar el resto de la aplicación.

Después vienen autenticación con Cognito, observabilidad, y Terraform desde el pipeline con los permisos acotados que ese tema requiere.

Ventana de terminal
cd taskflow-p01-infra
make shell
# vaciar los buckets del sitio
aws s3 rm "s3://taskflow-p01-prod-123456789012" --recursive
aws s3 rm "s3://taskflow-p01-dev-123456789012" --recursive
exit
make destroy ENV=prod
make destroy ENV=dev
# el destroy de prod ya eliminó los registros de Route 53; luego
make shell
aws s3 rm "s3://taskflow-p01-tfstate-123456789012" --recursive
exit
cd bootstrap && terraform destroy
# comprobar que no quedó nada
make shell
aws s3 ls | grep taskflow
aws cloudfront list-distributions --query "DistributionList.Items[].Comment" --output text
aws iam list-roles --query "Roles[?starts_with(RoleName, 'taskflow')].RoleName" --output text
aws acm list-certificates --region us-east-1 --query "CertificateSummaryList[].DomainName" --output text

Gracias por llegar hasta aquí. Si algo de la guía te hizo perder tiempo, dilo: es un error nuestro y se corrige.