Como Criar um Módulo Terraform Personalizado

O autor selecionou o Fundo Livre e de Código Aberto para receber uma doação como parte do programa Write for DOnations.

Introdução

Módulos do Terraform permitem agrupar recursos distintos de sua infraestrutura em um único recurso unificado. Você pode reutilizá-los posteriormente com possíveis personalizações, sem repetir as definições de recursos cada vez que precisar deles, o que é benéfico para projetos grandes e complexamente estruturados. Você pode personalizar instâncias de módulos usando variáveis de entrada que você define, assim como extrair informações deles usando saídas. Além de criar seus próprios módulos personalizados, você também pode usar os módulos pré-fabricados publicados publicamente no Registro do Terraform. Os desenvolvedores podem usar e personalizar eles usando entradas como os módulos que você cria, mas seu código-fonte é armazenado e extraído da nuvem.

Neste tutorial, você criará um módulo Terraform que configurará múltiplos Droplets atrás de um Balanceador de Carga para redundância. Você também usará os recursos de loop for_each e count da Linguagem de Configuração da HashiCorp (HCL) para implantar múltiplas instâncias personalizadas do módulo ao mesmo tempo.

Pré-requisitos

Nota: Este tutorial foi especificamente testado com Terraform 1.1.3.

Estrutura e Benefícios do Módulo

Nesta seção, você aprenderá quais benefícios os módulos trazem, onde geralmente são colocados no projeto e como devem ser estruturados.

Os módulos personalizados do Terraform são criados para encapsular componentes conectados que são usados e implantados juntos com frequência em projetos maiores. Eles são autônomos, agrupando apenas os recursos, variáveis e provedores de que precisam.

Os módulos geralmente são armazenados em uma pasta central na raiz do projeto, cada um em sua respectiva subpasta abaixo. Para manter uma separação limpa entre os módulos, sempre os arquitete com um único propósito e certifique-se de que nunca contenham submódulos.

É útil criar módulos a partir dos seus esquemas de recursos quando você se vê repetindo-os com customizações infrequentes. Embalar um único recurso como um módulo pode ser supérfluo e gradualmente remove a simplicidade da arquitetura geral.

Para pequenos projetos de desenvolvimento e teste, incorporar módulos não é necessário porque eles não trazem muita melhoria nesses casos. Com sua capacidade de personalização, os módulos são o elemento de construção de projetos complexamente estruturados. Os desenvolvedores usam módulos para projetos maiores devido às vantagens significativas em evitar a duplicação de código. Os módulos também oferecem o benefício de que as definições precisam ser modificadas apenas em um único local, o que então será propagado pelo restante da infraestrutura.

A seguir, você definirá, usará e personalizará módulos em seus projetos Terraform.

Criando um Módulo

Nesta seção, você definirá múltiplos Droplets e um Balanceador de Carga como recursos do Terraform e os empacotará em um módulo. Você também tornará o módulo resultante personalizável usando entradas de módulo.

Você irá armazenar o módulo em um diretório chamado droplet-lb, sob um diretório chamado módulos. Supondo que você esteja no diretório terraform-modules que você criou como parte dos pré-requisitos, crie ambos de uma vez executando:

  1. mkdir -p modules/droplet-lb

O argumento -p instrui o mkdir a criar todos os diretórios no caminho fornecido.

Navegue até ele:

  1. cd modules/droplet-lb

Como foi observado na seção anterior, os módulos contêm os recursos e variáveis que eles usam. A partir do Terraform 0.13, eles também devem incluir definições dos provedores que usam. Módulos não requerem nenhuma configuração especial para notar que o código representa um módulo, pois o Terraform considera todo diretório contendo código HCL como um módulo, mesmo o diretório raiz do projeto.

As variáveis definidas em um módulo são expostas como suas entradas e podem ser usadas nas definições de recursos para personalizá-los. O módulo que você irá criar terá duas entradas: o número de Droplets a serem criados e o nome de seu grupo. Crie e abra para edição um arquivo chamado variables.tf onde você irá armazenar as variáveis:

  1. nano variables.tf

Adicione as seguintes linhas:

modules/droplet-lb/variables.tf
variable "droplet_count" {}
variable "group_name" {}

Salve e feche o arquivo.

Você vai armazenar a definição do Droplet em um arquivo chamado droplets.tf. Crie e abra-o para edição:

  1. nano droplets.tf

Adicione as seguintes linhas:

modules/droplet-lb/droplets.tf
resource "digitalocean_droplet" "droplets" {
  count  = var.droplet_count
  image  = "ubuntu-20-04-x64"
  name   = "${var.group_name}-${count.index}"
  region = "fra1"
  size   = "s-1vcpu-1gb"
}

