Práticas recomendadas para usar o Terraform

Este documento apresenta diretrizes e recomendações para um desenvolvimento eficaz com o Terraform em vários membros da equipe e fluxos de trabalho.

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.

Diretrizes gerais de estilo e estrutura

As recomendações a seguir abordam o estilo e a estrutura básicos das configurações do Terraform. As recomendações se aplicam a módulos reutilizáveis do Terraform e a configurações raiz.

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

Módulos reutilizáveis

Para módulos destinados à reutilização, use as diretrizes a seguir e as anteriores.

Ativar as APIs necessárias em módulos

Os módulos do Terraform podem ativar todos os serviços necessários usando o recurso google_project_service ou o módulo project_services. A inclusão de ativação de APIs facilita as demonstrações.

  • Se a ativação da API estiver incluída em um módulo, ela precisa ser desativada ao expor uma variável enable_apis que tem true como padrão.
  • Se a ativação da API estiver incluída em um módulo, ela precisa definir disable_services_on_destroy como false, porque esse atributo pode causar problemas ao trabalhar com várias instâncias de do módulo.

    Exemplo:

    module "project-services" {
      source  = "terraform-google-modules/project-factory/google//modules/project_services"
      version = "~> 12.0"
    
      project_id  = var.project_id
      enable_apis = var.enable_apis
    
      activate_apis = [
        "compute.googleapis.com",
        "pubsub.googleapis.com",
      ]
      disable_services_on_destroy = false
    }
    

Incluir um arquivo de proprietários

Para todos os módulos compartilhados, inclua um arquivo OWNERS (ou CODEOWNERS no GitHub), que documenta quem é responsável pelo módulo. Antes que qualquer solicitação de envio seja mesclada, um proprietário deve aprová-la.

Lançar versões marcadas

Às vezes, os módulos exigem alterações interruptivas e você precisa comunicar os efeitos aos usuários para que eles possam fixar as configurações em uma versão específica.

Confira se os módulos compartilhados seguem o SemVer v2.0.0 quando novas versões são marcadas ou lançadas.

Ao referenciar um módulo, use uma restrição de versão para fixar na versão principal. Exemplo:

module "gke" {
  source  = "terraform-google-modules/kubernetes-engine/google"
  version = "~> 20.0"
}

Não configurar provedores ou back-ends

Os módulos compartilhados não podem configurar provedores ou back-ends. Em vez disso, configure provedores e back-ends em módulos raiz.

Para módulos compartilhados, defina as versões mínimas de provedor necessárias em um bloco required_providers, da seguinte maneira:

terraform {
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = ">= 4.0.0"
    }
  }
}

A menos que seja comprovado de outra forma, suponha que as novas versões do provedor funcionarão.

Expor rótulos como uma variável

Permitir flexibilidade na rotulagem de recursos por meio da interface do módulo. Forneça uma variável labels com um valor padrão de um mapa vazio, da seguinte maneira:

variable "labels" {
  description = "A map of labels to apply to contained resources."
  default     = {}
  type        = "map"
}

Expor saídas para todos os recursos

As variáveis e saídas permitem inferir dependências entre módulos e recursos. Sem nenhuma saída, os usuários não podem ordenar corretamente seu módulo em relação às configurações do Terraform.

Para cada recurso definido em um módulo compartilhado, inclua pelo menos uma saída que faça referência ao recurso.

Usar submódulos in-line para lógica complexa

  • Os módulos in-line permitem organizar módulos complexos do Terraform em unidades menores e eliminar a duplicação de recursos comuns.
  • Coloque módulos in-line em modules/$modulename.
  • Tratar módulos in-line como particulares, não a serem usados por módulos externos, a menos que a documentação do módulo compartilhado declare especificamente o contrário.
  • O Terraform não rastreia recursos refatorados. Se você começar com vários recursos no módulo de nível superior e depois enviá-los a submódulos, o Terraform tentará recriar todos os recursos refatorados. Para atenuar esse comportamento, use blocos moved (link em inglês) ao refatorar.
  • As saídas definidas por módulos internos não são expostas automaticamente. Para compartilhar as saídas de módulos internos, exporte-as novamente.

