Prácticas recomendadas para gestionar dependencias

En este documento se ofrecen recomendaciones para expresar las dependencias entre recursos en la configuración de Terraform.

Prioriza las dependencias implícitas sobre las explícitas

Las dependencias de recursos se producen cuando un recurso depende de la existencia de otros recursos. Terraform debe poder entender estas dependencias para asegurarse de que los recursos se crean en el orden correcto. Por ejemplo, si el recurso A tiene una dependencia del recurso B, el recurso B se crea antes que el recurso A.

Las dependencias de configuración de Terraform se pueden establecer mediante declaraciones de dependencias implícitas y explícitas. Las dependencias implícitas se declaran mediante referencias de expresiones, mientras que las dependencias explícitas se especifican mediante el metaargumento depends_on. El argumento depends_on especifica que Terraform debe completar todas las acciones de los objetos de los que depende un recurso o un módulo antes de continuar con el objeto dependiente.

Aunque ambos enfoques aseguran un orden correcto de las operaciones, las dependencias implícitas suelen dar lugar a una mayor eficiencia en la planificación de las actualizaciones y la sustitución de recursos. Esto se debe a que Terraform puede hacer un seguimiento inteligente de los campos específicos implicados en una dependencia implícita, lo que puede evitar cambios en el recurso dependiente si esos campos específicos no se modifican en la dependencia.

En comparación con las dependencias implícitas, las explícitas proporcionan menos información específica. Esto significa que Terraform solo puede formular planes más conservadores para la creación, las actualizaciones y la sustitución de recursos si no conoce los atributos concretos que constituyen la dependencia. En la práctica, esto afecta a la secuencia en la que Terraform crea los recursos y a cómo determina si los recursos requieren actualizaciones o sustituciones.

Recomendamos usar dependencias explícitas con el metaargumento depends_on solo como último recurso cuando una dependencia entre dos recursos esté oculta y no se pueda expresar mediante dependencias implícitas.

En el siguiente ejemplo, los servicios de proyecto necesarios deben habilitarse antes de crear un conjunto de datos de BigQuery. Esta dependencia se declara explícitamente:

No 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
}

En el siguiente ejemplo, se sustituye la dependencia explícita por una implícita haciendo referencia al argumento project_id como atributo de salida project_id del 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
}

El uso de dependencias implícitas permite declarar dependencias de forma precisa, como especificar la información exacta que se debe recoger de un objeto upstream. También reduce la necesidad de hacer cambios en varios lugares, lo que a su vez disminuye el riesgo de errores.

Hacer referencia a atributos de salida de recursos dependientes

Cuando crees dependencias implícitas haciendo referencia a valores de recursos anteriores, asegúrate de hacer referencia solo a atributos de salida, en concreto, a valores que aún no se conocen. De esta forma, Terraform esperará a que se creen los recursos upstream antes de aprovisionar el recurso actual.

En el ejemplo siguiente, el recurso google_storage_bucket_object hace referencia al argumento name del recurso google_storage_bucket. Los argumentos tienen valores conocidos durante la fase de planificación de Terraform. Esto significa que, cuando Terraform crea el recurso google_storage_bucket_object, no espera a que se cree el recurso google_storage_bucket, ya que hacer referencia a un argumento conocido (el nombre del contenedor) no crea una dependencia implícita entre google_storage_bucket_object y google_storage_bucket. De esta forma, se anula el propósito de la declaración de dependencia implícita entre los dos recursos.

No 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
}

En su lugar, el recurso google_storage_bucket_object debe hacer referencia al atributo id del recurso google_storage_bucket_object. Como el campo id es un atributo de salida, su valor solo se asigna después de que se haya ejecutado la creación de su recurso. Por lo tanto, Terraform esperará a que se complete la creación del recurso google_storage_bucket_object antes de empezar a crear el 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
}

A veces, no hay ningún atributo de salida obvio al que hacer referencia. Por ejemplo, considera el siguiente ejemplo en el que module_a toma el nombre del archivo generado como entrada. Dentro de module_a, el nombre de archivo se usa para leer el archivo. Si ejecutas este código tal cual, obtendrás una excepción no such file or directory, que se debe a que Terraform intenta leer el archivo durante la fase de planificación, momento en el que el archivo aún no se ha creado. En este caso, un análisis del atributo de salida del recurso local_file revela que no hay campos obvios que puedas usar en lugar del argumento de entrada filename.

No 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
}

Para solucionar este problema, introduce una dependencia explícita. Como práctica recomendada, añade un comentario sobre por qué es necesaria la dependencia explícita:

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
}

Siguientes pasos