Melhores práticas para estilo e estrutura geral

Este documento fornece recomendações básicas de estilo e estrutura para suas configurações do Terraform. Essas recomendações se aplicam a módulos reutilizáveis do Terraform e a configurações raiz.

Este guia não é uma introdução ao Terraform. Para uma introdução ao uso do Terraform com o Google Cloud, consulte Primeiros passos com o Terraform.

Seguir uma estrutura de módulos padrão

  • Os módulos do Terraform precisam seguir a estrutura padrão do módulo.
  • Inicie cada módulo com um arquivo main.tf, em que os recursos estão localizados por padrão.
  • Em todos os módulos, inclua um arquivo README.md no formato Markdown. No arquivo README.md, inclua a documentação básica sobre o módulo.
  • Coloque exemplos em uma pasta examples/, com um subdiretório separado para cada exemplo. Para cada exemplo, inclua um arquivo README.md detalhado.
  • Crie agrupamentos lógicos de recursos com os próprios arquivos e nomes descritivos, como network.tf, instances.tf ou loadbalancer.tf.
    • Evite dar a cada recurso o próprio arquivo. Agrupe recursos por finalidade compartilhada. Por exemplo, combine google_dns_managed_zone e google_dns_record_set em dns.tf.
  • No diretório raiz do módulo, inclua apenas o Terraform (*.tf) e os arquivos de metadados do repositório (como README.md e CHANGELOG.md).
  • Coloque qualquer documentação adicional em um subdiretório docs/.

Adotar uma convenção de nomenclatura

  • Nomeie todos os objetos de configuração usando sublinhados para delimitar várias palavras. Essa prática garante consistência com a convenção de nomenclatura para os tipos de recursos, tipos de fonte de dados e outros valores predefinidos. Essa convenção não se aplica a argumentos de nome.

    Recomendação:

    resource "google_compute_instance" "web_server" {
      name = "web-server"
    }
    

    Não recomendado:

    resource "google_compute_instance" "web-server" {
      name = "web-server"
    }
    
  • Para simplificar referências a um recurso que é o único do tipo (por exemplo, um único balanceador de carga para um módulo inteiro), nomeie o main do recurso.

    • É preciso pensar mais para se lembrar de some_google_resource.my_special_resource.id em vez de some_google_resource.main.id.
  • Para diferenciar os recursos de um mesmo tipo (por exemplo, primary e secondary), forneça nomes de recursos significativos.

  • Os nomes dos recursos precisam estar no singular.

  • No nome do recurso, não repita o tipo de recurso. Exemplo:

    Recomendação:

    resource "google_compute_global_address" "main" { ... }
    

    Não recomendado:

    resource "google_compute_global_address" "main_global_address" { … }
    

Use as variáveis com cuidado

  • Declare todas as variáveis em variables.tf.
  • Dê nomes descritivos às variáveis que sejam relevantes para o uso ou propósito:
    • Entradas, variáveis locais e saídas que representam valores numéricos, como tamanhos de disco ou de RAM, precisam ser nomeados com unidades (como ram_size_gb). As APIs do Google Cloud não têm unidades padrão. Portanto, a nomeação de variáveis com unidades deixa a unidade de entrada esperada clara para os administradores de configuração.
    • Para unidades de armazenamento, use prefixos de unidade binária (poderes de 1.024): kibi, mebi, gibi. Para todas as outras unidades de medida, use prefixos de unidade decimal (capacidades de 1.000): kilo, mega, giga. Esse uso corresponde ao uso no Google Cloud.
    • Para simplificar a lógica condicional, dê nomes positivos às variáveis booleanas (por exemplo, enable_external_access).
  • As variáveis precisam ter descrições. As descrições são incluídas automaticamente na documentação gerada automaticamente de um módulo publicado. As descrições adicionam mais contexto aos novos desenvolvedores que nomes descritivos não podem fornecer.
  • Dê tipos definidos às variáveis.
  • Quando apropriado, forneça valores padrão:
    • No caso de variáveis que tenham valores independentes de ambiente (como tamanho do disco), forneça valores padrão.
    • Para variáveis que tenham valores específicos do ambiente (como project_id), não forneça valores padrão. Dessa forma, o módulo de chamada precisa fornecer valores significativos.
  • Use padrões vazios para variáveis (como strings ou listas vazias) somente quando deixar a variável vazia é uma preferência válida que as APIs subjacentes não rejeitam.
  • Tenha bom senso ao usar variáveis. Apenas parametrize valores que precisam variar para cada instância ou ambiente. Ao decidir se quer expor uma variável, verifique se você tem um caso de uso concreto para alterá-la. Se houver apenas uma pequena chance de que uma variável seja necessária, não a exponha.
    • Adicionar uma variável com um valor padrão é compatível com versões anteriores.
    • A remoção de uma variável é incompatível com versões anteriores.
    • Nos casos em que um literal é reutilizado em vários lugares, é possível usar um valor local sem expô-lo como uma variável.

