Melhores práticas em gerenciamento de dependências

Este documento fornece recomendações para expressar dependências entre recursos na configuração do Terraform.

Favorecer dependências implícitas em vez de dependências explícitas

As dependências de recursos surgem quando um recurso depende da existência de outros recursos. O Terraform deve ser capaz de compreender essas dependências para garantir que os recursos sejam criados na ordem correta. Por exemplo, se o recurso A depende do recurso B, o recurso B é criado antes do recurso A.

As dependências de configuração do Terraform podem ser estabelecidas por meio de declarações de dependência implícitas e explícitas. As dependências implícitas são declaradas por meio de referências de expressão, enquanto as dependências explícitas são especificadas usando o meta-argumento depends_on. O argumento depends_on especifica que o Terraform precisa concluir todas as ações nos objetos dos quais um recurso ou módulo depende antes de prosseguir com o objeto dependente.

Embora ambas as abordagens garantam uma ordem correta de operações, as dependências implícitas geralmente levam a mais eficiência no planejamento de atualizações e substituição de recursos. Isso ocorre porque o Terraform pode rastrear de forma inteligente os campos específicos envolvidos em uma dependência implícita, evitando potencialmente alterações no recurso dependente se esses campos específicos permanecerem inalterados dentro da dependência.

Em comparação com as dependências implícitas, as dependências explícitas transmitem informações menos específicas. Isto significa que o Terraform só pode formular planos mais conservadores para criação, atualizações e substituição de recursos na ausência de conhecimento dos atributos específicos que constituem a dependência. Na prática, isto afeta a sequência em que os recursos são criados pelo Terraform e como o Terraform determina se os recursos requerem atualizações ou substituições.

Recomendamos o uso de dependências explícitas com o metaargumento depends_on apenas como último recurso quando uma dependência entre dois recursos está oculta e não pode ser expressa por meio de dependências implícitas.

No exemplo a seguir, os serviços de projeto necessários devem ser ativados antes de criar um conjunto de dados do BigQuery. Esta dependência é declarada explicitamente:

Não recomendado:

module "project_services" {
  source  = "terraform-google-modules/project-factory/google//modules/project_services"
  version = "~> 14.4"

  project_id = var.project_id
  activate_apis = [
    "bigquery.googleapis.com",
    "bigquerystorage.googleapis.com",
  ]
}

module "bigquery" {
  source       = "terraform-google-modules/bigquery/google"
  version      = "~> 5.4"

  dataset_id   = "demo_dataset"
  dataset_name = "demo_dataset"
  project_id   = var.project_id
  depends_on = [module.project_services] # <- explicit dependency
}

O exemplo a seguir substitui a dependência explícita por uma dependência implícita referenciando o argumento project_id como o atributo de saída project_id do recurso project_services:

Recomendação:

module "bigquery" {
  source       = "terraform-google-modules/bigquery/google"
  version      = "~> 5.4"

  dataset_id   = "demo_dataset"
  dataset_name = "demo_dataset"
  project_id   = module.project_services.project_id # <- implicit dependency
}

O uso de dependências implícitas permite declarações precisas de dependências, como especificar as informações exatas que precisam ser coletadas de um objeto upstream. Isto também reduz a necessidade de fazer alterações em vários locais, o que, por sua vez, reduz o risco de erros.

Referenciar atributos de saída de recursos dependentes

Ao criar dependências implícitas referenciando valores de recursos upstream, faça referência apenas aos atributos de saída, especificamente valores que ainda não são conhecidos. Isso garantirá que o Terraform aguarde a criação dos recursos upstream antes de provisionar o recurso atual.

No exemplo a seguir, o recurso google_storage_bucket_object faz referência ao argumento de nome do recurso google_storage_bucket. Os argumentos têm valores conhecidos durante a fase de plano do Terraform. Isso significa que quando o Terraform cria ogoogle_storage_bucket_object recurso, ele não espera pelogoogle_storage_bucket recurso a ser criado porque fazer referência a um argumento conhecido (o nome do intervalo) não cria uma dependência implícita entre o google_storage_bucket_object e a google_storage_bucket. Isto anula o propósito da declaração de dependência implícita entre os dois recursos.

Não recomendado:

# Cloud Storage bucket
resource "google_storage_bucket" "bucket" {
  name = "demo-bucket"
  location = "US"
}

resource "google_storage_bucket_object" "bucket_object" {
  name   = "demo-object"
  source = "./test.txt"
  bucket = google_storage_bucket.bucket.name # name is an input argument
}

Em vez disso, o recurso google_storage_bucket_object precisa fazer referência ao atributo de saída id do recurso google_storage_bucket_object. Como o campo id é um atributo de saída, o valor dele só é definido após a execução da criação do recurso. Portanto, o Terraform aguardará a conclusão da criação do recurso google_storage_bucket_object antes de iniciar a criação do recurso google_storage_bucket_object.

Recomendação:

resource "google_storage_bucket_object" "bucket_object" {
  name   = "demo-object"
  source = "./test.txt"
  bucket = google_storage_bucket.bucket.id # id is an output attribute
}

Às vezes não há nenhum atributo de saída óbvio para referência. Por exemplo, considere o exemplo a seguir, em que module_a usa o nome do arquivo gerado como entrada. Dentro de module_a, o nome do arquivo é usado para ler o arquivo. Se você executar esse código como está, receberá uma exceção no such file or directory, causada pela tentativa do Terraform de ler o arquivo durante a fase de planejamento, momento em que o arquivo ainda não foi criado. Nesse caso, uma análise do atributo de saída do recurso local_file revela que não há campos óbvios que possam ser usados no lugar do argumento de entrada do nome do arquivo.

Não recomendado:

resource "local_file" "generated_file" {
 filename = "./generated_file.text"
 content = templatefile("./template.tftpl", {
   project_id = var.project_id
 })
}

module "module_a" {
 source = "./modules/module-a"
 root_config_file_path = local_file.generated_file.filename
}

Você pode resolver esse problema introduzindo uma dependência explícita. Como prática recomendada, adicione um comentário sobre por que a dependência explícita é necessária:

Recomendação:

module "module_a" {
 source = "./modules/module-a"
 root_config_file_path = local_file.generated_file.filename
 depends_on = [local_file.generated_file] # waiting for generated_file to be created
}

A seguir