課程內容與代碼會放在 Github 上: https://github.com/chechiachang/terraform-30-days
賽後文章會整理放到個人的部落格上 http://chechia.net/
前六堂課程,我們簡單認識 Terraform 的核心觀念,包括 State & Backend,以及Module 使用,已經可以在工作上實際使用 Terraform。然而,隨著使用時間越長,使用的 module 越多,開始會發現許多 Terraform 的程式碼會不斷的重複,違反 DRY 原則,例如:
_poc/container_registry/
這邊的範例,一堆 soft link provider.tf 嗎Ex. Registry 的 input arguments 中許多參數,跟別的 root module 重複
# _poc/container_registry/provider.tf
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 2.65.0"
}
}
required_version = ">= 1.0.1"
# remote Backend
backend "azurerm" {
resource_group_name = "terraform-30-days"
storage_account_name = "tfstatee903f2ef317fb0b4"
container_name = "tfstate"
key = "container_registry.tfstate" # 唯一不一樣的設定
}
Ex. Registry 的 input arguments 中許多參數,跟別的 root module 重複
# _poc/container_registry/registry.tf
location = "southeastasia"
resource_group_name = "terraform-30-days"
Don't Repeat Youself 是一個軟體工程開發的基本原則,不斷重複的程式碼往往代表未來維護的困難,違反 DRY 不一定代表不好,在某些情形工程師可能會選擇更好的可讀性,而犧牲 DRY。Terraform Lanuguage 由於語言特性,有一部份重複的程式碼。
這部分重複的程式碼,會隨團隊使用 Terraform 的規模線性成長,對於管理大量 Terraform 的維護人員造成困擾,也拖慢開發進度。
因此,在開始接觸大量複雜範例之前,我們選擇先導入 Terragrunt 這個工具,來精簡程式碼。
Terragrunt 是 gruntwork 推出的一個 Terraform thin wrapper,在執行 Terraform 前可以先"調整" root module 內的 .tf 檔案,保持程式碼的精簡,並提供許多而外工具,加速開發
這裡附上 Gruntwork 官方的 Get Started Guide
可以直接到 Github release 頁面 下載 binary
wget https://github.com/gruntwork-io/terragrunt/releases/download/v0.31.3/terragrunt_darwin_amd64
chmod +x terragrunt_darwin_amd64
sudo mv terragrunt_darwin_amd64 /usr/local/bin/terragrunt
如果使用 pkg manager 或其他工具,也可以直接使用
brew install terragrunt
安裝完可以使用 terragrunt
terragrunt --help
VERSION:
v0.31.3
接下來我們要設定 terragrunt。由於Terragrunt 有非常多的功能,這邊我們先專注在兩個需求:
這邊我們直接看範例,首先要說明整體資料夾結構:
tree -L 1 azure
azure
├── _poc
├── foundation
├── dev
│ └── southeastasia
├── modules
├── prod
├── stag
├── test
├── terragrunt.hcl
└── env.tfvars
azure/_poc
是使用 terragrunt 之前我們所有的範例那剩下兩個東西是什麼?
這是與 terragrunt 設定有關,請見底下的說明。
首先我們可以在產生一組 terraform backend
到 azure/foundation/southeastasia/terraform_backend
,一看,裡面只剩下一個檔案
# terragrunt.hcl
# TERRAGRUNT CONFIGURATION
terraform {
source = "../../../..//azure/modules/terraform_backend"
}
# dependency cycle: terraform_backend is provisioned before all terragrunt usage. There is no terragrunt.hcl at that time.
#include {
# path = "${find_in_parent_folders()}"
#}
inputs = {
resource_group_name = "terraform-30-days"
location = "southeastasia" # Or use japaneast
}
接下來進行 init 與 plan。我們把新的 terraform backend 使用 terragrunt 產生出來
terragrunt init
terragrunt plan
terragrunt apply
到這邊,使用起來跟直接使用 terraform 應該沒有差異,上面這個例子並沒有使用 terragrunt 的額外功能。terragrunt 單純把 terraform 的 command 傳遞下去,底下還是執行 terraform。
terragrunt 的功能,下個例子就會展現。
網路 / VPC 網段的管理,是公有雲的必要工作,這邊以 provision 新的網段為例
到 azure/foundation/compute_network
,裡面有兩個檔案
tree
.
├── compute_network.tf
└── terragrunt.hcl
provider.tf 在這邊已經消失了
看一下 terragrunt.hcl 的設定,重點在 include {} 這個 code block
find_in_parent_folders()
是 terragrunt 提供的 function
# terragrunt.hcl
# TERRAGRUNT CONFIGURATION
# use terragrunt function to include .hcl file
# in this case, will find azure/terragrunt.hcl
include {
path = find_in_parent_folders()
}
terraform {
source = "../../..//azure/foundation/compute_network"
# use double-slash (//) after repository root path to avoid
# - WARN[0000] No double-slash (//) found in source URL
#source = "."
}
底下的 terraform {} code block 則跟上一個例子一樣,指向 root module
../../../..//azure/modules/terraform_backend
其實就是 .
也就是當前所在路徑接下來看上層 include 的 ~/terraform-30-days/azure/terragrunt.hcl
內容
首先是 provider.tf,這邊使用 generate {} code block 來產生 provider.tf
# azure/terragrunt.hcl
generate "provider" {
path = "provider.tf"
if_exists = "overwrite_terragrunt"
contents = <<EOF
provider "azurerm" {
features {}
}
EOF
}
再來產生的事 backend.tf,使用 remote_state
{} code block 設定 remote backend
# azure/terragrunt.hcl
remote_state {
backend = "azurerm"
generate = {
path = "backend.tf"
if_exists = "overwrite_terragrunt"
}
config = {
key = "${path_relative_to_include()}/terraform.tfstate"
resource_group_name = "terraform-30-days"
storage_account_name = "tfstate445d2966b56b5d05"
container_name = "tfstate"
}
}
path_relative_to_include()
是另一個 terragrunt function
azure/foundation/compute_network/terragrunt.hcl
azure/terragrunt.hcl
path_relative_to_include() = foundation/compute_network
├── terragrunt.hcl
├── env.tfvars
├── foundation
│ ├── compute_network
│ │ ├── compute_network.tf
│ │ └── terragrunt.hcl
所以整個效果等同於產生一個 backend.tf
# backend.tf
terraform {
backend "azurerm" {
key = "foundation/compute_network/terraform.tfstate"
resource_group_name = "terraform-30-days"
storage_account_name = "tfstate445d2966b56b5d05"
container_name = "tfstate"
}
}
為何 key 要設為 foundation/compute_network/terraform.tfstate
foundation/compute_network/terragrunt.hcl
blob//foundation/compute_network/terraform.tfstate
最後一段 terraform {} block,可以在 terragrunt 驅動的 terraform command 做許多調整,例如
extra_arguments
get_terraform_commands_that_need_vars()
,回傳一串接受 -var 與 -var-file 參數的 terraform commandrequired_var_files
參數把 env.tfvars 檔案,作為 terraform -var-file 的參數get_parent_terragrunt_dir()
使用,拿到上層 terragrunt.hcl 的絕對路徑~/terraform-30-days/azure/env.tfvars
,作為 -var-file 的參數# azure/terragrunt.hcl
terraform {
extra_arguments "env" {
commands = get_terraform_commands_that_need_vars()
required_var_files = [
"${get_parent_terragrunt_dir()}/env.tfvars",
]
}
}
效果等同在 terraform plan 與 apply (以及其他 command) 執行時,多加參數
terraform plan -var-file ~/terraform-30-days/azure/env.tfvars
terraform apply -var-file ~/terraform-30-days/azure/env.tfvars
terragrunt 會將 terraform module cache 一分在本目錄,cache
如果是在本地開發中的 module,有可能會 cache 到錯誤的 module,請把本地 cache 清除再重新 init
rm -rf .terragrunt-cache
terragrunt init
terraform 的命令在 terragrunt 上完全都能使用,所以才說 terragrunt 是一層 wrapper,意思是
使用 Terragrunt 是一個額外的選擇,團隊可以依據狀況去選擇
Pros
Cons
您好,初學 terragrunt 有些地方不太瞭解,
想請問產生出的 .terragrunt-cache
在多人協作下,是否需要共同持有?
不需要,請務必加到 .gitignore
https://github.com/gruntwork-io/terragrunt/blob/master/.gitignore