Módulos raiz do Terraform

As configurações raiz (módulos raiz) são os diretórios de trabalho de onde você executa a CLI do Terraform. Verifique se as configurações raiz estão de acordo com os padrões a seguir (e com as diretrizes anteriores do Terraform, quando aplicável). As recomendações explícitas para módulos raiz substituem as diretrizes gerais.

Minimizar o número de recursos em cada módulo raiz

É importante evitar que uma única configuração raiz fique muito grande, com muitos recursos armazenados no mesmo diretório e estado. Todos os recursos em uma configuração raiz específica são atualizados sempre que o Terraform é executado. Isso pode causar lentidão na execução se muitos recursos estiverem incluídos em um único estado. Uma regra geral: não inclua mais de 100 recursos (e, de preferência, apenas algumas dezenas) em um único estado.

Use diretórios separados para cada app.

Para gerenciar aplicativos e projetos de maneira independente, coloque recursos em cada aplicativo e projeto nos próprios diretórios do Terraform. Um serviço pode representar um aplicativo específico ou um serviço comum, como uma rede compartilhada. Aninhe todo o código do Terraform para um serviço específico em um diretório (incluindo subdiretórios).

Dividir aplicativos em subdiretórios específicos do ambiente

Ao implantar serviços no Google Cloud, divida a configuração do Terraform do serviço em dois diretórios de nível superior: um diretório modules que contém a configuração real do serviço e um diretório environments que contém as configurações raiz de cada ambiente.

-- SERVICE-DIRECTORY/
   -- OWNERS
   -- modules/
      -- <service-name>/
         -- main.tf
         -- variables.tf
         -- outputs.tf
         -- provider.tf
         -- README
      -- ...other…
   -- environments/
      -- dev/
         -- backend.tf
         -- main.tf

      -- qa/
         -- backend.tf
         -- main.tf

      -- prod/
         -- backend.tf
         -- main.tf

Usar diretórios de ambiente

Para compartilhar código entre ambientes, faça referência aos módulos. Normalmente, pode ser um módulo de serviço que inclua a configuração base compartilhada do Terraform para o serviço. Nos módulos de serviço, codifique entradas comuns e exija apenas entradas específicas do ambiente como variáveis.

Cada diretório do ambiente precisa conter os seguintes arquivos:

  • Um arquivo backend.tf que declara o local do estado do back-end do Terraform (normalmente, Cloud Storage)
  • Um arquivo main.tf que instancia o módulo de serviço

Cada diretório de ambiente (dev, qa, prod) corresponde a um espaço de trabalho do Terraform e implanta uma versão do serviço nesse ambiente. Esses espaços de trabalho isolam recursos específicos do ambiente nos próprios contextos. Use apenas o espaço de trabalho padrão.

Não é recomendado ter vários espaços de trabalho da CLI em um ambiente pelos seguintes motivos:

  • Pode ser difícil inspecionar a configuração em cada espaço de trabalho.
  • Não é recomendado ter um único back-end compartilhado para vários espaços de trabalho porque ele se torna um único ponto de falha se for usado para separação de ambiente.
  • Embora a reutilização de código seja possível, dificulta a leitura do código, que precisa ser alternado com base na variável atual do espaço de trabalho (por exemplo, terraform.workspace == "foo" ? this : that).

Para ver mais informações, consulte os seguintes tópicos:

Expor saídas por estado remoto

Verifique se você está expondo saídas úteis de instâncias de módulo de um módulo raiz.

Por exemplo, o snippet de código a seguir passa pela saída do ID do projeto da instância do módulo de fábrica do projeto como uma saída do módulo raiz.

# Project root module
terraform {
  backend "gcs" {
    bucket  = "BUCKET"
  }
}

module "project" {
  source  = "terraform-google-modules/project-factory/google"
  ...
}

output "project_id" {
  value       = module.project.project_id
  description = "The ID of the created project"
}

Outros ambientes e aplicativos do Terraform podem se referir apenas às saídas raiz no módulo.

