Skip to main content

Welcome to a Grasp lesson

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

Lesson 76

Defining ECS Task Definitions for .NET Apps with Terraform and ECR

Recommended resources

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

Na lição anterior, você construiu a fundação da nossa infraestrutura na AWS, provisionando a rede (VPC, sub-redes) e um cluster ECS vazio usando Terraform. Agora temos o "onde" nossa aplicação vai rodar. Nesta lição, vamos definir o "o quê" e o "como".

Nosso objetivo é usar o Terraform para definir uma Definição de Tarefa ECS (ECS Task Definition) para uma aplicação .NET, referenciando uma imagem de um repositório ECR. A Task Definition é o coração da nossa aplicação no ECS; é o blueprint que descreve exatamente qual contêiner executar e como executá-lo.

Vamos detalhar os três componentes principais que constituem essa tarefa:

  1. Um repositório de contêiner para armazenar nossa imagem Docker.
  2. As permissões necessárias para o ECS acessar esse repositório.
  3. A própria Definição de Tarefa, que une tudo.

1. Pré-requisito: Um Lugar para a Imagem (ECR)

Antes que o ECS possa executar um contêiner, ele precisa saber de onde puxar a imagem. Para isso, usaremos o Amazon Elastic Container Registry (ECR), um serviço de registro de contêineres totalmente gerenciado pela AWS.

Vamos usar o Terraform para criar um repositório ECR privado para nossa aplicação .NET.

Streamlining AWS ECS Infrastructure with Terraform

Para entender como criar um repositório ECR com Terraform, vamos consultar o artigo "Streamlining AWS ECS Infrastructure with Terraform". Ele mostra uma configuração limpa e boas práticas.

Leia a seção "Step 8: ECR Repository". Foque em entender o recurso aws_ecr_repository e seus atributos como image_tag_mutability e image_scanning_configuration.

Agora, crie um novo arquivo no seu projeto chamado ecr.tf e adicione o seguinte código:

// ecr.tf