Para o parâmetro count, que especifica quantas instâncias de um recurso criar, você passa a variável droplet_count. Seu valor será especificado quando o módulo for chamado a partir do código do projeto principal. O nome de cada Droplet implantado será diferente, o que você alcança ao anexar o índice do Droplet atual ao nome do grupo fornecido. A implantação dos Droplets será na região fra1 e eles executarão o Ubuntu 20.04.

Quando terminar, salve e feche o arquivo.

Com os Droplets agora definidos, você pode prosseguir para a criação do Balanceador de Carga. Você vai armazenar a definição de recurso dele em um arquivo chamado lb.tf. Crie e abra-o para edição executando:

  1. nano lb.tf

Adicione sua definição de recurso:

modules/droplet-lb/lb.tf
resource "digitalocean_loadbalancer" "www-lb" {
  name   = "lb-${var.group_name}"
  region = "fra1"

  forwarding_rule {
    entry_port     = 80
    entry_protocol = "http"

    target_port     = 80
    target_protocol = "http"
  }

  healthcheck {
    port     = 22
    protocol = "tcp"
  }

  droplet_ids = [
    for droplet in digitalocean_droplet.droplets:
      droplet.id
  ]
}

Você define o Balanceador de Carga com o nome do grupo em seu nome para torná-lo distinguível. Você o implanta na região fra1 juntamente com os Droplets. As próximas duas seções especificam as portas e protocolos de destino e monitoramento.

O bloco droplet_ids destacado recebe os IDs dos Droplets, que devem ser gerenciados pelo Balanceador de Carga. Como existem vários Droplets, e o número deles não é conhecido antecipadamente, você utiliza um laço for para percorrer a coleção de Droplets (digitalocean_droplet.droplets) e obter seus IDs. Você envolve o laço for com colchetes ([]) para que a coleção resultante seja uma lista.

Salve e feche o arquivo.

Agora você definiu o Droplet, o Balanceador de Carga e as variáveis para o seu módulo. Você precisará definir os requisitos do provedor, especificando quais provedores o módulo utiliza, incluindo sua versão e onde estão localizados. Desde o Terraform 0.13, os módulos devem definir explicitamente as fontes dos provedores não mantidos pela Hashicorp que eles usam; isso ocorre porque eles não os herdam do projeto pai.

Você armazenará os requisitos do provedor em um arquivo chamado provider.tf. Crie-o para edição executando:

  1. nano provider.tf

Adicione as seguintes linhas para exigir o provedor digitalocean:

modules/droplet-lb/provider.tf
terraform {
  required_providers {
    digitalocean = {
      source = "digitalocean/digitalocean"
      version = "~> 2.0"
    }
  }
}

Salve e feche o arquivo quando terminar. O módulo droplet-lb agora requer o provedor digitalocean.

Os módulos também suportam saídas, que você pode usar para extrair informações internas sobre o estado de seus recursos. Você definirá uma saída que expõe o endereço IP do Balanceador de Carga e a armazenará em um arquivo chamado outputs.tf. Crie-o para edição:

  1. nano outputs.tf

Adicione a seguinte definição:

modules/droplet-lb/outputs.tf
output "lb_ip" {
  value = digitalocean_loadbalancer.www-lb.ip
}

Esta saída recupera o endereço IP do Balanceador de Carga. Salve e feche o arquivo.

O módulo droplet-lb agora está completamente funcional e pronto para implantação. Você o chamará do código principal, que você armazenará na raiz do projeto. Primeiro, navegue até ele indo para cima no diretório de arquivos duas vezes:

  1. cd ../..

Em seguida, crie e abra para edição um arquivo chamado main.tf, no qual você usará o módulo:

  1. nano main.tf

Adicione as seguintes linhas:

main.tf
module "groups" {
  source = "./modules/droplet-lb"

  droplet_count = 3
  group_name    = "group1"
}

output "loadbalancer-ip" {
  value = module.groups.lb_ip
}

Nesta declaração, você invoca o módulo droplet-lb localizado no diretório especificado como source. Você configura as entradas que ele fornece, droplet_count e group_name, que é definido como group1 para que você possa distinguir entre as instâncias posteriormente.

Como a saída do IP do Balanceador de Carga é definida em um módulo, não será mostrada automaticamente quando você aplicar o projeto. A solução para isso é criar outra saída recuperando seu valor (loadbalancer_ip).

Salve e feche o arquivo quando terminar.

Inicialize o módulo executando:

  1. terraform init

A saída será parecida com esta:

Output
Initializing modules... - groups in modules/droplet-lb Initializing the backend... Initializing provider plugins... - Finding digitalocean/digitalocean versions matching "~> 2.0"... - Installing digitalocean/digitalocean v2.19.0... - Installed digitalocean/digitalocean v2.19.0 (signed by a HashiCorp partner, key ID F82037E524B9C0E8) ... Terraform has been successfully initialized! You may now begin working with Terraform. Try running "terraform plan" to see any changes that are required for your infrastructure. All Terraform commands should now work. If you ever set or change modules or backend configuration for Terraform, rerun this command to reinitialize your working directory. If you forget, other commands will detect it and remind you to do so if necessary.