Ao usar o estado remoto, é possível referenciar as saídas do módulo raiz. Para permitir o uso por outros aplicativos dependentes para configuração, verifique se você está exportando informações relacionadas aos endpoints de um serviço para o estado remoto.

# Networks root module
data "terraform_remote_state" "network_project" {
  backend = "gcs"

  config = {
    bucket = "BUCKET"
  }
}

module "vpc" {
  source  = "terraform-google-modules/network/google"
  version = "~> 9.0"

  project_id   = data.terraform_remote_state.network_project.outputs.project_id
  network_name = "vpc-1"
  ...
}

Às vezes, por exemplo, ao invocar um módulo de serviço compartilhado a partir de diretórios de ambiente, é adequado exportar novamente todo o módulo filho, da seguinte maneira:

output "service" {
  value       = module.service
  description = "The service module outputs"
}

Fixar em versões secundárias do provedor

Nos módulos raiz, declare cada provedor e fixe em uma versão secundária. Isso permite o upgrade automático para novas versões de patch, mantendo um destino sólido. Para consistência, nomeie o arquivo como versions.tf.

terraform {
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "~> 4.0.0"
    }
  }
}

Armazenar variáveis em um arquivo tfvars

Para módulos raiz, forneça variáveis usando um arquivo de variáveis .tfvars. Para consistência, nomeie arquivos de variável como terraform.tfvars.

Não especifique variáveis usando opções de linha de comando alternativas var-files ou var='key=val'. As opções da linha de comando são temporárias e fáceis de esquecer. Usar um arquivo de variáveis padrão é mais previsível.

Conferir o arquivo .terraform.lock.hcl

Para módulos raiz, o arquivo de bloqueio de dependência .terraform.lock.hcl precisa ser verificado no controle de origem. Isso permite rastrear e analisar mudanças em seleções de provedores para uma determinada configuração.

Comunicação entre configurações

Um problema comum que ocorre ao usar o Terraform é como compartilhar informações em diferentes configurações do Terraform (possivelmente mantidas por equipes diferentes). Geralmente, as informações podem ser compartilhadas entre configurações, sem exigir que sejam armazenadas em um único diretório (ou até mesmo em um único repositório).

A maneira recomendada de compartilhar informações entre diferentes configurações do Terraform é usar o estado remoto para referenciar outros módulos raiz. O Cloud Storage ou o Terraform Enterprise são os back-ends de estado preferidos.

Para consultar recursos não gerenciados pelo Terraform, use fontes de dados do provedor do Google. Por exemplo, a conta de serviço padrão do Compute Engine pode ser recuperada usando uma fonte de dados. Não use fontes de dados para consultar recursos gerenciados por outra configuração do Terraform. Isso pode criar dependências implícitas em nomes e estruturas de recursos que as operações normais do Terraform podem interromper acidentalmente.

Como trabalhar com recursos do Google Cloud

As práticas recomendadas para provisionar recursos do Google Cloud com o Terraform são integradas aos módulos do Cloud Foundation Toolkit que o Google mantém. Esta seção reforça algumas dessas práticas recomendadas.

Definir imagens da máquina virtual

Em geral, recomendamos que você defina imagens de máquinas virtuais usando uma ferramenta como o Packer. O Terraform só precisará iniciar máquinas usando as imagens predefinidas.

Se imagens predefinidas não estiverem disponíveis, o Terraform poderá transferir novas máquinas virtuais para uma ferramenta de gerenciamento de configuração com um bloco provisioner. Recomendamos evitar esse método e usá-lo apenas como último recurso. Para limpar o estado antigo associado à instância, os provisionadores que exigem lógica de desmontagem precisam usar um bloco provisioner com when = destroy.

O Terraform deve fornecer informações de configuração da VM para o gerenciamento de configuração com metadados de instância.

Gerenciar o Identity and Access Management

Ao provisionar associações do IAM com o Terraform, vários recursos diferentes estão disponíveis:

  • google_*_iam_policy (por exemplo, google_project_iam_policy)
  • google_*_iam_binding (por exemplo, google_project_iam_binding)
  • google_*_iam_member (por exemplo, google_project_iam_member)

