依附元件管理的最佳做法

本文提供 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 資源的名稱引數。在 Terraform 規劃階段,引數會有已知的值。也就是說,當 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 資源必須參照 google_storage_bucket_object 資源的 id 輸出屬性。由於 id 欄位是輸出屬性,因此只有在執行資源建立作業後,系統才會設定其值。因此,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
}

後續步驟