Você pode tentar planejar o projeto para ver quais ações o Terraform tomaria executando:

  1. terraform plan -var "do_token=${DO_PAT}"

A saída será semelhante a esta:

Output
... Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # O módulo groups.digitalocean_droplet.droplets[0] será criado + resource "digitalocean_droplet" "droplets" { ... + name = "group1-0" ... } # O módulo groups.digitalocean_droplet.droplets[1] será criado + resource "digitalocean_droplet" "droplets" { ... + name = "group1-1" ... } # O módulo groups.digitalocean_droplet.droplets[2] será criado + resource "digitalocean_droplet" "droplets" { ... + name = "group1-2" ... } # O módulo groups.digitalocean_loadbalancer.www-lb será criado + resource "digitalocean_loadbalancer" "www-lb" { ... + name = "lb-group1" ... } Plan: 4 to add, 0 to change, 0 to destroy. ...

Este resultado detalha que o Terraform criaria três Droplets, nomeados group1-0, group1-1 e group1-2, e também criaria um Balanceador de Carga chamado group1-lb, que gerenciará o tráfego de e para os três Droplets.

Você pode tentar aplicar o projeto na nuvem executando:

  1. terraform apply -var "do_token=${DO_PAT}"

Digite yes quando solicitado. O resultado mostrará todas as ações e também será mostrado o endereço IP do Balanceador de Carga:

Output
module.groups.digitalocean_droplet.droplets[1]: Creating... module.groups.digitalocean_droplet.droplets[0]: Creating... module.groups.digitalocean_droplet.droplets[2]: Creating... ... Apply complete! Resources: 4 added, 0 changed, 0 destroyed. Outputs: loadbalancer-ip = ip_address

Você criou um módulo contendo um número personalizável de Droplets e um Balanceador de Carga que será configurado automaticamente para gerenciar seu tráfego de entrada e saída.

Renomeando Recursos Implementados

Na seção anterior, você implantou o módulo que definiu e o chamou de groups. Se desejar alterar o nome, simplesmente renomear a chamada do módulo não produzirá os resultados esperados. Renomear a chamada fará com que o Terraform destrua e recrie recursos, causando tempo de inatividade excessivo.

Por exemplo, abra main.tf para edição executando:

  1. nano main.tf

Renomeie o módulo groups para groups_renamed, conforme destacado:

main.tf
module "groups_renamed" {
  source = "./modules/droplet-lb"

  droplet_count = 3
  group_name    = "group1"
}

output "loadbalancer-ip" {
  value = module.groups_renamed.lb_ip
}

Salve e feche o arquivo. Em seguida, inicialize o projeto novamente:

  1. terraform init

Agora você pode planejar o projeto:

  1. terraform plan -var "do_token=${DO_PAT}"

A saída será longa, mas parecerá semelhante a isto:

Output
... Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create - destroy Terraform will perform the following actions: # module.groups.digitalocean_droplet.droplets[0] será destruído ... # module.groups_renamed.digitalocean_droplet.droplets[0] será criado ...

O Terraform solicitará que você destrua as instâncias existentes e crie novas. Isso é destrutivo e desnecessário, e pode levar a tempo de inatividade indesejado.

Em vez disso, usando o bloco moved, você pode instruir o Terraform a mover os recursos antigos para o novo nome. Abra main.tf para edição e adicione as seguintes linhas ao final do arquivo:

moved {
  from = module.groups
  to   = module.groups_renamed
}

Quando terminar, salve e feche o arquivo.

Agora você pode planejar o projeto:

  1. terraform plan -var "do_token=${DO_PAT}"

Quando você planeja com o bloco moved presente em main.tf, o Terraform quer mover os recursos, em vez de recriá-los:

Output
Terraform will perform the following actions: # O módulo groups.digitalocean_droplet.droplets[0] foi movido para module.groups_renamed.digitalocean_droplet.droplets[0] ... # O módulo groups.digitalocean_droplet.droplets[1] foi movido para module.groups_renamed.digitalocean_droplet.droplets[1] ...

A movimentação de recursos altera seu lugar no estado do Terraform, o que significa que os recursos reais na nuvem não serão modificados, destruídos ou recriados.

Como você modificará significativamente a configuração no próximo passo, destrua os recursos implantados executando:

  1. terraform destroy -var "do_token=${DO_PAT}"

Insira sim quando solicitado. A saída terminará em:

Output
... Destroy complete! Resources: 4 destroyed.

Nesta seção, você renomeou recursos em seu projeto Terraform sem destruí-los no processo. Agora, você implantará várias instâncias de um módulo a partir do mesmo código usando for_each e count.