Expor saídas

  • Organize todas as saídas em um arquivo outputs.tf.
  • Forneça descrições significativas para todas as saídas.
  • Descrições de saída do documento no arquivo README.md. Gerar descrições automaticamente na confirmação com ferramentas como terraform-docs.
  • Mostra todos os valores úteis que os módulos raiz podem precisar para consultar ou compartilhar. Especialmente para módulos de código aberto ou muito usados, exponha todas as saídas que têm potencial de consumo.
  • Não transmita saídas diretamente por meio de variáveis de entrada, porque isso impede que elas sejam adicionadas corretamente ao gráfico de dependência. Para garantir que as dependências implícitas sejam criadas, verifique se a saída tem atributos de referência de recursos. Em vez de referenciar diretamente uma variável de entrada para uma instância, transmita o atributo conforme mostrado aqui:

    Recomendação:

    output "name" {
      description = "Name of instance"
      value       = google_compute_instance.main.name
    }
    

    Não recomendado:

    output "name" {
      description = "Name of instance"
      value       = var.name
    }
    

Usar fontes de dados

  • Coloque fontes de dados ao lado dos recursos que fazem referência a elas. Por exemplo, se você estiver buscando uma imagem para usar na inicialização de uma instância, coloque-a ao lado da instância em vez de coletar os recursos de dados no próprio arquivo.
  • Se o número de fontes de dados ficar grande, mova-as para um arquivo data.tf dedicado.
  • Para buscar dados relativos ao ambiente atual, use a interpolação de variáveis ou recursos.

Limitar o uso de scripts personalizados

  • Use scripts somente quando necessário. O estado dos recursos criados por scripts não é contabilizado ou gerenciado pelo Terraform.
    • Evite scripts personalizados, se possível. Use-os somente quando os recursos do Terraform não forem compatíveis com o comportamento desejado.
    • Todos os scripts personalizados precisam ter um motivo claramente documentado para um plano de suspensão de uso atual e idealmente.
  • O Terraform pode chamar scripts personalizados por meio de provisionamentos, incluindo o provisionamento local-exec.
  • Coloque scripts personalizados chamados pelo Terraform em um diretório scripts/.

Incluir scripts auxiliares em um diretório separado

  • Organize scripts auxiliares que não são chamados pelo Terraform em um diretório helpers/.
  • Documente os scripts auxiliares no arquivo README.md com explicações e invocações de exemplo.
  • Se os scripts auxiliares aceitarem argumentos, forneça verificação de argumentos e saída de --help.

Colocar arquivos estáticos em um diretório separado

  • Os arquivos estáticos referenciados pelo Terraform, mas não executados (como scripts de inicialização carregados em instâncias do Compute Engine) precisam ser organizados em um diretório files/.
  • Coloque arquivos longos do HereDocs em arquivos externos, separados dos arquivos HCL. Faça referência a eles com a função file().
  • Para arquivos lidos com a função templatefile do Terraform, use a extensão de arquivo .tftpl.
    • Os modelos precisam ser colocados em um diretório templates/.

Proteger recursos com estado

Para recursos com estado, como bancos de dados, verifique se a proteção contra exclusão está ativada. Exemplo:

resource "google_sql_database_instance" "main" {
  name = "primary-instance"
  settings {
    tier = "D0"
  }

  lifecycle {
    prevent_destroy = true
  }
}

Usar a formatação integrada

Todos os arquivos do Terraform precisam estar em conformidade com os padrões de terraform fmt.

Limitação da complexidade das expressões

  • Limite a complexidade de expressões interpoladas individuais. Se muitas funções forem necessárias em uma única expressão, considere dividi-la em várias expressões usando valores locais.
  • Nunca tenha mais de uma operação ternária em uma única linha. Em vez disso, use vários valores locais para criar a lógica.

Usar count para valores condicionais

Para instanciar um recurso condicionalmente, use o meta-argumento count. Exemplo:

variable "readers" {
  description = "..."
  type        = list
  default     = []
}

resource "resource_type" "reference_name" {
  // Do not create this resource if the list of readers is empty.
  count = length(var.readers) == 0 ? 0 : 1
  ...
}

Tenha moderação ao usar variáveis especificadas pelo usuário para definir a variável count para recursos. Se um atributo de recurso for fornecido para essa variável (como project_id) e esse recurso ainda não existir, o Terraform não poderá gerar um plano. Em vez disso, o Terraform informa o erro value of count cannot be computed. Nesses casos, use uma variável enable_x separada para calcular a lógica condicional.

Usar for_each para recursos iterados

Se você quiser criar várias cópias de um recurso com base em um recurso de entrada, use o meta-argumento for_each.

Publicar módulos em um registro

A seguir