Skip to content
目前 Blog 頁籤支援折疊側邊欄功能,在閱讀文章會更方便,歡迎多加利用 💪
如何導入 Terragrunt,Terragrunt 好處是什麼?

如何導入 Terragrunt,Terragrunt 好處是什麼?

發布於 2023-06-21 / 最後更新於 2026-02-03

我們接續上一篇的 如何將 Terraform 改寫成 module ? ,我們已經將 Terraform 改成 module 的方式來進行管理,但當我們要管理的資源越來越多,且有分不同的專案時,整個服務架構會長的像以下:


      • main.tf
      • outputs.tf
      • variables.tf
          • backend.tf
          • main.tf
          • provider.tf
          • backend.tf
          • main.tf
          • provider.tf
          • backend.tf
          • main.tf
          • provider.tf
          • backend.tf
          • main.tf
          • provider.tf
          • backend.tf
          • main.tf
          • provider.tf
          • backend.tf
          • main.tf
          • provider.tf
          • backend.tf
          • main.tf
          • provider.tf
          • backend.tf
          • main.tf
          • provider.tf
          • backend.tf
          • main.tf
          • provider.tf

這邊的範例是以不同專案來分,再分區不同的服務,每個服務裡面都會有 backend.tfmain.tfprovider.tf 檔案所組成,我們可以來比較一下 gcp-1234 的 aaa 服務以及 gcp-2345 的 aaa 服務差異:


檔案差異
檔案差異

可以看到在 backend.tf 除了 prefix 路徑以外,其他設定也都一樣,但因為 Terraform 本身沒辦法透過帶入參數的方式來設定 backend.tf 後端部分,所以必須要先寫好每個服務所存放的後端位置,十分的不方便。

backend.tf
terraform {
  backend "gcs" {
    bucket      = "terragrunt-tfstate"
    prefix      = "/gcp-1234-aaa"
  }
}

為了減少上述這些需要一直重複寫差不多檔案的工作內容,因此有了 Terragrunt 這個工具,Terragrunt 是 Terraform 的包裝器,可以彌補 Terraform 上的一些缺陷,並且讓我們的 IaC 更貼近 DRY 原則


這邊說明一下 DRY 原則

DRY 全名是 Don’t repeat yourself,也就是不要做重複的事情,能夠一次做完的就不要重複的去做。


Terragrunt 安裝方式

那要怎麼使用 Terragrunt 呢!?

第一步當然是安裝它囉,我們系統是 macOS,所以我們安裝方式是 Homebrew 來進行安裝:

brew install terragrunt

接著我們的指令會從 terraform XXX 變成以下:

terragrunt plan
terragrunt apply
terragrunt output
terragrunt destroy

Terragrunt 會將所有命令、參數和選項直接轉發到 Terraform。(所以我們也需要下載 Terraform)

Terragrunt 的預設檔案名稱是 terragrunt.hcl ,Terragrunt 的設定檔案基本上與 Module 差不多,只是有更多更方便的變數可以使用。


Terragrunt 好處


接著介紹一下 Terragrunt 的好處:

  1. 方便管理後端狀態設定

  2. 將後端存儲桶納入管理

  3. 使用 generate 自動生成檔案

  4. 使用 include 檔案來達到 DRY 原則

  5. 管理 Module 之間的依賴性

  6. 產生依賴關聯圖


方便管理後端狀態設定


首先第一個方便管理後端狀態設定,也就是我們上面提到的 backend.tf 設定。在 Terraform 原生為了區別不同專案不同服務的狀態檔,就必須先寫好每個儲存的路徑,但使用 Terragrunt,可以先在該目錄下,也就是 gcp-3456 目錄下先寫一個設定檔案 (我們以 gcp-3456 專案為例),讓底下的 aaa、bbb、ccc 服務可以去 include 它,我們就不需要每個服務都寫幾乎差不多的設定檔,接著我們在 gcp-3456 資料夾下新增 terragrunt.hcl 檔案來說明:

