Esta demo valida a infraestrutura AWS ECS Fargate com Auto Scaling, Multi-AZ e Observabilidade. Assumimos que você já tem o código-fonte, vai provisionar a infraestrutura do zero e está logado no AWS CLI.
O que vamos fazer:
- Provisionar infraestrutura completa via CloudFormation
- Validar distribuição Multi-AZ das tasks
- Realizar teste de carga com k6 para acionar o scaling
- Observar métricas e logs no CloudWatch
Ambiente:
- Região: us-east-1
- Cluster ECS: filepack-cluster
- Service ECS: filepack-service
O provisionamento utiliza CloudFormation para criar toda a infraestrutura de forma declarativa e reproduzível. São duas stacks: uma para o repositório de imagens (ECR) e outra para o cluster ECS com todos os componentes de rede, balanceamento e auto scaling.
O Amazon ECR (Elastic Container Registry) é onde armazenamos as imagens Docker da aplicação. Precisa ser criado primeiro porque o ECS depende da imagem para iniciar as tasks.
aws cloudformation create-stack \
--stack-name filepack-ecr \
--template-body file://infra/1-ecr.yml
aws cloudformation wait stack-create-complete \
--stack-name filepack-ecrExportar variáveis:
OUTPUTS_ECR=$(aws cloudformation describe-stacks --stack-name filepack-ecr \
--query "Stacks[0].Outputs[*].[OutputKey,OutputValue]" --output text)
export ACCOUNT_ID=$(echo "$OUTPUTS_ECR" | awk '$1 == "AccountId" {print $2}')
export REPO_URI=$(echo "$OUTPUTS_ECR" | awk '$1 == "RepositoryUri" {print $2}')
echo "Repository: $REPO_URI"Saída:
Repository: 27307****022.dkr.ecr.us-east-1.amazonaws.com/filepack-api
O output mostra o URI completo do repositório onde enviaremos a imagem Docker.
Com o repositório criado, fazemos o build da aplicação e enviamos para o ECR. O login via aws ecr get-login-password gera um token temporário para autenticação.
# Login no 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
# Build e push
./mvnw clean install
docker build -t filepack-api .
docker tag filepack-api:latest ${REPO_URI}:latest
docker push ${REPO_URI}:latestEsta stack cria o cluster ECS, VPC, subnets Multi-AZ, Load Balancer, Security Groups, IAM Roles e as políticas de Auto Scaling. A flag CAPABILITY_NAMED_IAM é obrigatória porque estamos criando roles com nomes específicos.
📁 infra/2-ecs.yml — Principais recursos:
# Multi-AZ: duas subnets em zonas diferentes
PublicSubnet: # us-east-1a
PublicSubnet2: # us-east-1b
# Auto Scaling
ScalableTarget: # Min: 2, Max: 6 tasks
ScaleOutPolicy: # Step Scaling +1/+2 tasks
ScaleInPolicy: # Step Scaling -1 task
HighCpuAlarm: # CPU > 25% → scale-out
LowCpuAlarm: # CPU < 10% → scale-inaws cloudformation create-stack \
--stack-name filepack-ecs \
--template-body file://infra/2-ecs.yml \
--capabilities CAPABILITY_NAMED_IAM
aws cloudformation wait stack-create-complete \
--stack-name filepack-ecsObter URL do Load Balancer:
export ALB_URL=$(aws cloudformation describe-stacks --stack-name filepack-ecs \
--query "Stacks[0].Outputs[?OutputKey=='LoadBalancerUrl'].OutputValue" --output text)
echo "ALB URL: $ALB_URL"Saída:
ALB URL: http://filepack-alb-*********.us-east-1.elb.amazonaws.com
Este é o endpoint público da aplicação. O ALB distribui requisições entre as tasks ECS e faz health checks automáticos.
Antes de executar testes de carga, validamos que todos os componentes estão funcionando: health check da aplicação, API respondendo, tasks distribuídas entre AZs e políticas de scaling configuradas.
O Spring Actuator expõe o endpoint /actuator/health que o ALB usa para verificar se as tasks estão saudáveis.
curl -s $ALB_URL/actuator/healthSaída:
{"groups":["liveness","readiness"],"status":"UP"}Status UP confirma que a aplicação está pronta para receber requisições. O ALB só roteia tráfego para tasks com health check OK.
Teste funcional da API enviando um arquivo para compactação com senha.
📁 Arquivos de teste: data/
curl -X POST $ALB_URL/api/filepack \
-F "files=@data/infrastructure_config.xml" \
-F "password=teste123" \
--output validation_pack.zipO arquivo validation_pack.zip será baixado. O conteúdo só pode ser extraído com a senha informada.
Multi-AZ garante alta disponibilidade: se uma zona de disponibilidade falhar, a aplicação continua operando na outra. O ECS distribui tasks automaticamente entre as subnets configuradas.
aws ecs describe-services \
--cluster filepack-cluster \
--services filepack-service \
--query "services[0].{Desired:desiredCount,Running:runningCount,Pending:pendingCount}" \
--output tableSaída:
+---------+-----------+-----------+
| Desired | Pending | Running |
+---------+-----------+-----------+
| 2 | 0 | 2 |
+---------+-----------+-----------+
Desired=2 e Running=2 indica que o service está estável com a quantidade desejada de tasks rodando.
Verificar distribuição entre AZs:
TASKS=$(aws ecs list-tasks \
--cluster filepack-cluster \
--service-name filepack-service \
--desired-status RUNNING \
--query 'taskArns' \
--output text)
aws ecs describe-tasks \
--cluster filepack-cluster \
--tasks $TASKS \
--query 'tasks[].{AZ:availabilityZone,Status:lastStatus}' \
--output tableSaída:
+-------------+-----------+
| AZ | Status |
+-------------+-----------+
| us-east-1a | RUNNING |
| us-east-1b | RUNNING |
+-------------+-----------+
Uma task em cada AZ. Se us-east-1a ficar indisponível, us-east-1b continua atendendo requisições.
O Auto Scaling ajusta automaticamente a quantidade de tasks baseado em métricas. Usamos Step Scaling que permite respostas diferentes para cada nível de carga.
Comparação de estratégias de Auto Scaling:
| Estratégia | Como Funciona | Quando Usar |
|---|---|---|
| Target Tracking | Mantém métrica em valor-alvo (ex: 70% CPU) | Cargas estáveis, configuração simples |
| Step Scaling | Ajustes diferentes por faixa de métrica | Cargas voláteis, controle granular |
| Scheduled | Escala em horários específicos (cron) | Padrões temporais conhecidos |
Nossa escolha: Step Scaling com threshold de 25% CPU permite demonstrar o scaling rapidamente. Em produção, Target Tracking com 70% seria mais comum.
📁 Configuração em infra/2-ecs.yml:
ScaleOutPolicy:
StepScalingPolicyConfiguration:
StepAdjustments:
- MetricIntervalLowerBound: 0 # CPU 25-35% → +1 task
MetricIntervalUpperBound: 10
ScalingAdjustment: 1
- MetricIntervalLowerBound: 10 # CPU ≥35% → +2 tasks
ScalingAdjustment: 2aws application-autoscaling describe-scaling-policies \
--service-namespace ecs \
--resource-id service/filepack-cluster/filepack-service \
--query "ScalingPolicies[].{PolicyName:PolicyName,PolicyType:PolicyType}" \
--output tableSaída:
+--------------------------+---------------+
| PolicyName | PolicyType |
+--------------------------+---------------+
| filepack-step-scale-in | StepScaling |
+--------------------------+---------------+
| filepack-step-scale-out | StepScaling |
+--------------------------+---------------+
Duas políticas configuradas: scale-out para aumentar capacidade sob carga e scale-in para reduzir quando ociosa.
Os CloudWatch Alarms monitoram métricas e disparam as políticas de scaling quando os thresholds são ultrapassados.
📁 Configuração em infra/2-ecs.yml:
HighCpuAlarm:
Threshold: 25
EvaluationPeriods: 1 # Reage imediatamente
AlarmActions: [!Ref ScaleOutPolicy]
LowCpuAlarm:
Threshold: 10
EvaluationPeriods: 2 # Aguarda 2 min (evita flapping)
AlarmActions: [!Ref ScaleInPolicy]aws cloudwatch describe-alarms \
--alarm-name-prefix filepack \
--query "MetricAlarms[*].{Name:AlarmName,State:StateValue,Threshold:Threshold}" \
--output tableSaída:
+------------------------------+-------+-------------+
| Name | State | Threshold |
+------------------------------+-------+-------------+
| filepack-high-cpu-scale-out | OK | 25.0 |
| filepack-low-cpu-scale-in | OK | 10.0 |
+------------------------------+-------+-------------+
Ambos em estado OK indicam que a CPU está entre 10% e 25%. Quando CPU > 25%, o alarme scale-out entra em ALARM e adiciona tasks.
O k6 é uma ferramenta moderna de load testing que usa JavaScript para definir cenários. Vamos gerar carga suficiente para ultrapassar 25% de CPU e acionar o auto scaling.
GitBash
MSYS_NO_PATHCONV=1 docker run --rm \
-v "$PWD:/workspace" \
-w /workspace \
-e "BASE_URL=$ALB_URL" \
grafana/k6:latest run k6/load-test.jsLinux e MAC
docker run --rm \
-v ${PWD}:/workspace \
-w /workspace \
-e BASE_URL=$ALB_URL \
grafana/k6:latest run k6/load-test.jsPerfil de carga (k6/load-test.js):
- 20s: ramp-up até 24 VUs
- 70s: sustenta 36 VUs
- 20s: pico de 48 VUs (força scale-out)
- 10s: ramp-down
checks_succeeded...: 100.00% 318 out of 318
✓ status is 200
✓ response has content
✓ response is zip
http_req_duration..: avg=4.15s min=2.4s med=3.78s max=18.96s p(95)=6.79s
http_req_failed....: 0.00% 0 out of 106
http_reqs..........: 106 0.56/s
data_received......: 55 MB 289 kB/s
data_sent..........: 553 MB 2.9 MB/s
100% de sucesso com 106 requisições processadas. O tempo médio de 4.15s reflete a operação de compactação + criptografia AES que é CPU-intensive. Durante o teste, o alarme de CPU dispara e novas tasks são provisionadas automaticamente.
Após o teste de carga, verificamos o histórico de scaling para confirmar que o sistema reagiu corretamente à variação de demanda.
O histórico mostra cada ação de scaling com timestamp, status e causa (qual alarme disparou).
aws application-autoscaling describe-scaling-activities \
--service-namespace ecs \
--resource-id service/filepack-cluster/filepack-service \
--max-results 5 \
--query "ScalingActivities[*].{Time:StartTime,Status:StatusCode,Cause:Cause}" \
--output tableSaída típica:
+-----------------------------------------------------------------------------------+------------+---------------------------+
| Cause | Status | Time |
+-----------------------------------------------------------------------------------+------------+---------------------------+
| monitor alarm filepack-high-cpu-scale-out in state ALARM triggered policy... | Successful| 2026-04-07T11:20:15Z |
| monitor alarm filepack-low-cpu-scale-in in state ALARM triggered policy... | Successful| 2026-04-07T11:18:44Z |
+-----------------------------------------------------------------------------------+------------+---------------------------+
O histórico confirma que os alarmes disparam corretamente as políticas. scale-out durante carga alta, scale-in após estabilização.
Durante o pico de carga, verificamos quantas tasks o auto scaling provisionou.
aws ecs describe-services \
--cluster filepack-cluster \
--services filepack-service \
--query "services[0].{Desired:desiredCount,Running:runningCount}" \
--output tableSaída durante carga:
+---------+-----------+
| Desired | Running |
+---------+-----------+
| 5 | 5 |
+---------+-----------+
O service escalou de 2 para 5 tasks. O Step Scaling adicionou +1 task quando CPU passou de 25%, e +2 tasks quando ultrapassou 35%. Após a carga diminuir, o scale-in reduzirá gradualmente de volta ao mínimo de 2.
A observabilidade permite entender o comportamento da aplicação em produção através de métricas, logs e traces. O ECS integra nativamente com CloudWatch para centralizar essas informações.
Dashboard automático com métricas de CPU, memória, rede e disco agregadas por cluster, service e task.
Console: CloudWatch > Container Insights > Resources > ECS Clusters > filepack-cluster
Logs no formato key=value facilitam análise e correlação. O requestId permite rastrear uma requisição através de todos os eventos.
📁 Modificações para logs estruturados:
// Controller - início e fim da requisição
log.info("REQUEST_START requestId={} fileCount={} totalSizeBytes={}", requestId, ...);
log.info("REQUEST_END requestId={} zipSizeBytes={}", requestId, ...);
// Service - processo de compactação
log.info("PACK_START requestId={} fileNames={}", requestId, ...);
log.info("PACK_END requestId={} compressionTimeMs={} outputSizeBytes={}", requestId, ...);Log Group: /ecs/filepack-api
📁 Configuração do log driver em infra/2-ecs.yml:
LogConfiguration:
LogDriver: awslogs
Options:
awslogs-group: /ecs/filepack-apiExemplo de log:
REQUEST_START requestId=a3f7b2c1 fileCount=4 totalSizeBytes=1048576
PACK_START requestId=a3f7b2c1 fileNames=[file1.json, file2.json, ...]
PACK_END requestId=a3f7b2c1 compressionTimeMs=342 outputSizeBytes=524288
REQUEST_END requestId=a3f7b2c1 zipSizeBytes=524288
Cada requisição gera 4 eventos rastreáveis pelo mesmo requestId, permitindo debug end-to-end.
Linguagem SQL-like para analisar logs em escala. Útil para debugging, análise de performance e troubleshooting.
Rastrear requisições:
fields @timestamp, @message
| filter @message like /REQUEST_START|REQUEST_END/
| parse @message "requestId=* " as requestId
| sort @timestamp desc
| limit 50
Análise de performance:
fields @timestamp, @message
| filter @message like /PACK_END/
| parse @message "compressionTimeMs=* " as timeMs
| stats avg(timeMs) as avgMs, max(timeMs) as maxMs, count() as total
Remover todos os recursos para evitar custos. A ordem é importante: primeiro o ECS (que depende do ECR), depois forçar remoção das imagens, e por fim deletar o repositório.
# 1. Deletar stack ECS
aws cloudformation delete-stack --stack-name filepack-ecs
aws cloudformation wait stack-delete-complete --stack-name filepack-ecs
# 2. Deletar repositório ECR (força remoção de imagens)
aws ecr delete-repository --repository-name filepack-api --force
# 3. Deletar stack ECR
aws cloudformation delete-stack --stack-name filepack-ecr
aws cloudformation wait stack-delete-complete --stack-name filepack-ecr
# 4. Deletar log group (opcional)
aws logs delete-log-group --log-group-name /ecs/filepack-api 2>/dev/null || trueVerificar cleanup:
aws cloudformation list-stacks \
--stack-status-filter CREATE_COMPLETE UPDATE_COMPLETE \
--query "StackSummaries[?contains(StackName, 'filepack')].StackName" \
--output textSaída esperada: (vazio - nenhuma stack restante)
| Métrica | Threshold | Ação |
|---|---|---|
| CPU > 25% | +1 task (25-35%), +2 tasks (≥35%) | Scale-out rápido |
| CPU < 10% | -1 task (após 2 min) | Scale-in conservador |
| Recurso | Configuração |
|---|---|
| Tasks | Min: 2, Max: 6 |
| CPU/Memória | 256 CPU / 512 MB |
| Multi-AZ | us-east-1a, us-east-1b |
| HealthCheck | wget -q --spider http://localhost:8080/actuator/health |