google_*_iam_policy e google_*_iam_binding criam associações autoritativas de IAM, em que os recursos do Terraform servem como a única fonte da verdade para quais permissões podem ser atribuídas ao recurso relevante.

Se as permissões mudarem fora do Terraform, ele substitui todas as permissões para representar a política, conforme definido na sua configuração. Isso pode ser útil para recursos totalmente gerenciados por uma configuração específica do Terraform. No entanto, isso significa que os papéis gerenciados automaticamente pelo Google Cloud são removidos. Isso pode interromper a funcionalidade de alguns serviços.

Para evitar isso, recomendamos o uso de recursos google_*_iam_member diretamente ou do módulo do IAM no Google.

Controle de versões

Assim como em outras formas de código, armazene o código da infraestrutura no controle de versões para preservar o histórico e permitir reversões fáceis.

Usar uma estratégia de ramificação padrão

Para todos os repositórios que contêm o código do Terraform, use a seguinte estratégia por padrão:

  • main é a ramificação de desenvolvimento principal e representa o código aprovado mais recente. A ramificação main é protegida.
  • O desenvolvimento ocorre em ramificações de recursos e correções de bugs que se ramificam da ramificação main.
    • Nomeie as ramificações de recursos feature/$feature_name.
    • Nomeie as ramificações de correção de bugs fix/$bugfix_name.
  • Quando um recurso ou uma correção de bug for concluído, mescle-o novamente na ramificação main com uma solicitação de envio.
  • Para evitar conflitos, realoque as ramificações antes de mesclá-las.

Use ramificações do ambiente para configurações raiz

Para repositórios que incluem configurações raiz implantadas diretamente no Google Cloud, uma estratégia de lançamento segura é necessária. Recomendamos que você tenha uma ramificação separada para cada ambiente. Assim, as alterações na configuração do Terraform podem ser promovidas com a combinação de alterações entre as diferentes ramificações.

Ramificação separada para cada ambiente

Permitir ampla visibilidade

Torne o código-fonte e os repositórios do Terraform amplamente visíveis e acessíveis em todas as organizações de engenharia para os proprietários da infraestrutura (por exemplo, SREs) e as partes interessadas da infraestrutura (por exemplo, desenvolvedores). Isso garante que as partes interessadas na infraestrutura possam entender melhor a infraestrutura de que dependem.

Incentivar as partes interessadas da infraestrutura a enviar solicitações de integração como parte do processo de solicitação de mudança.

Nunca confirme secrets

Nunca confirme secrets para controle de origem, incluindo na configuração do Terraform. Em vez disso, faça o upload deles para um sistema como o Secret Manager e referencie-os usando fontes de dados.

Lembre-se de que esses valores confidenciais ainda podem acabar no arquivo de estado e também ser expostos como saídas.

Organizar repositórios de acordo com os limites das equipes

Embora seja possível usar diretórios separados para gerenciar limites lógicos entre recursos, os limites organizacionais e a logística determinam a estrutura do repositório. Em geral, use o princípio de design no qual configurações com diferentes requisitos de aprovação e gerenciamento são separados em diferentes repositórios de controle de origem. Para ilustrar esse princípio, veja algumas configurações de repositório possíveis:

  • Um repositório central: neste modelo, todo o código do Terraform é gerenciado centralmente por uma única equipe de plataforma. Esse modelo funciona melhor quando há uma equipe de infraestrutura dedicada responsável por todo o gerenciamento de nuvem e aprova as alterações solicitadas por outras equipes.

  • Repositórios de equipe:nesse modelo, cada equipe é responsável pelo próprio repositório do Terraform, em que ela gerencia tudo relacionado à infraestrutura própria. Por exemplo, a equipe de segurança pode ter um repositório em que todos os controles de segurança são gerenciados, e as equipes de aplicativos têm o próprio repositório do Terraform para implantar e gerenciar o aplicativo.

    Organizar repositórios nos limites da equipe é a melhor estrutura para a maioria dos cenários corporativos.

  • Repositórios desacoplados: neste modelo, cada componente lógico do Terraform é dividido em um repositório próprio. Por exemplo, a rede pode ter um repositório dedicado e pode haver um repositório de fábrica de projetos separado para criação e gerenciamento de projetos. Isso funciona melhor em ambientes altamente descentralizados em que as responsabilidades alternam com frequência entre as equipes.