terragrunt.hcl
remote_state {
  backend = "gcs"
  generate = {
    path      = "backend.tf"
    if_exists = "overwrite"
  }
  config = {
    bucket  = "terragrunt-tfstate"
    prefix = "${path_relative_to_include()}"
  }
}

這邊的設定其實跟之前的 backend.tf 差不多,只是後端現在儲存的 block 改叫做 remote_state,可以看到 backend 設定我們一樣是存在 gcs 上,generate 這個 block 它會判斷 backend.tf 檔案是否存在,如果沒有它就會幫我們建立,其中設定檔案內容是將狀態檔案存在 terragrunt-tfstate 這個 bucket,並透過${path_relative_to_include()} 這個變數來自動帶入有 include 這份檔案的路徑,並在相對路徑產生 backend.tf 檔案。


有點抽象,所以我畫一個比較簡單的架構圖來做說明一下,假設現在有三個服務,如下:

    • terragrunt.hcl
    • terragrunt.hcl
    • terragrunt.hcl
  • terragrunt.hcl

我們剛剛的後端設定是寫在此根目錄的 terragrunt.hcl 檔案(第 8 行),然後 ccc 這個服務去 include 根目錄的 terragrunt.hcl 檔案, Terragrunt 就會自動幫你產生以下的 backend.tf 檔案:

backend.tf
terraform {
  backend "gcs" {
    bucket      = "terragrunt-tfstate"
    prefix      = "/ccc"
  }
}

這樣就可以省下我們要重複寫 backend.tf 的時間,在維護上也會更加的方便。


將後端存儲桶納入管理

接著,大家有沒有想過,我們都已經使用 Terraform 來管理 IaC ,並把狀態檔案放到 gcs 上面來保存,但一開始還沒有用 Terraform 管理 gcs 的資源,我們還需要在設定 backend.tf 前,先手動去新增一個 gcs ,才能來存放 tfstate 狀態檔案呢?


因此在 Terragrunt remote_state 的 config 時,可以多設定 gcs 的 project id 以及 location,Terragrunt 在初始化後端時,會檢查是否有該 gcs bucket,如果沒有就會自動建立,設定檔如下:

terragrunt.hcl
remote_state {
  backend = "gcs"
  generate = {
    path      = "backend.tf"
    if_exists = "overwrite"
  }
  config = {
    project = "gcp-xxxxxx"
    location = "asia"
    bucket  = "terragrunt-tfstate"
    prefix = "${path_relative_to_include()}"
  }
}

2026/02/03 資訊更新
最近剛好在新公司建立 Terragrunt,發現之前 Terragrunt 會在 init 時,如果沒有 gcs bucket 會詢問是否需要建立,但現在不行了,需要多加 flag 才會觸發新增 gcs bucket 的動作,指令以及截圖如下:

terragrunt plan --backend-bootstrap

原本執行方式 & 新增 flag 後成功建立
原本執行方式 & 新增 flag 後成功建立

當前測試版本
當前測試版本

官方說明的文章連結:https://terragrunt.gruntwork.io/docs/migrate/cli-redesign/#use-the-new-backend-capabilities


使用 generate 自動生成檔案

在剛剛我們可以使用 generate 來自動 backend.tf 檔案,那代表我們也可以把每個 provider.tf 的內容,也透過 generate 來生成,如下:

terragrunt.hcl
generate "provider" {
  path = "provider.tf"
  if_exists = "overwrite"
  contents = <<EOF
terraform {
  required_providers {
    google = {
      source = "hashicorp/google"
      version = "~> 4.48.0"
    }
  }
}
EOF
}

這樣子每個 include 這份設定檔的服務除了 backend.tf 檔案以外,還會自動產生 provider.tf 檔案。


使用 include 檔案來達到 DRY 原則

我們搞定了 backend.tfprovider.tf 後,還剩下 main.tf,所以我們也將它改成 Terragrunt 的格式如下:

