Toda vez que preciso conectar dois serviços ECS Fargate, alguém no time sugere um ALB. Funciona, mas pagar ~$16/mês fixo pra resolver tráfego interno entre containers na mesma VPC é dinheiro jogado fora.
Nesse projeto montei uma alternativa usando AWS Cloud Map: dois serviços Fargate em subnets privadas, sem IP público, sem ALB. O serviço interno se registra no Cloud Map e o Nginx resolve o nome via proxy_pass — tudo dentro da VPC, tudo via Terraform.
O código está no GitHub.
leonanviana
/
terraform-ecs-fargate-service-discovery
Terraform example: ECS Fargate services communicating via AWS Cloud Map service discovery, with Nginx as reverse proxy — no ALB, no public IPs
terraform-ecs-fargate-service-discovery
Português
Exemplo em Terraform demonstrando o uso do AWS Cloud Map Service Discovery com ECS Fargate.
Todos os serviços rodam em subnets privadas, sem IP público. O serviço interno se registra em um namespace DNS privado do Cloud Map (myapp.internal), e o Nginx resolve esse nome via proxy_pass para rotear o tráfego internamente na VPC.
Arquitetura
VPC (privada)
│
▼
[Nginx - ECS Fargate] ← subnet privada, sem IP público
│
│ proxy_pass via DNS do Cloud Map
└──► internal-tool.myapp.internal → [Internal Tool - ECS Fargate]
Estrutura
stage/
├── main.tf # Providers Terraform, backend
├── vpc.tf # Módulo VPC (subnets, NAT Gateway)
├── locals.tf # Prefixo e tags comuns
├── variables.tf # Todas as variáveis de entrada
├── cloudmap.tf # Namespace Cloud Map + registros de service discovery
├── cluster.tf # Cluster ECS
├── cluster-sg.tf # Security groups e regras
├── service-nginx.tf #…Arquitetura
VPC (privada)
│
▼
[Nginx — ECS Fargate] ← subnet privada, sem IP público
│
│ proxy_pass via DNS do Cloud Map
│
└──► internal-tool.myapp.internal → [Internal Tool — ECS Fargate]
O Nginx é a única porta de entrada. O internal-tool nunca expõe IP público — o Cloud Map registra o IP da task automaticamente e o Nginx resolve esse nome sem precisar de configuração adicional a cada deploy.
Estrutura dos arquivos
stage/
├── main.tf # Providers, backend, módulo VPC
├── locals.tf # Prefixo e tags comuns
├── variables.tf # Variáveis de entrada
├── cloudmap.tf # Namespace DNS + service discovery
├── cluster.tf # Cluster ECS
├── cluster-sg.tf # Security groups
├── service-nginx.tf # ECS service do Nginx
├── task-nginx.tf # Task definition do Nginx
├── service-internal-tool.tf # ECS service da ferramenta interna
├── task-internal-tool.tf # Task definition da ferramenta interna
└── templates/
├── nginx-json.tpl
└── internal-tool-json.tpl
Prefiro um arquivo por recurso. Quando o projeto cresce, você não fica caçando aws_ecs_service enterrado no meio de um main.tf com 400 linhas.
1. Namespace DNS privado com Cloud Map
O cloudmap.tf cria a zona DNS myapp.internal, privada à VPC:
resource "aws_service_discovery_private_dns_namespace" "ecs" {
name = "myapp.internal"
vpc = module.vpc.vpc_id
description = "Private DNS namespace for myapp ECS services"
}
Nenhum tráfego sai pra internet. É um DNS interno só pra dentro da VPC.
O registro do serviço define como os IPs das tasks são publicados:
resource "aws_service_discovery_service" "internal_tool" {
name = "internal-tool"
dns_config {
namespace_id = aws_service_discovery_private_dns_namespace.ecs.id
dns_records {
ttl = 10
type = "A"
}
routing_policy = "MULTIVALUE"
}
health_check_custom_config {}
}
MULTIVALUE faz o Cloud Map retornar todos os IPs de tasks ativas quando consultado. Com mais de uma task rodando, o Nginx distribui o tráfego entre elas sem configuração extra.
2. Serviço interno com registro no Cloud Map
O service-internal-tool.tf tem dois blocos que fazem o serviço aparecer no DNS:
resource "aws_ecs_service" "internal_tool" {
name = "${local.prefix}-internal-tool"
cluster = aws_ecs_cluster.main.id
task_definition = aws_ecs_task_definition.task_internal_tool.id
launch_type = "FARGATE"
desired_count = var.app_count
network_configuration {
security_groups = [aws_security_group.internal_tool_sg.id]
subnets = [module.vpc.private_subnets[1]]
assign_public_ip = false
}
# Service Connect — sidecar proxy do ECS com métricas embutidas
service_connect_configuration {
enabled = true
namespace = aws_service_discovery_private_dns_namespace.ecs.name
}
# Registra o IP da task no Cloud Map
# Nginx resolve: internal-tool.myapp.internal → IP da task
service_registries {
registry_arn = aws_service_discovery_service.internal_tool.arn
container_name = "myapp-internal-tool"
}
lifecycle {
ignore_changes = [desired_count]
}
force_new_deployment = true
}
O bloco service_registries conecta o ECS ao Cloud Map. Quando uma task sobe, o ECS registra o IP dela no namespace DNS. Quando desce ou é substituída num deploy, o registro atualiza sozinho.
O service_connect_configuration habilita o sidecar proxy do ECS — métricas de latência e erro entre serviços sem instrumentar o código da aplicação.
3. Nginx como proxy reverso
O Nginx resolve internal-tool.myapp.internal via proxy_pass porque está na mesma VPC. Não precisa se registrar no Cloud Map.
resource "aws_ecs_service" "nginx" {
name = "${local.prefix}-nginx"
cluster = aws_ecs_cluster.main.id
task_definition = aws_ecs_task_definition.task_nginx.id
launch_type = "FARGATE"
desired_count = var.app_count
network_configuration {
security_groups = [aws_security_group.nginx_ecs_sg.id]
subnets = [module.vpc.private_subnets[0]]
assign_public_ip = false
}
force_new_deployment = true
}
assign_public_ip = false nos dois serviços. Nenhum dos dois é acessível diretamente da internet.
Pré-requisitos
- Conta AWS com permissões para ECS, ECR e VPC
- Bucket S3 criado — atualize o nome no backend em
main.tf - IAM role
ecsTaskExecutionRolecom as managed policies do ECS
Como usar
cd stage/
terraform init
terraform plan
terraform apply
Cloud Map vs ALB — quando vale a troca
ALB faz sentido quando você precisa expor um serviço publicamente, quer roteamento por path/host, ou precisa de SSL termination gerenciado. Pra tráfego interno entre containers na mesma VPC, você paga pela infraestrutura sem usar a maior parte do que ela oferece.
Cloud Map cobra por instância registrada e por consultas DNS. Em workloads moderados, a diferença de custo é relevante. Você também elimina um hop de rede — a latência entre os serviços cai.
A contrapartida: sem ALB você perde os health checks dele e o SSL termination. Pra comunicação interna isso raramente importa — o Service Connect já traz métricas e os health checks do ECS cuidam das tasks.
Se você usa ECS Fargate com múltiplos serviços internos e ainda não conhecia o Cloud Map, vale testar. O repositório tem tudo pronto pra subir num terraform apply.
Top comments (0)