Exemplo de estrutura do repositório

É possível combinar esses princípios para dividir a configuração do Terraform em diferentes tipos de repositório:

  • Básico
  • Específico para a equipe e o app
Repositório básico

Um repositório fundamental que contém os principais componentes central, como pastas ou IAM da organização. Esse repositório pode ser gerenciado pela equipe central do Cloud.

  • Nesse repositório, inclua um diretório para cada componente principal (por exemplo, pastas, redes e assim por diante).
  • Nos diretórios de componentes, inclua uma pasta separada para cada ambiente (refletindo a orientação da estrutura de diretórios mencionada anteriormente).

Estrutura do repositório básico

Repositórios específicos de aplicativos e equipes

Implante repositórios de aplicativos específicos e de equipe separadamente para que cada equipe gerencie a configuração exclusiva do Terraform específica para cada aplicativo.

Estrutura de repositório específica de aplicativos e equipes

Operações

Manter sua infraestrutura segura depende de um processo estável e seguro para aplicar as atualizações do Terraform.

Sempre planeje primeiro

Sempre gere um plano para as execuções do Terraform. Salve o plano em um arquivo de saída. Depois que um proprietário da infraestrutura aprová-la, execute o plano. Mesmo quando os desenvolvedores fazem a prototipagem de mudanças localmente, eles precisam gerar um plano e revisar os recursos a serem adicionados, modificados e destruídos antes de aplicar o plano.

Implementar um pipeline automatizado

Para garantir um contexto de execução consistente, execute o Terraform com ferramentas automatizadas. Se um sistema de compilação (como o Jenkins) já estiver em uso e amplamente adotado, use-o para executar os comandos terraform plan e terraform apply automaticamente. Se não houver um sistema disponível, adote o Cloud Build ou o Terraform Cloud.

Usar credenciais de conta de serviço para integração contínua

Quando o Terraform é executado em uma máquina em um pipeline de CI/CD, ele precisa herdar as credenciais da conta de serviço do serviço que executa o pipeline. Sempre que possível, execute pipelines de CI no Google Cloud porque o Cloud Build, o Google Kubernetes Engine ou o Compute Engine injetam credenciais sem fazer o download de chaves da conta de serviço.

Para pipelines executados fora do Google Cloud, prefira a federação de identidade da carga de trabalho para receber credenciais sem fazer o download de chaves da conta de serviço.

Evitar importar recursos atuais

Sempre que possível, evite importar recursos existentes, usando (terraform import), porque isso pode dificultar a compreensão da procedência e a configuração dos recursos criados manualmente. Em vez disso, crie novos recursos por meio do Terraform e exclua os recursos antigos.

Nos casos em que a exclusão de recursos antigos criaria tarefas repetitivas, use o comando terraform import com aprovação explícita. Depois que um recurso for importado para o Terraform, gerencie-o exclusivamente com o Terraform.

O Google oferece uma ferramenta que pode ser usada para importar seus recursos do Google Cloud para o estado do Terraform. Para mais informações, consulte Importar recursos do Google Cloud para o estado do Terraform.

Não modificar o estado do Terraform manualmente

O arquivo de estado do Terraform é fundamental para manter o mapeamento entre a configuração do Terraform e os recursos do Google Cloud. A corrupção pode levar a grandes problemas de infraestrutura. Quando modificações no estado do Terraform forem necessárias, use o comando terraform state.

Revisar regularmente os PINs de versão

Fixar versões garante a estabilidade, mas impede que correções de bugs e outras melhorias sejam incorporadas à sua configuração. Por isso, verifique regularmente os pinos de versão do Terraform, dos provedores e dos módulos do Terraform.

Para automatizar esse processo, use uma ferramenta como o Dependabot.

Usar credenciais padrão do aplicativo para executar localmente

