Skip to content

Guia: Desplegar un Servicio a AWS

Esta guia describe el proceso completo para desplegar un microservicio de la plataforma Imagy a AWS, desde la construccion de la imagen Docker hasta la verificacion en produccion, incluyendo el pipeline de CI/CD y la estrategia de promocion entre ambientes.

Prerequisitos

  • AWS CLI configurado con credenciales apropiadas
  • Docker instalado localmente
  • Acceso al repositorio ECR del servicio
  • Permisos de deployment en el ambiente destino
  • Servicio pasando todos los tests en CI

Arquitectura de Deployment

1. Build de la Imagen Docker

Build local (para testing)

bash
# Desde la raiz del repositorio del servicio
docker build -t imagy-{domain}:local \
  -f src/Imagy.{Domain}.Api/Dockerfile .

# Verificar que la imagen funciona
docker run --rm -p 8080:8080 \
  -e ASPNETCORE_ENVIRONMENT=Development \
  imagy-{domain}:local

# Verificar health check
curl http://localhost:8080/health/live

Build en CI (GitHub Actions)

El pipeline de CI construye la imagen automaticamente en cada push:

yaml
# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  build-and-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup .NET
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '10.0.x'

      - name: Restore
        run: dotnet restore

      - name: Build
        run: dotnet build --no-restore

      - name: Test
        run: dotnet test --no-build --verbosity normal

      - name: Build Docker image
        run: |
          docker build -t imagy-{domain}:${{ github.sha }} \
            -f src/Imagy.{Domain}.Api/Dockerfile .

2. Push a ECR

bash
# Autenticarse en ECR
aws ecr get-login-password --region us-east-1 | \
  docker login --username AWS --password-stdin \
  {account_id}.dkr.ecr.us-east-1.amazonaws.com

# Tag de la imagen
docker tag imagy-{domain}:local \
  {account_id}.dkr.ecr.us-east-1.amazonaws.com/imagy-{domain}:{version}

# Push
docker push \
  {account_id}.dkr.ecr.us-east-1.amazonaws.com/imagy-{domain}:{version}

En CI, esto se automatiza:

yaml
# .github/workflows/deploy.yml (fragmento)
  push-to-ecr:
    needs: build-and-test
    runs-on: ubuntu-latest
    steps:
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::{account_id}:role/github-actions-deploy
          aws-region: us-east-1

      - name: Login to ECR
        uses: aws-actions/amazon-ecr-login@v2

      - name: Build and push
        run: |
          docker build -t $ECR_REGISTRY/imagy-{domain}:${{ github.sha }} .
          docker push $ECR_REGISTRY/imagy-{domain}:${{ github.sha }}

3. Ejecutar Migraciones

Las migraciones se ejecutan antes del deployment del servicio, como un paso separado:

bash
# Ejecutar migraciones contra el ambiente destino
# Opcion 1: Task de ECS dedicada
aws ecs run-task \
  --cluster imagy-{environment} \
  --task-definition imagy-{domain}-migrations \
  --overrides '{
    "containerOverrides": [{
      "name": "migrations",
      "command": ["dotnet", "Imagy.{Domain}.Api.dll", "--migrate"]
    }]
  }' \
  --network-configuration '{
    "awsvpcConfiguration": {
      "subnets": ["subnet-xxx"],
      "securityGroups": ["sg-xxx"]
    }
  }'

# Opcion 2: Desde CI con conexion directa (solo develop)
dotnet ef database update \
  --connection "Host={rds-endpoint};Database=imagy_{domain};..."

Reglas de migraciones

  • Las migraciones deben ser backward-compatible (no romper la version anterior del servicio).
  • No eliminar columnas en la misma release que deja de usarlas. Usar un ciclo de 2 releases:
    1. Release N: dejar de usar la columna (codigo no la lee ni escribe).
    2. Release N+1: eliminar la columna en migracion.
  • Agregar columnas como nullable o con default value.
  • Indices nuevos se crean con CONCURRENTLY para no bloquear la tabla.

4. Deploy a ECS

Actualizar Task Definition

