Post

Terraform Além do Básico: Utilizando count, for_each e Expressões Condicionais

Terraform Além do Básico: Utilizando count, for_each e Expressões Condicionais

Introdução

A declaração estática de recursos é o ponto de partida no Terraform. Um bloco resource define uma VM, outro define um bucket, e assim por diante. Embora funcional para setups simples, essa abordagem rapidamente se torna impraticável em cenários complexos, levando à repetição de código e dificuldade de manutenção.

Existem métodos mais eficientes para aplicarmos lógica e criar configurações dinâmicas e escaláveis. A transição de configurações manuais e repetitivas para a automação inteligente é viabilizada a princípio por três mecanismos: os meta-argumentos count e for_each, e o uso de expressões condicionais.

Este artigo demonstra como utilizar essas ferramentas para gerenciar múltiplos recursos, implementar lógica condicional e escrever código IaC eficiente e reutilizável, seguindo as melhores práticas.

1. O Meta-Argumento count

O count permite criar múltiplas instâncias de um recurso a partir de um único bloco de código. Ele aceita um valor numérico e gera essa quantidade de cópias idênticas do recurso.

Funcionamento Básico

Considere a necessidade de provisionar três sub-redes em uma VPC. Em vez de duplicar o bloco aws_subnet três vezes, podemos empregar count:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
variable "subnet_count" {
  description = "Número de sub-redes a criar"
  type        = number
  default     = 3
}

resource "aws_subnet" "example" {
  count = var.subnet_count

  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.${count.index + 1}.0/24"
  availability_zone = "us-east-1${element(["a", "b", "c"], count.index)}"

  tags = {
    Name = "subnet-${count.index}"
  }
}

Neste exemplo, o Terraform provisionará três instâncias de aws_subnet. O objeto count.index fornece o índice da iteração atual (0, 1 e 2), possibilitando a diferenciação dinâmica de atributos como cidr_block e availability_zone.

Limitações do count

O count organiza os recursos em uma lista ordenada. Se um recurso intermediário nesta lista for removido ou a ordem alterada (por exemplo, ao reduzir o count ou modificar elementos que influenciam o índice), o Terraform pode reindexar e, consequentemente, destruir e recriar recursos existentes, alterando seus IDs. Este comportamento de “deslocamento” (shifting) pode ser destrutivo e é, geralmente, indesejável em ambientes de produção.

Uso Recomendado: O count é adequado para criar um número arbitrário de recursos idênticos onde a identidade individual de cada instância não é crítica, ou em conjunto com expressões condicionais para criar/não criar um recurso (discutido adiante).

2. O Meta-Argumento for_each

O for_each foi introduzido para mitigar as limitações do count. Em vez de um valor numérico, ele aceita um mapa (map) ou um conjunto de strings (set of strings).

Funcionamento e Vantagens

O for_each itera sobre os itens do mapa ou conjunto fornecido, criando uma instância de recurso para cada um. A principal diferença é que ele utiliza a chave do mapa (ou o valor do conjunto) como um identificador único e estável para cada recurso, em vez de um índice numérico.

Vamos voltar ao exemplo anterior das sub-redes, agora com for_each para gerenciar sub-redes com identidades lógicas específicas:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
variable "subnets" {
  description = "Um mapa de configurações de sub-redes a serem criadas"
  type = map(object({
    cidr_block = string
    az         = string
  }))
  default = {
    "public_a_zone1" = {
      cidr_block = "10.0.1.0/24"
      az         = "us-east-1a"
    },
    "public_b_zone2" = {
      cidr_block = "10.0.2.0/24"
      az         = "us-east-1b"
    },
    "private_a_zone1" = {
      cidr_block = "10.0.10.0/24"
      az         = "us-east-1a"
    }
  }
}

