Why Terragrunt
ไอเดียในหนึ่งประโยค
หัวข้อที่มีชื่อว่า “ไอเดียในหนึ่งประโยค”Terraform workspace แก้ reuse ได้ดีแต่ปล่อยให้ isolation อ่อนแอ directory-per-environment แก้ isolation ได้ดีแต่เอา boilerplate ที่ซ้ำกันกลับมาใหม่ และ Terragrunt มีไว้เพื่อปิดช่องว่างระหว่างสองอันนี้โดยเฉพาะ โดยไม่ต้องให้ยอมสละอย่างใดอย่างหนึ่งไป
สองครึ่งของปัญหาเดียวกัน ทบทวน
หัวข้อที่มีชื่อว่า “สองครึ่งของปัญหาเดียวกัน ทบทวน”สองบทที่แล้วพาไปดูคำตอบแบบ native สองแบบของ Terraform ต่อปัญหา multi-environment และแต่ละแบบแลก goal หนึ่งกับอีก goal หนึ่ง workspace เก็บ .tf config เดียวและ backend/provider block เดียว แค่สลับว่า state file ไหน active อยู่
# Workspaces: one directory, one backend, one provider, one subscription_id# for every environment, only the active workspace changesterraform workspace select prodterraform applyDirectory-per-environment ไปอีกทาง คือ isolation จริงผ่าน directory, backend, และ provider block ที่แยกกันจริง ๆ ต่อ environment แลกมาด้วย backend/provider boilerplate นั้นถูก copy-paste แบบตัวอักษรเป๊ะ ๆ เข้าไปในทุก environment
# Directory-per-environment: real isolation, separate backend and provider# per environment, but environments/dev, environments/staging, and# environments/prod each carry their own near-identical backend.tfcd environments/prodterraform initterraform applyไม่มีวิธีไหนผิด และไม่มีวิธีไหนเป็น bug ใน Terraform ทั้งสองคือ trade-off ที่ตรงไปตรงมาสองแบบระหว่าง goal เดียวกันสองอัน สิ่งที่ Terraform เองไม่มีให้คือ primitive เดียวที่ให้ isolation ที่แข็งแรงและ configuration duplication เป็นศูนย์ข้าม environment ได้พร้อมกัน
Terragrunt คืออะไรจริง ๆ
หัวข้อที่มีชื่อว่า “Terragrunt คืออะไรจริง ๆ”Terragrunt คือ wrapper บาง ๆ และ orchestrator ที่นั่งอยู่บนตัว binary จริงของ terraform (หรือ tofu) ไม่ได้เพิ่ม resource syntax ใหม่ และโค้ด module ที่เขียนเพื่อสร้าง Azure resource จริง ๆ ยังคงเป็นสิ่งเดิมอยู่แล้ว คือ Terraform ธรรมดา ในไฟล์ .tf ธรรมดา ไม่เปลี่ยนแปลง
สิ่งที่ Terragrunt เพิ่มเข้ามาคือ layer ที่อยู่เหนือโค้ดนั้น เล็งตรงไปที่ duplication ที่ directory-per-environment เอากลับมา แทนที่จะเขียน backend.tf กับ provider.tf ที่เหมือนกันแทบทุกตัวอักษรในทุก environment directory ด้วยมือ Terragrunt ให้ define backend และ provider configuration ครั้งเดียว จากศูนย์กลาง แล้ว generate รายละเอียดต่อ environment เช่น state key, subscription_id, resource group จาก definition อันนั้นอันเดียว
# terragrunt.hcl (a conceptual preview only; the actual syntax is the# subject of the next module, Terragrunt Fundamentals)# One central place that generates the backend and provider blocks for# every environment, instead of copy-pasting them into environments/dev,# environments/staging, and environments/prod by handremote_state { backend = "azurerm"}ไอเดียเดียวกันขยายไปถึงการส่ง input variable ของแต่ละ environment และเมื่อ infrastructure ถูกแยกเป็นหลาย unit ที่ apply แยกกันเป็นอิสระต่อกัน ไปถึงการเข้าใจว่า unit ไหนต้องพึ่ง unit ไหนก่อน เพื่อให้ Terragrunt apply ตามลำดับที่ถูกต้อง
Terragrunt ไม่ใช่อะไร
หัวข้อที่มีชื่อว่า “Terragrunt ไม่ใช่อะไร”Terragrunt ไม่ใช่ fork ของ Terraform ไม่ได้ compile ไปเป็น infrastructure-as-code language อื่น และไม่ใช่ตัวแทนของ terraform binary ทุกคำสั่ง Terragrunt สุดท้ายแล้ว shell out ไปเรียกคำสั่ง terraform หรือ tofu จริง ๆ เบื้องหลัง รันกับ provider เดียวกันและ state format เดียวกันที่ Terraform เข้าใจอยู่แล้ว
# Every terragrunt command wraps a real terraform (or tofu) commandterragrunt plan # generates config, then runs a real `terraform init` and `terraform plan`terragrunt apply # generates config, then runs a real `terraform apply`ความต่างนั้นสำคัญด้วยเหตุผลอีกอย่างนอกจากลด boilerplate Terraform configuration เดียวมี dependency graph ภายในของตัวเองระหว่าง resource อยู่แล้ว แต่ graph นั้นหยุดอยู่ที่ขอบของ terraform apply หนึ่งครั้ง เมื่อ infrastructure ของ environment หนึ่งถูกตั้งใจแยกเป็นหลาย unit ที่ apply แยกกันเป็นอิสระต่อกัน เช่น network unit, database unit, application unit แต่ละอันมี state ของตัวเอง ไม่มีอะไรใน Terraform เองรู้ว่า application unit ต้องการ output ของ database unit ก่อน นั่นคือปัญหาอีกแบบที่ Terragrunt ถูกสร้างมาแก้ คือการเรียงลำดับและส่ง output ข้าม unit ที่ apply แยกกัน ซึ่งบทต่อไปจะลงรายละเอียด
flowchart LR workspaces["Terraform workspaces: DRY, weak isolation"] --> gap["The gap: no native primitive gives both at once"] dirPerEnv["Directory-per-environment: isolated, duplicated"] --> gap gap --> terragrunt["Terragrunt: generates config, removes duplication"] terragrunt --> realTf["Real terraform / tofu binary actually runs"]