bash
# Crear nueva revision de la task definition con la nueva imagen
aws ecs register-task-definition \
  --family imagy-{domain}-{environment} \
  --container-definitions '[{
    "name": "api",
    "image": "{account_id}.dkr.ecr.us-east-1.amazonaws.com/imagy-{domain}:{version}",
    "portMappings": [{"containerPort": 8080, "protocol": "tcp"}],
    "healthCheck": {
      "command": ["CMD-SHELL", "curl -f http://localhost:8080/health/live || exit 1"],
      "interval": 30,
      "timeout": 5,
      "retries": 3,
      "startPeriod": 60
    },
    "environment": [
      {"name": "ASPNETCORE_ENVIRONMENT", "value": "{Environment}"}
    ],
    "secrets": [
      {"name": "ConnectionStrings__DefaultConnection", "valueFrom": "arn:aws:secretsmanager:..."},
      {"name": "ConnectionStrings__RabbitMq", "valueFrom": "arn:aws:secretsmanager:..."},
      {"name": "ConnectionStrings__Redis", "valueFrom": "arn:aws:secretsmanager:..."}
    ],
    "logConfiguration": {
      "logDriver": "awslogs",
      "options": {
        "awslogs-group": "/ecs/imagy-{domain}-{environment}",
        "awslogs-region": "us-east-1",
        "awslogs-stream-prefix": "api"
      }
    }
  }]' \
  --requires-compatibilities FARGATE \
  --cpu 512 \
  --memory 1024 \
  --network-mode awsvpc \
  --execution-role-arn arn:aws:iam::{account_id}:role/ecsTaskExecutionRole \
  --task-role-arn arn:aws:iam::{account_id}:role/imagy-{domain}-task-role

Actualizar el Servicio

bash
# Forzar nuevo deployment con la ultima task definition
aws ecs update-service \
  --cluster imagy-{environment} \
  --service imagy-{domain} \
  --force-new-deployment \
  --deployment-configuration '{
    "maximumPercent": 200,
    "minimumHealthyPercent": 100,
    "deploymentCircuitBreaker": {
      "enable": true,
      "rollback": true
    }
  }'

Monitorear el Deployment

bash
# Esperar a que el deployment se estabilice
aws ecs wait services-stable \
  --cluster imagy-{environment} \
  --services imagy-{domain}

# Verificar estado del servicio
aws ecs describe-services \
  --cluster imagy-{environment} \
  --services imagy-{domain} \
  --query 'services[0].{status:status,running:runningCount,desired:desiredCount,deployments:deployments[*].{status:status,running:runningCount,desired:desiredCount}}'

5. Verificar Health

bash
# Health check via ALB
curl -s https://api.{tenant}.reimaginetech.io/v1/{domain}/health/ready | jq .

# Respuesta esperada
{
  "status": "Healthy",
  "checks": [
    { "name": "npgsql", "status": "Healthy", "duration": "00:00:00.012" },
    { "name": "rabbitmq", "status": "Healthy", "duration": "00:00:00.008" },
    { "name": "redis", "status": "Healthy", "duration": "00:00:00.003" }
  ]
}

# Verificar logs
aws logs tail /ecs/imagy-{domain}-{environment} --follow --since 5m

# Verificar metricas en CloudWatch
aws cloudwatch get-metric-statistics \
  --namespace ECS/ContainerInsights \
  --metric-name CpuUtilized \
  --dimensions Name=ServiceName,Value=imagy-{domain} \
  --start-time $(date -u -v-5M +%Y-%m-%dT%H:%M:%S) \
  --end-time $(date -u +%Y-%m-%dT%H:%M:%S) \
  --period 60 \
  --statistics Average

6. Rollback

Si algo sale mal, ejecutar rollback:

bash
# Opcion 1: ECS Circuit Breaker (automatico)
# Si el deployment falla health checks, ECS revierte automaticamente
# (configurado con deploymentCircuitBreaker.rollback = true)

# Opcion 2: Rollback manual a task definition anterior
aws ecs update-service \
  --cluster imagy-{environment} \
  --service imagy-{domain} \
  --task-definition imagy-{domain}-{environment}:{previous_revision}

# Opcion 3: Rollback de migracion (si aplica)
dotnet ef database update {previous_migration_name} \
  --connection "Host={rds-endpoint};Database=imagy_{domain};..."

Criterios para Rollback

