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/liveBuild 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:
- Release N: dejar de usar la columna (codigo no la lee ni escribe).
- Release N+1: eliminar la columna en migracion.
- Agregar columnas como nullable o con default value.
- Indices nuevos se crean con
CONCURRENTLYpara 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-roleActualizar 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 Average6. 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
| Criterio | Accion |
|---|---|
| Health check falla por mas de 2 minutos | Rollback automatico (circuit breaker) |
| Error rate > 5% en los primeros 5 minutos | Rollback manual inmediato |
| Latencia p99 > 3x del baseline | Investigar, rollback si no se resuelve en 10 min |
| Errores en consumers (DLQ creciendo) | Rollback manual |
| Migracion rompe servicio anterior | Rollback 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
| Ambiente | Trigger | Aprobacion | Base de datos | Datos |
|---|---|---|---|---|
| develop | Push a main | Automatica | RDS dev (datos sinteticos) | Seed data |
| staging | Exito en develop | Automatica | RDS staging (copia anonimizada) | Datos reales anonimizados |
| production | Exito en staging | Manual (tech lead) | RDS production | Datos 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
| Variable | develop | staging | production |
|---|---|---|---|
ASPNETCORE_ENVIRONMENT | Development | Staging | Production |
| Log level | Debug | Information | Warning |
| ECS tasks | 1 | 2 | 3 (min) - 10 (max) |
| CPU | 256 | 512 | 1024 |
| Memory | 512 | 1024 | 2048 |
| Auto-scaling | No | Si (CPU > 70%) | Si (CPU > 60%) |
| RDS instance | db.t3.micro | db.t3.small | db.r6g.large |
| Backups | No | Diario | Continuo (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
- Infraestructura — Arquitectura AWS, ECS, networking
- Seguridad — Secrets, IAM roles, encryption
- Guia: Nuevo Servicio — Crear un servicio desde cero
- Guia: Nuevo Evento — Agregar eventos al servicio