Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 77

Terraform for ECS Services on Fargate

Recommended resources

Olá! Bem-vindo à nossa décima quinta lição.

Na aula anterior, você montou o "blueprint" da nossa aplicação .NET no ECS: criamos o repositório ECR para a imagem, definimos as permissões com uma IAM Role e, o mais importante, criamos a aws_ecs_task_definition. Agora temos o "onde" (o cluster ECS) e o "o quê" (a definição da tarefa), mas a nossa aplicação ainda não está em execução.

Nesta lição, vamos dar vida à nossa infraestrutura. Nosso objetivo é usar o Terraform para definir um Serviço ECS (aws_ecs_service) para executar e manter o número desejado de tarefas no Fargate. O serviço é o maestro da orquestra: ele pega a partitura (a Task Definition) e garante que os músicos (as tarefas) estejam tocando continuamente.

1. O Papel do Serviço ECS

Um serviço ECS é um recurso de longa duração que gerencia as tarefas dentro de um cluster. Suas principais responsabilidades são:

  • Manter o estado desejado: Ele garante que um número especificado de instâncias de uma Task Definition (as "tarefas") esteja sempre em execução. Se uma tarefa falhar, o serviço a substitui automaticamente.
  • Integração com Load Balancer: Ele pode registrar as tarefas em um Application Load Balancer (ALB) para distribuir o tráfego de entrada entre elas.
  • Estratégias de deploy: Ele gerencia como as novas versões da sua aplicação são implantadas (por exemplo, rolling updates), minimizando o tempo de inatividade.

A imagem abaixo ilustra perfeitamente onde o serviço se encaixa. O tráfego do usuário chega a um ALB, que o encaminha para o ECS Service. O serviço, por sua vez, distribui esse tráfego entre as tarefas em execução que ele gerencia.

AWS ECS Fargate Application Deployment Architecture
Este diagrama mostra a arquitetura de uma aplicação em ECS. O `ECS Service` (`sun-api service` na imagem) é o componente que gerencia as `Tasks` (instâncias da sua aplicação) dentro do `ECS Cluster` e as conecta ao `Application Load Balancer` através de um `Target Group`.

Para que o serviço funcione e seja acessível, precisamos de duas coisas: o próprio serviço e um meio de expô-lo à internet. Vamos começar configurando o ponto de entrada: o Application Load Balancer.


2. Expondo o Serviço: Application Load Balancer (ALB)

Um ALB distribui o tráfego de entrada para vários destinos, como tarefas ECS, em diferentes Zonas de Disponibilidade, aumentando a tolerância a falhas da sua aplicação.

Para configurar um ALB com Terraform, precisamos de três recursos principais:

  1. aws_lb: O próprio load balancer.
  2. aws_lb_target_group: Um grupo de destino para onde o tráfego será roteado. Nossas tarefas ECS serão registradas aqui.
  3. aws_lb_listener: Um "ouvinte" no load balancer que verifica as solicitações de conexão (por exemplo, na porta 80 para HTTP) e as encaminha para um Target Group com base em regras.

Vamos criar um novo arquivo, alb.tf, para definir esses recursos.

// alb.tf

// 1. O próprio Application Load Balancer
resource "aws_lb" "app_lb" {
  name               = "my-dotnet-api-lb"
  internal           = false
  load_balancer_type = "application"
  security_groups    = [aws_security_group.lb_sg.id]
  subnets            = module.vpc.public_subnets // Referencia as sub-redes públicas do módulo VPC

  tags = {
    Name = "my-dotnet-api-lb"
  }
}

// 2. O Target Group para as tarefas ECS
resource "aws_lb_target_group" "app_tg" {
  name        = "my-dotnet-api-tg"
  port        = 80
  protocol    = "HTTP"
  vpc_id      = module.vpc.vpc_id // Referencia o VPC do módulo
  target_type = "ip"

  health_check {
    path                = "/healthz" // Endpoint de health check da sua API .NET
    protocol            = "HTTP"
    matcher             = "200"
    interval            = 30
    timeout             = 5
    healthy_threshold   = 2
    unhealthy_threshold = 2
  }

  tags = {
    Name = "my-dotnet-api-tg"
  }
}