CriterioAccion
Health check falla por mas de 2 minutosRollback automatico (circuit breaker)
Error rate > 5% en los primeros 5 minutosRollback manual inmediato
Latencia p99 > 3x del baselineInvestigar, rollback si no se resuelve en 10 min
Errores en consumers (DLQ creciendo)Rollback manual
Migracion rompe servicio anteriorRollback de migracion + servicio

7. Pipeline CI/CD Completo

yaml
# .github/workflows/deploy.yml
name: Deploy

on:
  push:
    branches: [main]
  workflow_dispatch:
    inputs:
      environment:
        description: 'Target environment'
        required: true
        type: choice
        options: [develop, staging, production]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: dotnet test

  build-and-push:
    needs: test
    runs-on: ubuntu-latest
    outputs:
      image-tag: ${{ github.sha }}
    steps:
      - uses: actions/checkout@v4
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ secrets.AWS_DEPLOY_ROLE }}
          aws-region: us-east-1
      - uses: aws-actions/amazon-ecr-login@v2
      - run: |
          docker build -t $ECR_REGISTRY/imagy-{domain}:${{ github.sha }} .
          docker push $ECR_REGISTRY/imagy-{domain}:${{ github.sha }}

  deploy-develop:
    needs: build-and-push
    if: github.ref == 'refs/heads/main'
    uses: ./.github/workflows/deploy-ecs.yml
    with:
      environment: develop
      image-tag: ${{ needs.build-and-push.outputs.image-tag }}

  deploy-staging:
    needs: deploy-develop
    uses: ./.github/workflows/deploy-ecs.yml
    with:
      environment: staging
      image-tag: ${{ needs.build-and-push.outputs.image-tag }}

  deploy-production:
    needs: deploy-staging
    environment: production
    uses: ./.github/workflows/deploy-ecs.yml
    with:
      environment: production
      image-tag: ${{ needs.build-and-push.outputs.image-tag }}

8. Promocion entre Ambientes

AmbienteTriggerAprobacionBase de datosDatos
developPush a mainAutomaticaRDS dev (datos sinteticos)Seed data
stagingExito en developAutomaticaRDS staging (copia anonimizada)Datos reales anonimizados
productionExito en stagingManual (tech lead)RDS productionDatos reales

Reglas de Promocion

  • develop: Se despliega automaticamente con cada merge a main. Puede tener downtime breve.
  • staging: Se despliega automaticamente si develop pasa health checks por 5 minutos. Ambiente identico a produccion.
  • production: Requiere aprobacion manual en GitHub. Solo se despliega en ventanas de mantenimiento (lunes a jueves, 10:00-16:00 UTC-5) salvo hotfixes.

Hotfix Process

Para fixes urgentes en produccion:

bash
# 1. Crear branch desde main
git checkout -b hotfix/{description} main

# 2. Aplicar fix, commit, push
git push -u origin hotfix/{description}

# 3. Crear PR a main con label "hotfix"
gh pr create --base main --label hotfix

# 4. Tras merge, el pipeline permite deploy directo a production
# (skip staging wait time, pero requiere aprobacion)

9. Configuracion por Ambiente

Variabledevelopstagingproduction
ASPNETCORE_ENVIRONMENTDevelopmentStagingProduction
Log levelDebugInformationWarning
ECS tasks123 (min) - 10 (max)
CPU2565121024
Memory51210242048
Auto-scalingNoSi (CPU > 70%)Si (CPU > 60%)
RDS instancedb.t3.microdb.t3.smalldb.r6g.large
BackupsNoDiarioContinuo (PITR)

Checklist de Deployment

Pre-deployment

  • [ ] Todos los tests pasan en CI
  • [ ] Migraciones son backward-compatible
  • [ ] Feature flags configurados (si aplica)
  • [ ] Documentacion actualizada
  • [ ] Changelog actualizado

Durante deployment

  • [ ] Migraciones ejecutadas exitosamente
  • [ ] Servicio desplegado sin errores
  • [ ] Health checks pasando
  • [ ] Logs sin errores criticos
  • [ ] Metricas dentro de rangos normales

Post-deployment

  • [ ] Smoke tests manuales (endpoints criticos)
  • [ ] Verificar consumers procesando mensajes
  • [ ] Verificar que no hay mensajes en DLQ
  • [ ] Monitorear por 15 minutos post-deploy
  • [ ] Notificar al equipo que el deploy fue exitoso

Referencias

Reimagine Tech LLC — Documentacion Interna