DEV Community

Cover image for ECS Fargate com Service Discovery usando AWS Cloud Map e Terraform
Leonan Viana
Leonan Viana

Posted on

ECS Fargate com Service Discovery usando AWS Cloud Map e Terraform

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.

GitHub logo 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 | English


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]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
}
Enter fullscreen mode Exit fullscreen mode

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 {}
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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 ecsTaskExecutionRole com as managed policies do ECS

Como usar

cd stage/
terraform init
terraform plan
terraform apply
Enter fullscreen mode Exit fullscreen mode

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)