terragrunt.hcl
terraform {
  source = "${get_path_to_repo_root()}/modules/google_compute_instance"
}

include "root" {
  path = find_in_parent_folders()
}

inputs = {
  instance_name = "gcp-3456-ccc"
  machine_type  = "e2-small"
  instance_zone = "asia-east1-b"
  instance_tags = []
  instance_labels = {}
  boot_disk_auto_delete = true
  boot_disk_image_name  = "debian-cloud/debian-10"
  ... 設定部分省略 ...
}

這邊可以看到 terraform source 它就是我們使用 module 的路徑,也可以用 ${get_path_to_repo_root()} 這個變數他會自動抓該專案的根目錄,我們就不需要去特別設定。


此外也可以將 module 獨立成一個專案,或是使用其他人寫好的 module,在 source 的時候可以使用 git::https://[gitlab-網址]/sre/terraform/module.git//google_compute_address 的方式來取得 module,可以設定要使用哪個分支或是 tag,只需要在網址後面加上,?ref=[分之 or tag 名稱] 即可,這樣可以讓開發中的 module 不會影響到線上其他正在使用中的 module 設定。

(module.git 後面的 // 是 Terraform module source 的規則,如果不加會跳警告訊息)


接著我們可以看到 include "root" {} 這段,裡面有使用 find_in_parent_folders 這邊變數,他就是上面的提到會自動去抓放在父資料夾的 remote_stategenerate terragrunt.hcl 檔案。


後面的 input 就跟使用 module 時一樣,將 module 的參數帶入即可。

補充:所以我們也可以把一些通用的設定寫在根目錄的 terragrunt.hcl 檔案,例如專案的 id,可以寫以下內容來讓 include 它的檔案吃到同一個參數設定:

terragrunt.hcl
inputs = {
  project_id = "gcp-3456"
}

管理 Module 之間的依賴性

由於 Terragrunt 是把每個服務拆分成最小化,沒辦法把使用不同 module 的資源放在一起(單純使用 module 的話,可以一次 source 多了 module,並把他放在同一個 tf 檔案中),那像是我們建立 k8s 會使用到 google_container_clustergoogle_container_node_pool 兩種不同的 module 該怎麼辦呢?


首先我們先在 modules 資料夾放上 google_container_clustergoogle_container_node_pool 兩個 module 的設定檔案,詳細程式可以點我前往

      • main.tf
      • outputs.tf
      • variables.tf
      • main.tf
      • variables.tf

在 projects 底下新增 gke 資料夾,新增 terragrunt.hcl 來放 remote_state provider 的設定,並區分兩個資料夾,分別是 cluster 資料夾來存放 cluster 資訊,以及 test 資料夾來存放 test node-pool 資訊:

        • terragrunt.hcl
      • terragrunt.hcl
        • terragrunt.hcl

cluster 的 terragrunt.hcl 檔案如下:

terragrunt.hcl
terraform {
  source = "${get_path_to_repo_root()}/modules/google_container_cluster"
}

include {
  path = find_in_parent_folders()
}

inputs = {
  cluster_name             = "tf-test"
  cluster_location         = "asia-east1-b"
  node_locations           = []
  cluster_version          = "1.24.12-gke.500"
  network_name             = "projects/gcp-202011216-001/global/networks/bbin-testdev"
  subnetwork_name          = "projects/gcp-202011216-001/regions/asia-east1/subnetworks/bbin-testdev-dev-platform"
  node_max_pods            = 64
  remove_default_node_pool = true
  initial_node_count       = 1
  enable_shielded_nodes    = false
  resource_labels = {
    "dept" : "pid",
    "env" : "dev",
    "product" : "bbin"
  }
  dns_enabled                  = false
  cluster_dns                  = "PROVIDER_UNSPECIFIED"
  cluster_dns_scope            = "DNS_SCOPE_UNSPECIFIED"
  private_cluster_ipv4_cidr    = "172.16.0.176/28"
  binary_authorization_enabled = true
  binary_authorization         = "DISABLED"
}

test node-pool 檔案如下:

terragrunt.hcl
terraform {
  source = "${get_path_to_repo_root()}/modules/google_container_node_pool"
}

include {
  path = find_in_parent_folders()
}

dependency "cluster" {
  config_path = "../cluster"
}

inputs = {
  cluster_name      = dependency.cluster.outputs.cluster_name
  cluster_location  = dependency.cluster.outputs.cluster_location
  cluster_version   = dependency.cluster.outputs.cluster_version
  node_pool_name    = "test"
  node_count        = 1
  node_machine_type = "e2-small"
  node_disk_size    = 100
  node_disk_type    = "pd-standard"
  node_image_type   = "COS_CONTAINERD"
  node_oauth_scopes = [
    "https://www.googleapis.com/auth/devstorage.read_only",
    "https://www.googleapis.com/auth/logging.write",
    "https://www.googleapis.com/auth/monitoring",
    "https://www.googleapis.com/auth/service.management.readonly",
    "https://www.googleapis.com/auth/servicecontrol",
    "https://www.googleapis.com/auth/trace.append"
  ]
  node_tags                        = []
  node_taint_enabled               = false
  node_taint_key                   = ""
  node_taint_value                 = ""
  node_taint_effect                = ""
  auto_repair                      = true
  auto_upgrade                     = true
  upgrade_max_surge                = 1
  upgrade_max_unavailable          = 0
  upgrade_strategy                 = "SURGE"
  autoscaling_enabled              = true
  autoscaling_max_node_count       = 2
  autoscaling_min_node_count       = 1
  autoscaling_total_max_node_count = 0
  autoscaling_total_min_node_count = 0
}

上面兩個檔案分別是 cluster 的設定,以及 test node-pool 的設定,裡面的設定,上面基本都有提過,這邊要提的是 dependencydependency 他是 Terragrunt 提供讓我們可以方便地去管理 IaC 之間的相依性,像是我們這邊,需要先建立好 cluster 才能建立 node_pool,此時就可以依靠 dependency block 來完成需求。

( 靠 dependency 來取得 cluster 的資訊,並帶入 node_pool 中 )


此時的執行指令是在 gke 目錄下,使用 terragrunt run-all [參數] 來跑整個相依的 module,我們這邊就建立一個名為 tf-test 的 cluster,並且有一個名為 test 的 node_pool ,其他設定請參考上面程式:


測試 terragrunt run-all
測試 terragrunt run-all

(黃色的 WARN 是因為 gcs 上還沒有存過該狀態檔案,所以會跳出提示)


等到 cluster 建立完成後,會將 cluster 的資訊帶入 node_pool,才開始建立 node_pool 的資源:

測試 terragrunt run-all
測試 terragrunt run-all

產生依賴關聯圖

當我們服務使用到很多依賴關係,想要釐清是誰依賴誰,如果單純看程式會比較麻煩,在 Terragrunt 還有一個好用的指令,可以使用以下指令,產生對應的依賴關係圖,在檢視時可以更清楚知道關係:

terragrunt graph-dependencies | dot -Tpng > graph.png

( 這個 dot 指令是另外的套件,會將關係圖程式碼轉成圖檔,請先安裝 brew install graphviz )


關聯圖
關聯圖

參考資料

事半功倍 — 使用 Terragrunt 搭配 Terraform 管理基礎設置:https://medium.com/act-as-a-software-engineer/%E4%BA%8B%E5%8D%8A%E5%8A%9F%E5%80%8D-%E4%BD%BF%E7%94%A8-terragrunt-%E6%90%AD%E9%85%8D-terraform-%E7%AE%A1%E7%90%86%E5%9F%BA%E7%A4%8E%E8%A8%AD%E6%96%BD-f70c30166639