resource "aws_subnet" "example" {
  for_each = var.subnets

  vpc_id            = aws_vpc.main.id
  cidr_block        = each.value.cidr_block
  availability_zone = each.value.az

  tags = {
    Name = "subnet-${each.key}"
  }
}

Com for_each, os recursos são endereçados como elementos de um mapa, por exemplo: aws_subnet.example["public_a_zone1"]. Se a sub-rede “public_b_zone2” for removida do mapa de variáveis, o Terraform destruirá apenas essa instância específica, sem afetar as demais, devido à identidade estável fornecida pela chave (each.key).

Uso Recomendado: for_each é a escolha ideal para iteração sobre recursos onde a identidade individual e estável de cada instância é fundamental. Ele promove um código mais previsível e de fácil manutenção.

3. Expressões Condicionais

As configurações do Terraform frequentemente exigem lógica para adaptar o provisionamento a diferentes ambientes (produção, homologação, desenvolvimento) ou requisitos específicos. A ferramenta para isso é o operador ternário: condição ? valor_se_verdadeiro : valor_se_falso.

3.1. Definição Dinâmica de Atributos

Este é o caso de uso mais comum, permitindo que atributos de recursos sejam definidos de forma condicional:

1
2
3
4
5
6
7
8
9
10
11
12
13
variable "environment" {
  type    = string
  default = "dev"
}

resource "aws_instance" "web" {
  ami           = "ami-0c55b159cbfafe1f0" # Exemplo de AMI, deve ser dinâmica em um cenário real
  instance_type = var.environment == "prod" ? "t3.large" : "t3.micro"

  tags = {
    Name = "web-${var.environment}"
  }
}

Aqui, o instance_type da VM será t3.large se var.environment for “prod”, e t3.micro caso contrário.

3.2. Criação Condicional de Recursos

Para criar ou omitir um recurso baseado em uma condição, o count é frequentemente combinado com uma expressão condicional:

1
2
3
4
5
6
7
8
9
10
11
12
13
variable "enable_s3_logging" {
  type    = bool
  default = false
}

resource "aws_s3_bucket" "detailed_logs" {
  # O recurso será criado (count = 1) se 'enable_s3_logging' for verdadeiro,
  # ou não será criado (count = 0) se for falso.
  count = var.enable_s3_logging ? 1 : 0

  bucket = "logs-detalhados-prod"
  acl    = "private"
}

Se var.enable_s3_logging for true, o bucket S3 será provisionado. Se false, o count será 0 e nenhum bucket será criado. Ao referenciar este recurso, o índice [0] deve ser utilizado: aws_s3_bucket.detailed_logs[0].id.

Para for_each, a criação condicional de um recurso único pode ser feita passando um mapa vazio:

1
2
3
4
5
6
resource "aws_s3_bucket" "another_logs_bucket" {
  for_each = var.enable_s3_logging ? { "prod_specific_bucket" = true } : {}

  bucket = "minha-empresa-outros-logs-prod"
  acl    = "private"
}

Ambas as abordagens são válidas, mas o padrão count = var.condicao ? 1 : 0 é largamente adotado para a criação condicional de recursos singulares.

Conclusão

O domínio de count, for_each e expressões condicionais é um passo fundamental para otimizar suas configurações Terraform. Essas ferramentas permitem uma visão mais ampla e previsível do código terraform além da simples declaração de recursos, capacitando você a construir infraestruturas dinâmicas, escaláveis e reutilizáveis.

  • Utilize count com critério, ciente de seus potenciais impactos na reindexação de recursos.
  • Prefira for_each para iterações que requerem estabilidade e identificação única de cada recurso.
  • Empregue expressões ternárias para injetar lógica condicional, adaptando atributos e a existência de recursos conforme as necessidades do ambiente.

A aplicação desses conceitos resulta em um código mais limpo, reutilizável e alinhado aos princípios do Don’t Repeat Yourself (DRY), elevando a maturidade de suas práticas com Terraform.

This post is licensed under CC BY 4.0 by the author.