Quando os desenvolvedores estão iterando localmente na configuração do Terraform, eles precisam se autenticar executando gcloud auth application-default login para gerar credenciais padrão do aplicativo. Não faça o download de chaves de conta de serviço, porque elas são mais difíceis de gerenciar e proteger.

Definir aliases para o Terraform

Para facilitar o desenvolvimento local, adicione aliases ao seu perfil do shell de comando:

  • alias tf="terraform"
  • alias terrafrom="terraform"

Segurança

O Terraform requer acesso confidencial à infraestrutura de nuvem para funcionar. Seguir estas práticas recomendadas de segurança ajuda a minimizar os riscos associados e melhorar a segurança geral da nuvem.

Usar estado remoto

Para clientes do Google Cloud, recomendamos o uso do back-end do estado do Cloud Storage. Essa abordagem bloqueia o estado para permitir a colaboração em equipe. Ele também separa o estado e todas as informações potencialmente confidenciais do controle de versões.

Certifique-se de que apenas o sistema de compilação e os administradores altamente privilegiados possam acessar o bucket usado para estado remoto.

Para evitar a confirmação acidental do estado de desenvolvimento para o controle de origem, use gitignore nos arquivos de estado do Terraform.

Criptografe o estado

Os buckets do Google Cloud são criptografados em repouso, mas é possível usar chaves de criptografia fornecidas pelo cliente para fornecer uma camada extra de proteção. Para isso, use a variável de ambiente GOOGLE_ENCRYPTION_KEY. Mesmo que nenhum secret esteja no arquivo de estado, sempre criptografe o estado como uma medida adicional de defesa.

Não armazenar secrets no estado

Há muitos recursos e provedores de dados no Terraform que armazenam valores de secret em texto simples no arquivo de estado. Sempre que possível, evite armazenar secrets no estado. Veja a seguir alguns exemplos de provedores que armazenam secrets em texto simples:

Marcar saídas confidenciais

Em vez de tentar criptografar valores confidenciais manualmente, use o suporte integrado do Terraform para gerenciamento de estados confidenciais. Ao exportar valores confidenciais para a saída, verifique se eles estão marcados como confidenciais.

Separe as tarefas

Se não for possível executar o Terraform a partir de um sistema automatizado a que nenhum usuário tenha acesso, separe as permissões e os diretórios separando as tarefas. Por exemplo, um projeto de rede corresponde a uma conta de serviço do Terraform ou a um usuário com acesso limitado a esse projeto.

Execute verificações de pré-aplicação

Ao executar o Terraform em um pipeline automatizado, use uma ferramenta como gcloud terraform vet para verificar a saída do plano em relação às políticas antes de aplicá-la. Isso pode detectar regressões de segurança antes que elas aconteçam.

Execute auditorias contínuas

Depois que o comando terraform apply for executado, execute verificações automáticas de segurança. Essas verificações podem ajudar a garantir que a infraestrutura não se desloque para um estado não seguro. As seguintes ferramentas são opções válidas para esse tipo de verificação:

Teste

Às vezes, testar módulos e configurações do Terraform segue padrões e convenções diferentes do teste de código do aplicativo. Embora o teste do código do aplicativo envolva principalmente o teste da lógica de negócios dos próprios aplicativos, o teste completo do código da infraestrutura requer a implantação de recursos reais da nuvem para minimizar o risco de falhas de produção. Há algumas considerações ao executar testes do Terraform:

  • A execução de um teste do Terraform cria, modifica e destrói a infraestrutura real. Portanto, os testes podem ser demorados e caros.
  • Não é possível realizar somente um teste de unidade em uma arquitetura de ponta a ponta. A melhor abordagem é dividir a arquitetura em módulos e testá-los individualmente. Os benefícios dessa abordagem incluem desenvolvimento iterativo mais rápido devido ao tempo de execução mais rápido, custos reduzidos para cada teste e chances reduzidas de falhas de teste devido a fatores além do seu controle.
  • Evite reutilizar o estado, se possível. Pode haver situações em que você está fazendo testes com configurações que compartilham dados com outras configurações, mas o ideal é que cada teste seja independente e não reutilize o estado deles.

