ข้ามไปยังเนื้อหา

Why Terragrunt

Terragrunt คือ wrapper บาง ๆ ที่ห่อรอบ terraform (หรือ tofu) binary จริง คอย generate และจัดการ boilerplate ของ backend, provider, และ input ที่ซ้ำกันข้าม environment ทำให้ได้ทั้ง configuration ที่ DRY และ isolation ที่แข็งแรงพร้อมกัน แทนที่จะต้องแลกอย่างใดอย่างหนึ่งไป

ช่องว่างที่ทั้งสองวิธีของ Terraform เองปิดไม่ได้

หัวข้อที่มีชื่อว่า “ช่องว่างที่ทั้งสองวิธีของ Terraform เองปิดไม่ได้”

สองบทที่แล้วพาไปดูสองคำตอบที่ Terraform เองมีให้กับปัญหา multi-environment และแต่ละคำตอบปิดช่องว่างได้แค่ครึ่งเดียว

  • workspace เก่งเรื่อง DRY reuse มาก configuration เดียว, backend เดียว, state file หลายชุด แต่ isolation อ่อนแอ ทุก workspace แชร์ backend เดียวกัน, provider configuration เดียวกัน, และโค้ด .tf เดียวกัน มีแค่ string ที่ CLI เลือกไว้เป็นตัวแยกเท่านั้น
  • directory-per-environment เก่งเรื่อง isolation มาก directory จริง, backend จริง, อาจมี AWS account แยกกันจริงต่อ environment แต่พา duplication กลับมา backend block, provider block, และ argument source ของ module จบลงด้วยการ copy-paste แทบทุกตัวอักษรในทุก environment directory

Terraform เปล่า ๆ ไม่มี primitive ระดับ first-class ที่ให้ทั้ง isolation ที่แข็งแรงและ configuration ที่ไม่ duplicate เลยพร้อมกัน เลือก pattern ของ Terraform เองแบบไหนก็ต้องยอมรับจุดอ่อนของอีก pattern หนึ่งเสมอ

Terragrunt คือ wrapper และ orchestrator บาง ๆ ที่วางอยู่บน setup Terraform (หรือ OpenTofu) ที่มีอยู่แล้ว ไม่ได้เพิ่ม resource syntax ใหม่ และไม่ได้แทนที่อะไรข้างใน modules/app เลย โค้ด module จริงของคุณไม่เปลี่ยนแปลงเลย ยังเป็น Terraform ธรรมดา เหมือนที่เห็นมาในทุกบทก่อนหน้านี้ สิ่งที่ Terragrunt เพิ่มเข้ามาคือ layer ที่อยู่ข้างบนนั้น รับผิดชอบ boilerplate ที่เพิ่งเห็นสะสมอยู่ใน directory-per-environment pattern

  • generate backend configuration ของแต่ละ environment จาก definition ศูนย์กลางเดียว แทนที่จะเอา backend "s3" block ไปใส่มือในทุก environment directory
  • generate provider configuration ของแต่ละ environment ด้วยวิธีเดียวกัน
  • ใส่ input variable ของแต่ละ environment ไว้จุดเดียวต่อ environment โดยไม่ต้องทำ boilerplate ของ module block รอบ ๆ ซ้ำ
  • เข้าใจ dependency ordering เมื่อ infrastructure ถูกแยกเป็นหลาย unit ที่ apply แยกกันอิสระ เช่น unit vpc ที่ unit ec2 ต้องใช้ output

Terragrunt unit เล็ก ๆ สำหรับ environment dev จากบทที่แล้วหน้าตาเป็นแบบนี้ สังเกตว่า field ของ backend เป็นตัวเดียวกับที่เขียนมือไว้ก่อนหน้านี้เป๊ะ เพียงแต่ตอนนี้ generate มาจาก configuration แทนที่จะ copy-paste

environments/dev/app/terragrunt.hcl
remote_state {
backend = "s3"
generate = {
path = "backend.tf"
if_exists = "overwrite"
}
config = {
bucket = "acme-terraform-state-dev"
key = "app/terraform.tfstate"
region = "us-east-1"
use_lockfile = true
}
}
terraform {
source = "../../../modules/app"
}
inputs = {
environment = "dev"
instance_type = "t3.micro"
instance_count = 1
}

ตรงนี้ควรพูดให้ชัด เพราะเป็นความเข้าใจผิดที่พบบ่อยที่สุด Terragrunt ไม่ใช่ fork ของ Terraform และไม่ได้ compile configuration ของคุณไปเป็นภาษา infrastructure-as-code แบบอื่น ทุกคำสั่งของ Terragrunt สุดท้ายแล้ว shell out ไปเรียกคำสั่ง terraform หรือ tofu จริง ๆ ข้างใต้เสมอ รันกับ backend และ provider configuration ตัวเดียวกับที่ Terragrunt เพิ่ง assemble ไว้ สั่ง terragrunt plan แล้วข้างใต้คือ terraform plan ธรรมดาที่รันอยู่ หน้าที่ของ Terragrunt จบแค่การ generate input แล้ว orchestrate ว่าคำสั่งจริงนั้นควรรันตอนไหน

Terraform configuration เดียวเข้าใจลำดับ dependency ระหว่าง resource ภายใน state file เดียวอยู่แล้ว ถ้า security group อ้างถึง ID ของ VPC ตัวหนึ่ง Terraform คิดเองได้ว่าต้องสร้าง VPC ก่อน อัตโนมัติ แต่ graph นั้นหยุดอยู่ที่ขอบของ state file เดียว ทันทีที่ infrastructure ถูกแยกเป็นหลาย unit ที่ apply แยกกันอิสระ เช่น unit vpc ที่มี state ของตัวเอง กับ unit ec2 แยกที่มี state ของตัวเองซึ่งต้องใช้ subnet ID ของ VPC ไม่มีอะไรใน terraform apply ของแต่ละอันที่รู้เลยว่าอีก unit ต้องรันก่อน เพราะแต่ละอันคือ process คนละตัวที่ทำงานบน state file คนละไฟล์กันเลย

Terragrunt ปิดช่องว่างนั้นด้วย dependency block ที่อ่าน output ของ unit อื่น และ run queue ที่ apply unit ตามลำดับที่ถูกต้องให้อัตโนมัติ

Terminal window
# Terragrunt determines vpc must run before ec2, then applies both in order
terragrunt run --all apply
flowchart LR
  ws["Workspaces: DRY, weak isolation"] --> gap["The gap: no primitive gives both at once"]
  dirEnv["Directory-per-environment: isolated, duplicated boilerplate"] --> gap
  gap --> tg["Terragrunt: generates config, orders units"]
  tg --> tf["Real terraform / tofu binary"]
Workspaces (DRY, weak isolation) and directory-per-environment (isolated, duplicated) both leave a gap; Terragrunt wraps the real terraform binary to close it
ช่องว่างอะไรที่ยังเหลืออยู่หลังใช้ Terraform workspace หรือ directory-per-environment pattern อย่างใดอย่างหนึ่งเพียงลำพัง
Terragrunt คืออะไรกันแน่ อธิบายให้ถูกต้อง
Terragrunt มักถูกเข้าใจผิดว่าเป็นอะไร ทั้งที่จริง ๆ แล้วไม่ใช่
Terragrunt แก้ปัญหาแบบไหนที่ dependency graph ภายในของ Terraform configuration เดียวแก้ไม่ได้