resource "aws_ecr_repository" "app_repo" {
  name                 = "my-dotnet-api-repo" // O nome do seu repositório
  image_tag_mutability = "MUTABLE" // Usaremos MUTABLE por simplicidade, mas IMMUTABLE é uma prática melhor para produção.

  image_scanning_configuration {
    scan_on_push = true
  }

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

Isso define um repositório privado onde, mais tarde, faremos o push da imagem da nossa API .NET. A configuração scan_on_push = true é uma ótima prática de segurança, instruindo a AWS a verificar automaticamente as imagens em busca de vulnerabilidades conhecidas.


2. Permissões para Execução (IAM Role)

Agora que temos um lugar para a imagem, precisamos dar permissão ao ECS para puxá-la. Quando o ECS (especificamente, o agente Fargate) inicia sua tarefa, ele precisa agir em seu nome para realizar tarefas de infraestrutura. Isso requer uma IAM Role.

Para o ECS, existem duas roles importantes, e a distinção é crucial:

  • Task Execution Role: Concede ao agente ECS/Fargate as permissões necessárias para iniciar seu contêiner. As permissões mais comuns são para puxar imagens do ECR e enviar logs para o CloudWatch.
  • Task Role: Concede à sua aplicação (o código dentro do contêiner) permissões para interagir com outros serviços da AWS enquanto ela está em execução. Por exemplo, ler de um bucket S3 ou escrever em uma tabela DynamoDB.

Nesta lição, focaremos na Task Execution Role, que é obrigatória para a Task Definition.

ECS Task Role vs Task Execution Role
Este diagrama ilustra claramente a diferença entre os dois tipos de roles. A Task Execution Role é para o ECS gerenciar o contêiner (antes de iniciar), enquanto a Task Role é para o código da sua aplicação acessar serviços da AWS (enquanto está em execução).

Vamos criar a role. Crie um novo arquivo, iam.tf, e adicione o seguinte código, que é uma prática padrão para configurar essa role.

Streamlining AWS ECS Infrastructure with Terraform

A seção "IAM Roles" do artigo "Streamlining AWS ECS Infrastructure with Terraform" fornece o código exato que precisamos para criar a Task Execution Role.

Leia a seção "Step 7: IAM Roles". Preste atenção especial aos blocos resource "aws_iam_role" "ecs_task_execution_role" e resource "aws_iam_role_policy_attachment" "ecs-task-execution-role-policy". Vamos implementar exatamente isso.

Aqui está o código para o seu arquivo iam.tf:

// iam.tf

resource "aws_iam_role" "ecs_task_execution_role" {
  name = "my-app-ecs-execution-role"

  # A política de confiança que permite que o serviço ecs-tasks assuma esta role.
  assume_role_policy = jsonencode({
    Version = "2012-10-17",
    Statement = [
      {
        Effect    = "Allow",
        Principal = {
          Service = "ecs-tasks.amazonaws.com"
        },
        Action    = "sts:AssumeRole"
      }
    ]
  })

  tags = {
    Name = "my-app-ecs-execution-role"
  }
}

# Anexa a política gerenciada pela AWS que contém todas as permissões padrão
# necessárias para a execução de tarefas do ECS.
resource "aws_iam_role_policy_attachment" "ecs_task_execution_role_policy_attachment" {
  role       = aws_iam_role.ecs_task_execution_role.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
}
  • O recurso aws_iam_role cria a role com uma assume_role_policy que especifica que o serviço ecs-tasks.amazonaws.com tem permissão para "assumir" essa role.
  • O recurso aws_iam_role_policy_attachment anexa a política gerenciada AmazonECSTaskExecutionRolePolicy, que já contém as permissões necessárias para puxar imagens do ECR e escrever logs no CloudWatch.

3. O Blueprint: A Definição da Tarefa

Com o repositório e as permissões no lugar, estamos prontos para criar a aws_ecs_task_definition. Este recurso é o núcleo do nosso setup de aplicação.

Take full control of ecs fargate using terraform

Para ter uma visão geral de como uma Task Definition é construída no Terraform, assista a esta seção do vídeo "Take full control of ecs fargate using terraform".

Assista do timestamp 12:10 a 15:27. O vídeo detalha o recurso aws_ecs_task_definition, explicando seus principais parâmetros como family, requires_compatibilities, cpu, memory e, mais importante, a estrutura de container_definitions.

Agora, adicione o seguinte recurso ao seu arquivo ecs.tf (onde você já tem o cluster definido). Usaremos um jsonencode para construir a definição do contêiner de forma legível.

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

resource "aws_ecs_task_definition" "app_task" {
  family                   = "my-dotnet-api-task"
  network_mode             = "awsvpc"
  requires_compatibilities = ["FARGATE"]
  cpu                      = "256"  // 0.25 vCPU
  memory                   = "512"  // 512 MB
  execution_role_arn       = aws_iam_role.ecs_task_execution_role.arn

  # Definição do contêiner que será executado pela tarefa.
  container_definitions = jsonencode([
    {
      name      = "my-dotnet-api-container",
      # Esta é a parte crucial: referenciamos a URL do repositório que criamos!
      image     = "${aws_ecr_repository.app_repo.repository_url}:latest",
      essential = true,
      portMappings = [
        {
          containerPort = 8080, // A porta em que sua aplicação .NET escuta.
          hostPort      = 8080
        }
      ],
      # Configuração de logs para enviar a saída do contêiner para o CloudWatch.
      logConfiguration = {
        logDriver = "awslogs",
        options = {
          "awslogs-group"         = "/ecs/my-dotnet-api",
          "awslogs-region"        = var.aws_region,
          "awslogs-stream-prefix" = "ecs"
        }
      }
    }
  ])

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

# Cria o grupo de logs para o nosso contêiner.
resource "aws_cloudwatch_log_group" "app_log_group" {
  name = "/ecs/my-dotnet-api"

  tags = {
    Name = "my-dotnet-api-log-group"
  }
}

Vamos analisar as partes mais importantes:

  • requires_compatibilities = ["FARGATE"]: Especifica que esta tarefa é para ser executada no Fargate.
  • cpu e memory: Define os recursos alocados para a tarefa.
  • execution_role_arn: Referencia a IAM role que criamos na etapa anterior.
  • container_definitions: Este é um JSON que descreve o(s) contêiner(es) a serem executados.
    • image: Usamos uma interpolação ${...} para construir dinamicamente a URL da imagem a partir do recurso aws_ecr_repository que definimos. Isso demonstra o poder do Terraform de conectar recursos.
    • portMappings: Mapeia a porta do contêiner. O ASP.NET Core por padrão costumava rodar na porta 80, mas as imagens de contêiner mais recentes usam a porta 8080. Vamos usar 8080.
    • logConfiguration: Instruímos o ECS a usar o driver awslogs para enviar todos os logs do contêiner para um grupo de logs específico no CloudWatch, que também criamos com o recurso aws_cloudwatch_log_group.
Teste seu entendimento!

No bloco container_definitions, o campo image está definido como ${aws_ecr_repository.app_repo.repository_url}:latest. Se você aplicasse essa configuração do Terraform agora, a tarefa conseguiria iniciar com sucesso? Por quê?

Show answer

Não, a tarefa falharia ao iniciar. Embora o Terraform crie com sucesso o repositório ECR e a Task Definition que aponta para ele, o repositório em si estaria vazio. Nós ainda não construímos a imagem Docker da nossa API .NET nem fizemos o docker push dela para o ECR com a tag latest. O agente ECS tentaria puxar a imagem, não a encontraria e a tarefa falharia.


4. Provisionando os Recursos

Seu projeto agora deve ter os seguintes arquivos novos ou modificados: ecr.tf, iam.tf, e ecs.tf. É hora de aplicar as mudanças.

  1. Planejar: Abra seu terminal no diretório do projeto e execute terraform plan. A saída deve mostrar os novos recursos a serem criados: aws_ecr_repository, aws_iam_role, aws_iam_role_policy_attachment, aws_ecs_task_definition e aws_cloudwatch_log_group.

  2. Aplicar: Se o plano parecer correto, execute o comando para provisionar a infraestrutura:

    terraform apply
    

    Confirme com yes quando solicitado. Após alguns instantes, seus recursos estarão criados na AWS. Você pode verificar no console da AWS nos serviços ECR, IAM e ECS. Você verá seu novo repositório, sua nova role e, o mais importante, uma nova revisão da sua Definição de Tarefa.


Conclusão

Parabéns! Você acaba de criar o blueprint completo para sua aplicação .NET no ECS. Embora a aplicação ainda não esteja em execução, você definiu todos os metadados e configurações necessários para que isso aconteça.

Principais Pontos:

  • Uma Task Definition é o blueprint para uma tarefa ECS, especificando imagem, recursos (CPU/memória), portas e logging.
  • Ela depende de um repositório de contêineres, como o ECR (aws_ecr_repository), para armazenar a imagem.
  • Ela requer uma IAM Task Execution Role (aws_iam_role) para que o ECS tenha permissão para puxar a imagem e escrever logs.
  • A definição do contêiner (container_definitions) é onde a mágica acontece, conectando a imagem do ECR, mapeando portas e configurando logs do CloudWatch.

Próxima Lição:

Temos o "onde" (Cluster) e o "o quê" (Task Definition). Falta a peça final: o "como executar". Na próxima lição, vamos definir um Serviço ECS (aws_ecs_service). Este recurso é responsável por pegar nossa Task Definition, executar o número desejado de instâncias dela em nosso cluster e garantir que elas permaneçam em execução, substituindo-as se falharem. Também vamos conectar este serviço a um Application Load Balancer para expor nossa API para o mundo.