依赖项管理最佳实践

本文档提供了有关表达 Terraform 配置中资源之间依赖关系的建议。

建议使用隐式依赖项,而非显式依赖项

当一种资源依赖于其他资源的存在时,就会出现资源依赖性。Terraform 必须能够了解这些依赖项,以确保以正确的顺序创建资源。例如,如果资源 A 对资源 B 有依赖性,则资源 B 会在资源 A 之前创建。

Terraform 配置依赖项可以通过 隐式和显式依赖项声明。 隐式依赖项通过 表达式引用、 显式依赖关系使用 depends_on 元参数。depends_on 参数指定 Terraform 必须先对资源或模块所依赖的对象执行所有操作,然后才能继续处理依赖对象。

虽然这两种方法都可以确保操作顺序正确,但隐式 通常可以提高更新规划和 替代资源。这是因为 Terraform 可以智能地跟踪隐式依赖项中涉及的特定字段,如果这些特定字段在依赖项中保持不变,则可能会避免对依赖资源进行更改。

与隐式依赖项相比,显式依赖项传达的信号较少 特定信息。这意味着,如果不知道构成依赖项的特定属性,Terraform 只能制定更保守的资源创建、更新和替换计划。实际上,这会影响 Terraform 创建资源的顺序,以及 Terraform 确定资源是否需要更新或替换的方式。

我们建议,只有在两个资源之间的依赖项被隐藏且无法通过隐式依赖项表达时,才将 depends_on 元参数与显式依赖项搭配使用。

在以下示例中,必须先启用所需的项目服务, 创建 BigQuery 数据集的步骤。此依赖项已明确声明:

不推荐:

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
}

以下示例通过将 project_id 参数作为 project_services 资源的 project_id 输出属性来将显式依赖项替换为隐式依赖项:

推荐:

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
}

使用隐式依赖项可精确声明依赖项,例如指定需要从上游对象收集的确切信息。这也有助于减少 从而降低出错风险。

引用依赖资源中的输出属性

通过引用上游资源中的值来创建隐式依赖项时,请务必仅引用输出属性,尤其是尚不为人所知的值。这将确保 Terraform 等待上游资源的创建完成 然后再预配当前资源。

在以下示例中,google_storage_bucket_object 资源引用了 google_storage_bucket 资源的 name 参数。参数已知 值。这意味着当 Terraform 创建 google_storage_bucket_object 资源时,它不会等待 google_storage_bucket 资源创建,因为引用已知参数(存储桶名称)不会在 google_storage_bucket_objectgoogle_storage_bucket 之间创建隐式依赖关系。这会击败 两个资源之间隐式依赖项声明的用途。

不推荐:

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

google_storage_bucket_object 资源必须引用 id output 属性。google_storage_bucket_objectid 字段是输出属性,其值仅在创建 资源状态因此,Terraform 会等待系统创建 然后才能开始google_storage_bucket_object google_storage_bucket_object 资源的创建操作。

推荐:

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

有时,没有明显的输出属性可供引用。例如,请考虑以下示例,其中 module_a 采用生成的文件的名称作为输入。在 module_a 中,文件名用于读取文件。如果按原样运行此代码,您将收到 no such file or directory 异常,这是由于 Terraform 在其规划阶段尝试读取该文件而导致的,此时该文件尚未创建。在这种情况下,检查 local_file 资源的输出属性会发现,没有明显的字段可用于替换文件名输入参数。

不推荐:

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
}

您可以通过引入显式依赖项来解决此问题。最佳 练习时,请务必添加评论,说明为何需要该显式依赖项:

推荐:

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
}

后续步骤