Use métodos de teste mais baratos primeiro

Há vários métodos que você pode usar para testar o Terraform. Em ordem crescente de custo, tempo de execução e profundidade, eles incluem o seguinte:

  • Análise estática:teste a sintaxe e a estrutura da configuração sem implantar recursos usando ferramentas como compiladores, lints e simulações. Para fazer isso, use terraform validate.
  • Teste de integração do módulo: para garantir que os módulos funcionem corretamente, teste os módulos individuais isoladamente. O teste de integração de módulos envolve a implantação do módulo em um ambiente de teste e a verificação de que os recursos esperados foram criados. Há vários frameworks de teste que facilitam a criação de testes da seguinte maneira:
  • Teste de ponta a ponta:ao estender a abordagem de teste de integração a um ambiente inteiro, você pode confirmar se vários módulos funcionam juntos. Nessa abordagem, implante todos os módulos que compõem a arquitetura em um ambiente de teste atualizado. O ideal é que o ambiente de teste seja o mais semelhante possível ao ambiente de produção. Isso é caro, mas oferece a maior confiança de que as alterações não afetam seu ambiente de produção.

Vá aos poucos.

Certifique-se de que seus testes são criados iterativamente uns nos outros. Recomendamos executar testes menores primeiro e, em seguida, passar para testes mais complexos usando uma abordagem falha rápida.

Ordem aleatória de IDs de projetos e nomes de recursos

Para evitar conflitos de nomenclatura, verifique se as configurações têm um ID do projeto globalmente exclusivo e nomes de recursos não sobrepostos em cada projeto. Para fazer isso, use namespaces nos seus recursos. O Terraform tem um provedor aleatório integrado para isso.

Usar um ambiente separado para testar

Durante os testes, muitos recursos são criados e excluídos. Verifique se o ambiente está isolado dos projetos de desenvolvimento ou produção para evitar exclusões acidentais durante a limpeza de recursos. A melhor abordagem é fazer com que cada teste crie um novo projeto ou pasta. Para evitar configurações incorretas, considere criar contas de serviço especificamente para cada execução de teste.

Limpar todos os recursos

O teste do código da infraestrutura significa que você está implantando recursos reais. Para evitar cobranças, implemente uma etapa de limpeza.

Para destruir todos os objetos remotos gerenciados por uma configuração específica, use o comando terraform destroy. Alguns frameworks de teste têm uma etapa de limpeza integrada para você. Por exemplo, se você estiver usando o Terratest, adicione defer terraform.Destroy(t, terraformOptions) ao teste. Se você estiver usando Kitchen-Terraform, exclua seu espaço de trabalho usando terraform kitchen delete WORKSPACE_NAME.

Depois de executar o comando terraform destroy, execute também procedimentos de limpeza adicionais para remover todos os recursos que o Terraform não conseguiu destruir. Para fazer isso, exclua todos os projetos usados para execução do teste ou use uma ferramenta como o módulo project_cleanup.

Otimizar o tempo de execução do teste

Para otimizar o tempo de execução do teste, use as seguintes abordagens:

  • Executar testes em paralelo. Alguns frameworks de teste são compatíveis com a execução de vários testes do Terraform simultaneamente.
    • Por exemplo, com o Terratest, é possível fazer isso adicionando t.Parallel() após a definição da função de teste.
  • Teste em etapas. Separe seus testes em configurações independentes que possam ser testadas separadamente. Essa abordagem elimina a necessidade de passar por todos os estágios ao executar um teste e acelera o ciclo de desenvolvimento iterativo.
    • Por exemplo, no Kitchen-Terraform, divida testes em pacotes separados. Ao iterar, execute cada pacote de forma independente.
    • Da mesma forma, usando o Terratest, envolva cada estágio do teste com stage(t, STAGE_NAME, CORRESPONDING_TESTFUNCTION). Defina variáveis de ambiente que indicam quais testes serão executados. Exemplo, SKIPSTAGE_NAME="true".
    • O framework de teste de blueprint é compatível com a execução gradual.

A seguir