// 3. O Listener que conecta o LB ao Target Group
resource "aws_lb_listener" "http" {
  load_balancer_arn = aws_lb.app_lb.arn
  port              = "80"
  protocol          = "HTTP"

  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.app_tg.arn
  }
}

Observação: O código acima assume que você tem um módulo VPC chamado vpc (como fizemos em lições anteriores) e um endpoint de health check /healthz na sua API. A maioria dos templates de API .NET modernos inclui um endpoint de health check por padrão, muitas vezes em /health. Ajuste o path conforme necessário.

Também precisamos de grupos de segurança (security groups) para controlar o tráfego. Adicione um novo arquivo sg.tf:

// sg.tf

// Security Group para o Load Balancer
resource "aws_security_group" "lb_sg" {
  name        = "lb-sg"
  description = "Allow HTTP traffic to the load balancer"
  vpc_id      = module.vpc.vpc_id

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "lb-sg"
  }
}

// Security Group para as tarefas ECS
resource "aws_security_group" "ecs_tasks_sg" {
  name        = "ecs-tasks-sg"
  description = "Allow traffic from the LB to the ECS tasks"
  vpc_id      = module.vpc.vpc_id

  // Permite tráfego de entrada vindo do nosso Load Balancer
  ingress {
    from_port       = 8080 // A porta da nossa aplicação
    to_port         = 8080
    protocol        = "tcp"
    security_groups = [aws_security_group.lb_sg.id] // Apenas o LB pode acessar
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "ecs-tasks-sg"
  }
}

3. Definindo o aws_ecs_service

Agora que temos o roteamento de tráfego configurado, podemos finalmente criar o recurso aws_ecs_service. Ele vai unir o cluster, a definição da tarefa e o load balancer.

Take full control of ecs fargate using terraform

Para uma explicação visual e detalhada sobre como criar um serviço ECS com Terraform, assista a esta seção do vídeo "Take full control of ecs fargate using terraform".

Assista do timestamp 06:03 a 11:17. O vídeo aborda a criação do recurso aws_ecs_service, destacando parâmetros importantes como launch_type, task_definition, desired_count e a integração com o load_balancer.

Vamos agora adicionar o recurso do serviço ao nosso arquivo ecs.tf.

Mastering AWS ECS Configuration with Terraform

O artigo "Mastering AWS ECS Configuration with Terraform" também fornece um excelente exemplo de como definir o serviço.

Leia a seção "Creating an ECS Service". Observe como o recurso aws_ecs_service conecta o cluster, task_definition, load_balancer e network_configuration.

Adicione o seguinte bloco de código ao seu arquivo ecs.tf, após a task_definition:

// ecs.tf (adicionar após o recurso aws_ecs_task_definition)

resource "aws_ecs_service" "app_service" {
  name            = "my-dotnet-api-service"
  cluster         = aws_ecs_cluster.app_cluster.id
  task_definition = aws_ecs_task_definition.app_task.arn
  desired_count   = 2 // Queremos 2 instâncias da nossa API rodando
  launch_type     = "FARGATE"

  network_configuration {
    subnets         = module.vpc.private_subnets // Colocamos as tarefas em sub-redes privadas por segurança
    security_groups = [aws_security_group.ecs_tasks_sg.id]
  }

  load_balancer {
    target_group_arn = aws_lb_target_group.app_tg.arn
    container_name   = "my-dotnet-api-container" // Nome do contêiner da nossa Task Definition
    container_port   = 8080 // Porta do contêiner
  }

  // Ajuda a evitar problemas quando o Terraform tenta substituir o serviço
  // enquanto o ALB ainda está drenando as conexões.
  depends_on = [aws_lb_listener.http]

  tags = {
    Name = "my-dotnet-api-service"
  }
}

Vamos analisar as partes mais importantes:

  • cluster e task_definition: Referenciam os recursos que criamos anteriormente, conectando o serviço ao cluster e ao blueprint da aplicação.
  • desired_count = 2: Instruímos o ECS a executar e manter duas instâncias (tarefas) da nossa API.
  • launch_type = "FARGATE": Especificamos que queremos usar o modo de execução serverless.
  • network_configuration: Definimos que nossas tarefas serão executadas nas sub-redes privadas (private_subnets) e usarão o grupo de segurança ecs_tasks_sg que criamos, que só permite tráfego vindo do nosso load balancer.
  • load_balancer: É aqui que a mágica acontece. Ligamos o serviço ao Target Group do nosso ALB, especificando o nome e a porta do contêiner que deve receber o tráfego.
