Usando AWS para desplegar un proyecto de una startup: Parte 1 - Frontend

Usando AWS para desplegar un proyecto de una startup: Parte 1 - Frontend

El desafío

Años atrás, me uní a una startup para crear un producto que estaba intentando encontrar su espacio en el mercado para su idea. Primero me uní como desarrollador Frontend y, después de acabar con la primera versión de la aplicación, quería involucrarme más en el rol Backend. Así que intercambiamos roles con el desarrollador Backend.

La aplicación consistía en tres servicios: una API Backend, un Frontend cuyo objetivo era ser desplegado en dispositivos móviles, y un panel de administración.

Decidimos con el CTO en ese tiempo que podríamos desplegar nuestros servicios en AWS, y que podríamos usar Docker Images, así que escogimos AWS ECS como nuestra plataforma de despliegue.

No escogimos Kubernetes ya que se veía como un exceso, pues no teníamos tantos usuarios al principio.

Sin embargo, tuvimos unos requerimientos específicos:

Frontend: Debería estar almacenado en caché del lado del cliente, y el usuario debería ser capaz de subir una foto.

API Backend: Debería ser pública, para ser consumida por la aplicación móvil y el Frontend.

Admin: Debería ser una herramienta de uso interno. Ya que éramos una startup pequeña en ese entonces, estaba bien usar conexiones VPN.

Para todos los casos anteriores, requeríamos un entorno de pruebas, al que llamamos entorno staging.

Para almacenar el dominio, usamos Route53, y ya que teníamos que validar el certificado, podíamos automatizar la validación a través de DNS.

Para los siguientes ejemplos, usaré thestartup.domain (lastartup.dominio en español) como dominio de ejemplo. Los ejemplos pueden variar del diagrama y casos reales, ya que esto es un concepto general basado en mi memoria.

Por que evitamos usar sólo la consola de AWS, y por qué usamos Terraform

Primero experimentamos creando unos recursos manualmente en la consola, pero vimos que depender mucho de la memoria para empezar a hacer modificaciones era peligroso, ya que podríamos dañar algunos recursos accidentalmente.

A pesar de que teníamos acceso a CloudFormation, no recuerdo qué características nos faltaban en ese tiempo, así que decidimos ir con Terraform en su lugar. Sólo recuerdo que la razón principal que nos hizo escogerlo, era que la sintaxis de Terraform se veía mucho más limpia y fácil comparada con CloudFormation.

Frontend

Arquitectura

Para el frontend, averiguamos que podíamos usar S3 para almacenar nuestra aplicación estática. Podíamos usar CloudFront conectado a AWS Certificate Manager para desplegar una aplicación completa estática HTTPS para servir HTML. Además, ya que CloudFront almacena en caché los contenidos en sus Edges, se vio como la opción más lógica.

AWS Frontend

Dos zonas diferentes de disponibilidad

Usamos dos zonas de disponibilidad, ya que usar sólo us-east-1 es riesgoso, pues todos los nuevos cambios de AWS serán lanzados allí primero y pueden causar algunas disrupciones a la plataforma.

El único servicio que no podíamos mover fue el Administrador de Certificados (SSL). Para validar nuestro dominio, siempre debía usar us-east-1.

Usando dos dominios

La razón por la cual usamos dos dominios para desplegar la aplicación, fuera de “thestartup.domain”, era porque la página principal era una página landing, y queríamos desplegar la aplicación real dentro de un subdominio. Como escribí antes, tuvimos que desplegar un entorno de staging que cualquiera de nuestros desarrolladores pudiera utilizar.

Almacenando los entornos de producción y staging dentro del mismo bucket

Por supuesto, podíamos crear un bucket para cada proyecto. Pero para permitirle a Terraform fácilmente replicar un entorno por desarrollador, usamos un bucket único con diferentes directorios, ya que crear un bucket por entorno se veía como sobre-ingeniería para la arquitectura.

Además, habilitamos la característica de servir el contenido del bucket como una página web estática.

Pero no queríamos exponer todo el contenido del bucket a todo el público.

Usando Cloudfront para mostrar el contenido del bucket

Para tomar ventaja del contenido en caché y evitar exponerlo, creamos dos distribuciones CloudFront dentro de la capa más asequible: PriceClass_100. Con esto, nos aseguramos de que nuestro contenido sólo estaría en caché para el público de América (norte y sur).

Certificado

Nuestro certificado era de tipo wildcard. Esto por si necesitáramos tener más alternativas o instancias de prueba en el frontend. Un certificado de tipo wildcard te permite asignar múltiples subdominios a un certificado único.

Para este caso, usamos *.frontend.thestartup.domain.

Un entorno por desarrollador

Por un par de semanas, intentamos tener algo como desarrolladoruno.frontend.thestartup.domain. Pero, ya que no tuvimos tiempo de reforzar algunas políticas al momento de hacer merge en nuestras ramas de Git, era más difícil mantener el código de ese modo. La idea fue tener entornos frontend alternativos apuntando a la misma API de staging.

Incluso si hubiéramos querido recrear la misma estructura corriendo Terraform por cada Pull Request, podría tomar demasiado tiempo esperar por el despliegue, así que descartamos esta idea.

Sí es posible, por supuesto, pero si queremos una manera más sencilla de hacerlo, sería con contenedores.

Flujo para desplegar el proyecto (Pipeline)

