Práticas recomendadas sobre a gestão de dependências

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

Privilegie as dependências implícitas em detrimento das dependências explícitas

As dependências de recursos surgem quando um recurso depende da existência de outros recursos. O Terraform tem de conseguir compreender estas dependências para garantir que os recursos são criados pela ordem correta. Por exemplo, se o recurso A tiver uma dependência do recurso B, o recurso B é criado antes do recurso A.

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

Embora ambas as abordagens garantam uma ordem correta das operações, as dependências implícitas levam frequentemente a uma maior eficiência no planeamento das atualizações e da substituição de recursos. Isto acontece porque o Terraform pode monitorizar de forma inteligente os campos específicos envolvidos numa dependência implícita, evitando potencialmente alterações ao recurso dependente se esses campos específicos permanecerem inalterados na 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 a criação, as atualizações e a 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 a forma como o Terraform determina se os recursos requerem atualizações ou substituições.

Recomendamos a utilização de dependências explícitas com o argumento meta depends_on apenas como último recurso quando uma dependência entre dois recursos está oculta e não pode ser expressa através de dependências implícitas.

No exemplo seguinte, os serviços de projeto necessários têm de 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 seguinte substitui a dependência explícita por uma dependência implícita ao referenciar o argumento project_id como o atributo de saída project_id do recurso project_services:

Recomendado:

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
}

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

Atributos de saída de referência de recursos dependentes

Quando cria dependências implícitas referenciando valores de recursos a montante, certifique-se de que referencia apenas atributos de saída, especificamente valores que ainda não são conhecidos. Isto garante que o Terraform aguarda a criação dos recursos a montante antes de aprovisionar o recurso atual.

No exemplo seguinte, o recurso google_storage_bucket_object faz referência ao argumento name do recurso google_storage_bucket. Os argumentos têm valores conhecidos durante a fase de planeamento do Terraform. Isto significa que, quando o Terraform cria o recurso google_storage_bucket_object, não aguarda que o recurso google_storage_bucket seja criado, porque a referência a um argumento conhecido (o nome do contentor) não cria uma dependência implícita entre o google_storage_bucket_object e o google_storage_bucket. Isto anula o objetivo 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 alternativa, o recurso google_storage_bucket_object tem de fazer referência ao atributo de saída do recurso google_storage_bucket_object.id Uma vez que o campo id é um atributo de saída, o respetivo valor só é definido após a execução da criação do respetivo recurso. Por conseguinte, o Terraform aguarda a conclusão da criação do recurso google_storage_bucket_object antes de iniciar a criação do recurso google_storage_bucket_object.

Recomendado:

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

Por vezes, não existe um atributo de saída óbvio ao qual fazer referência. Por exemplo, considere o seguinte exemplo em que module_a usa o nome do ficheiro gerado como entrada. Dentro de module_a, o nome do ficheiro é usado para ler o ficheiro. Se executar este código tal como está, recebe uma exceção no such file or directory, que é causada pelo Terraform a tentar ler o ficheiro durante a fase de planeamento, altura em que o ficheiro ainda não foi criado. Neste caso, uma análise do atributo de saída do recurso local_file revela que não existem campos óbvios que possa usar em vez do argumento de entrada do nome do ficheiro.

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
}

Pode resolver este problema introduzindo uma dependência explícita. Como prática recomendada, certifique-se de que adiciona um comentário sobre o motivo pelo qual a dependência explícita é necessária:

Recomendado:

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
}

O que se segue?