Teste seu entendimento!

No recurso aws_ecs_service, por que normalmente colocamos as tarefas em sub-redes privadas (private_subnets) em vez de públicas? Qual é o papel do NAT Gateway nesse cenário?

Show answer

Colocamos as tarefas em sub-redes privadas por segurança, seguindo o princípio do menor privilégio. Elas não precisam de um endereço IP público, pois o único tráfego de entrada que devem receber é o do Application Load Balancer, que atua como um ponto de entrada controlado.

No entanto, as tarefas ainda podem precisar de acesso à internet para, por exemplo, puxar a imagem do contêiner do ECR, chamar APIs de terceiros ou baixar pacotes. O NAT Gateway, que reside em uma sub-rede pública, permite que os recursos nas sub-redes privadas iniciem conexões de saída para a internet, mas impede que a internet inicie conexões de entrada com eles. O tráfego de saída das tarefas é roteado para o NAT Gateway, que então o envia para a internet.


4. Provisionando e Verificando

Agora que todos os nossos recursos estão definidos, é hora de aplicar as mudanças.

  1. Inicializar: Como adicionamos novos security_groups que dependem do módulo VPC, é uma boa prática garantir que tudo esteja atualizado.

    terraform init -upgrade
    
  2. Planejar: Execute o terraform plan para ver o que será criado. Você deve ver os novos recursos: aws_lb, aws_lb_target_group, aws_lb_listener, os dois aws_security_group e, finalmente, o aws_ecs_service.

  3. Aplicar: Se o plano estiver correto, aplique-o.

    terraform apply
    

    Confirme com yes. O Terraform começará a provisionar a infraestrutura. Isso pode levar alguns minutos.

  4. Verificar: Após a conclusão, vá para o console da AWS:

    • EC2 > Load Balancers: Encontre seu novo ALB. Copie seu DNS name.
    • EC2 > Target Groups: Selecione seu Target Group. Na aba "Targets", você deverá ver duas novas entradas (os IPs das suas tarefas). Elas podem estar no estado initial ou unhealthy por um momento.
    • ECS > Clusters: Clique no seu cluster, vá para a aba "Services" e selecione seu serviço. Você deve ver que o Desired status é 2 e, após um tempo, o Running também deve ser 2.

Importante: Se na lição anterior você ainda não fez o push da sua imagem Docker .NET para o repositório ECR, as tarefas falharão ao iniciar (pending -> stopped) porque o ECS não conseguirá encontrar a imagem. Além disso, o health check do Target Group falhará, e as tarefas serão marcadas como unhealthy. Isso é esperado! No momento, estamos focados em garantir que a infraestrutura seja criada corretamente.


Conclusão

Parabéns! Você acaba de implantar uma aplicação em contêiner funcional e gerenciada na AWS usando Terraform. Você não apenas definiu o que executar, mas também como executá-lo, mantê-lo saudável e expô-lo ao mundo de forma segura.

Principais Pontos:

  • O aws_ecs_service é o recurso que gerencia o ciclo de vida das tarefas, garantindo que o número desejado (desired_count) esteja sempre em execução.
  • A exposição de um serviço à internet é feita através de um Application Load Balancer (ALB), configurado com um Listener e um Target Group.
  • A segurança é aprimorada colocando tarefas em sub-redes privadas e usando Security Groups para permitir tráfego apenas do ALB.
  • O bloco load_balancer dentro do aws_ecs_service é a cola que conecta o serviço ao Target Group, permitindo que o tráfego seja roteado para as tarefas.

Próxima Lição:

Nossa configuração está funcionando, mas está cheia de valores fixos ("hardcoded"). Na próxima lição, vamos refatorar nosso código para gerenciar a configuração com variáveis do Terraform e expor detalhes importantes dos recursos usando outputs. Isso tornará nosso código mais reutilizável, flexível e fácil de gerenciar.