Deployment

El flujo era para construir el HTML y desplegarlo como contenido estático. Para evitar salirme del sistema de AWS, estaba usando AWS CodeCommit combinado con AWS CodeBuild para la versión uno.

Pero las GitHub Actions comenzaron a ganar popularidad en ese entonces, y fue cuando decidimos hacer el salto a GitHub Actions, ya que tenía la posibilidad de crear reportes por Pull Request.

Entonces, en el bucket teníamos dos directorios: uno llamado /production (producción) y otro con /staging. Antes de construir cada proyecto, tuvimos que cambiar las variables de entorno para apuntar a la API de staging también.

Flujo en Github

Este es un script similar al que utilizamos para desplegar el código.

DOMAIN_NAME (nombre del dominio) se cambiaba entre entornos, ya que solíamos detectar cuál distribución de CloudFront queríamos apuntar para invalidar su caché una vez que hayamos desplegado el contenido en el bucket.

name: Desplegando producción
on: workflow_dispatch
jobs:
deploy:
runs-on: ubuntu-latest
env:
NODE_OPTIONS: --max-old-space-size=8192
ENVIRONMENT: production
steps:
- name: Checkout
uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: ".nvmrc"
cache: npm
- name: Instalando la última versión de NPM
run: npm install -g npm@10
- name: Instalando JQ
run: sudo apt-get install jq
- name: Configurar las credenciales de AWS
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_FRONTEND_PIPELINE_ID }}
aws-secret-access-key: ${{ secrets.AWS_FRONTEND_PIPELINE_KEY }}
aws-region: us-east-2
- name: Obtener y mostrar url del frontend en CloudFront
id: domain-data
run: |
DOMAIN_CNAME=$(dig -t cname app.thestartup.domain +short)
DOMAIN_CLOUDFRONT=${DOMAIN_CNAME%?}
echo "::set-output name=domain_name::$DOMAIN_CLOUDFRONT"
- name: Instalando dependencias
run: npm ci
- name: Copiar archivo env para producción
run: npm run prod:config
- name: Creando salida de frontend
run: npm run prod:build
- name: Copiar archivos a S3 e invalidar la caché de CloudFront
env:
CLOUDFRONT_DISTRIBUTION: ${{ steps.domain-data.outputs.domain_name }}
run: |
CLOUDFRONT_ID_RAW=$(aws cloudfront list-distributions|jq '.DistributionList.Items[] | select(.DomainName=="'$CLOUDFRONT_DISTRIBUTION'") | .Id')
CLOUDFRONT_ID="${CLOUDFRONT_ID_RAW//\"}"
aws s3 sync ./www s3://cloudfront-frontend-default-bucket/$ENVIRONMENT --delete
aws cloudfront create-invalidation --distribution-id $CLOUDFRONT_ID --paths '/*'

¿Qué mejoraría hoy en día?

¿Te ha pasado que has revisado tu código que escribiste años atrás y te sientes avergonzado? Es totalmente normal. Nosotros, como desarrolladores, una vez que obtenemos más conocimiento, veremos nuestros errores del pasado y pensaremos en maneras de cómo podrían haber sido evitados. Es por eso que creé una lista de mejoras que haría si tuviera que crear el mismo entorno actualmente.

Agregar WAF

Ya que la seguridad es tan importante hoy en día, agregar WAF sería obligatorio para mí. AWS WAF agrega una protección contra inyecciones SQL y cross-site scripting (XSS). Además, agrega una capa de protección contra bots. En el mundo actual, los bots continuarán usando tu sitio repetidamente, buscando alguna vulnerabilidad, o estarán “leyendo” (scraping) tu sitio, consumiendo recursos y memoria.

AWS Frontend Improved

Usar un bucket diferente por proyecto

Por objetivos de simplicidad, estaba intentando poner todos los contenidos del despliegue dentro del mismo bucket. Así, una vez que tuviera que dar acceso a cada desarrollador, sería simple, ya que sólo tenía que lidiar con un solo recurso.

Mi meta será crear buckets separados por cada entorno, permitiéndome administrar cada permiso de desarrollador individualmente por recurso y restringir el acceso al bucket de producción al personal autorizado dentro de la empresa.

AWS Frontend Deployment Improved

Si realmente necesitáramos un entorno diferente por desarrollador o por Pull Request

Si tuviéramos que tener más entornos fuera de staging o producción, preferiría desplegar esos entornos temporales dentro de un contenedor NGINX que serviría el contenido como un servidor web, ya que esperar por la creación de una distribución CloudFront puede ser costoso en temas de dinero y tiempo.

Acerca de la segunda parte

La parte 2 cubrirá el backend, donde la mayor complejidad estaba. Ya que teníamos que desplegar por contenedores, AWS ECS nos ayudó a reducir el mantenimiento de nuestros contenedores.

Voy a actualizar la referencia al post en la sección recomendados una vez que el post esté listo.

Mis posts no son generados por la IA, sin embargo, podrían estar corregidos por ella. El primer borrador siempre es de mi creación

Tags

Autor

Escrito por Helmer Davila

En otros lenguajes

How we deployed the frontend of a startup project using AWS

Using AWS to deploy a Startup project: Part 1 - Frontend

Comme nous avons déployé le frontend d’un projet de startup en utilisant AWS

Utiliser AWS pour déployer un projet d’une startup: Partie 1 - Frontend