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.
Práticas recomendadas:
Siga uma estrutura de módulos padrão.Adote uma convenção de nomenclatura.
Use as variáveis com cuidado.
Exponha as saídas.
Use fontes de dados.
Limitar o uso de scripts personalizados
Inclua scripts auxiliares em um diretório separado.
Coloque os arquivos estáticos em um diretório separado.
Proteja os recursos com estado.
Usar a formatação integrada.
Limitar a complexidade das expressões.
Use
count
para valores condicionais.Use
for_each
para recursos iterados.Publique módulos em um registro.
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 arquivoREADME.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 arquivoREADME.md
detalhado. - Crie agrupamentos lógicos de recursos com os próprios arquivos e
nomes descritivos, como
network.tf
,instances.tf
ouloadbalancer.tf
.- Evite dar a cada recurso o próprio arquivo. Agrupe recursos
por finalidade compartilhada. Por exemplo, combine
google_dns_managed_zone
egoogle_dns_record_set
emdns.tf
.
- Evite dar a cada recurso o próprio arquivo. Agrupe recursos
por finalidade compartilhada. Por exemplo, combine
- No diretório raiz do módulo, inclua apenas o Terraform
(
*.tf
) e os arquivos de metadados do repositório (comoREADME.md
eCHANGELOG.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 desome_google_resource.main.id
.
- É preciso pensar mais para
se lembrar de
Para diferenciar os recursos de um mesmo tipo (por exemplo,
primary
esecondary
), 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
).
- 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
- 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/
.
- Os modelos precisam ser colocados em um diretório
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: publique módulos reutilizáveis em um registro de módulos.
Módulos de código aberto: publique módulos de código aberto no Registro do Terraform.
Módulos privados: publique módulos particulares em um registro particular.
Módulos reutilizáveis
Para módulos destinados à reutilização, use as diretrizes a seguir e as anteriores.
Práticas recomendadas:
Ative as APIs necessárias em módulos.Inclua um arquivo de proprietários.
Liberar versões marcadas.
Não configurar provedores ou back-ends.
Exponha rótulos como uma variável.
Exponha as saídas para todos os recursos.
Usar submódulos in-line para lógica complexa.
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 temtrue
como padrão. Se a ativação da API estiver incluída em um módulo, ela precisa definir
disable_services_on_destroy
comofalse
, 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.
Práticas recomendadas:
Minimizar o número de recursos em cada módulo raiz.Use diretórios diferentes para cada app.
Divida aplicativos em subdiretórios específicos do ambiente.
Use diretórios de ambiente.
Expor saídas por estado remoto.
Fixe-se em versões secundárias do provedor.
Armazenar variáveis em um
tfvars
arquivo.Faça o check-in do arquivo
.terraform.lock.hcl
.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.
Práticas recomendadas:
Provisionar instâncias de máquina virtual.Gerenciar o Identity and Access Management.
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.
Práticas recomendadas:
Usar uma estratégia de ramificação padrão.Usar ramificações do ambiente para as configurações raiz.
Permitir ampla visibilidade.
Nunca confirme secrets.
Organize os repositórios de acordo com os limites das equipes.
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çãomain
é 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
.
- Nomeie as ramificações de recursos
- 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.
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).
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.
Operações
Manter sua infraestrutura segura depende de um processo estável e seguro para aplicar as atualizações do Terraform.
Práticas recomendadas:
Sempre planeje primeiro.Implemente um pipeline automatizado.
Use as credenciais da conta de serviço para CI.
Evite importar recursos.
Não modifique o estado do Terraform manualmente.
Consulte regularmente os PINs de versão.
Usar o Application Default Credentials ao executar localmente.
Definir aliases para o 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.
Práticas recomendadas:
Use o estado remoto.Estado de criptografia.
Não armazene secrets no estado.
Marque saídas confidenciais.
Separe os deveres.
Executar verificações de pré-aplicação.
Executar auditorias contínuas.
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.
Práticas recomendadas:
Use os métodos de teste mais baratos primeiro.Vá aos poucos.
Ordene aleatoriamente os IDs dos projetos e nomes de recursos.
Use um ambiente separado para testar.
Limpe todos os recursos.
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.
- Por exemplo, com o Terratest, é possível fazer isso adicionando
- 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,SKIP
STAGE_NAME="true"
. - O framework de teste de blueprint é compatível com a execução gradual.
A seguir
- Armazenar o estado do Terraform em um bucket do Cloud Storage
- Gerenciar a infraestrutura como código com o Terraform, o Cloud Build e o GitOps