Implantando Múltiplas Instâncias do Módulo

Nesta seção, você usará count e for_each para implantar o módulo droplet-lb várias vezes com personalizações.

Usando count

Uma maneira de implantar várias instâncias do mesmo módulo de uma vez é passar quantas para o parâmetro count, que está automaticamente disponível para cada módulo. Abra main.tf para edição:

  1. nano main.tf

Modifique-o para ficar assim, removendo a definição de saída existente e o bloco moved:

main.tf
module "groups" {
  source = "./modules/droplet-lb"

  count  = 3

  droplet_count = 3
  group_name    = "group1-${count.index}"
}

Ao definir count como 3, você instrui o Terraform a implantar o módulo três vezes, cada uma com um nome de grupo diferente. Quando terminar, salve e feche o arquivo.

Planeje a implantação executando:

  1. terraform plan -var "do_token=${DO_PAT}"

A saída será longa e parecerá com isso:

Output
... Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # O módulo groups[0].digitalocean_droplet.droplets[0] será criado ... # O módulo groups[0].digitalocean_droplet.droplets[1] será criado ... # O módulo groups[0].digitalocean_droplet.droplets[2] será criado ... # O módulo groups[0].digitalocean_loadbalancer.www-lb será criado ... # O módulo groups[1].digitalocean_droplet.droplets[0] será criado ... # O módulo groups[1].digitalocean_droplet.droplets[1] será criado ... # O módulo groups[1].digitalocean_droplet.droplets[2] será criado ... # O módulo groups[1].digitalocean_loadbalancer.www-lb será criado ... # O módulo groups[2].digitalocean_droplet.droplets[0] será criado ... # O módulo groups[2].digitalocean_droplet.droplets[1] será criado ... # O módulo groups[2].digitalocean_droplet.droplets[2] será criado ... # O módulo groups[2].digitalocean_loadbalancer.www-lb será criado ... Plan: 12 to add, 0 to change, 0 to destroy. ...

Detalhes do Terraform na saída indicam que cada uma das três instâncias do módulo teria três Droplets e um Balanceador de Carga associado a elas.

Usando for_each

Você pode usar for_each para módulos quando você precisa de uma customização de instância mais complexa, ou quando o número de instâncias depende de dados de terceiros (frequentemente apresentados como mapas) que não são conhecidos ao escrever o código.

Agora você vai definir um mapa que relaciona nomes de grupos com contagens de Droplets e implantar instâncias de droplet-lb de acordo com isso. Abra main.tf para edição executando:

  1. nano main.tf

Modifique o arquivo para ficar assim:

main.tf
variable "group_counts" {
  type    = map
  default = {
    "group1" = 1
    "group2" = 3
  }
}

module "groups" {
  source   = "./modules/droplet-lb"
  for_each = var.group_counts

  droplet_count = each.value
  group_name    = each.key
}

Primeiro você define um mapa chamado group_counts que contém quantos Droplets um grupo dado deve ter. Em seguida, você invoca o módulo droplet-lb, mas especifica que o loop for_each deve operar em var.group_counts, o mapa que você definiu logo antes. droplet_count recebe each.value, o valor do par atual, que é a contagem de Droplets para o grupo atual. group_name recebe o nome do grupo.

Salve e feche o arquivo quando terminar.

Tente aplicar a configuração executando:

  1. terraform plan -var "do_token=${DO_PAT}"

A saída detalhará as ações que o Terraform executaria para criar os dois grupos com seus Droplets e Balanceadores de Carga:

Output
... Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # O droplet module.groups["grupo1"].digitalocean_droplet.droplets[0] será criado ... # O balanceador de carga module.groups["grupo1"].digitalocean_loadbalancer.www-lb será criado ... # O droplet module.groups["grupo2"].digitalocean_droplet.droplets[0] será criado ... # O droplet module.groups["grupo2"].digitalocean_droplet.droplets[1] será criado ... # O droplet module.groups["grupo2"].digitalocean_droplet.droplets[2] será criado ... # O balanceador de carga module.groups["grupo2"].digitalocean_loadbalancer.www-lb será criado ...

Nesta etapa, você utilizou count e for_each para implantar várias instâncias personalizadas do mesmo módulo a partir do mesmo código central.

Conclusão

Neste tutorial, você criou e implantou módulos do Terraform. Você utilizou módulos para agrupar recursos logicamente vinculados e os personalizou para implantar várias instâncias diferentes a partir de uma definição de código central. Você também utilizou saídas para mostrar atributos dos recursos contidos no módulo.

Se você gostaria de aprender mais sobre o Terraform, confira nossa série Como Gerenciar Infraestrutura com Terraform.

Source:
https://www.digitalocean.com/community/tutorials/how-to-build-a-custom-terraform-module