Why Terragrunt
ไอเดียในหนึ่งประโยค
หัวข้อที่มีชื่อว่า “ไอเดียในหนึ่งประโยค”Terraform workspace แก้ปัญหา DRY reuse ได้ดีแต่ isolation ยังอ่อน directory-per-environment แก้ isolation ได้ดีแต่ดึง boilerplate ของ backend กับ provider ที่ซ้ำซ้อนกลับมา และ Terragrunt เกิดมาเพื่อปิดช่องว่างที่เหลือนั้นด้วยการห่อ binary terraform ตัวจริงแล้ว generate configuration ที่ซ้ำซ้อนให้แทนคุณ
ความตึงที่แม่นยำ พูดใหม่อีกครั้ง
หัวข้อที่มีชื่อว่า “ความตึงที่แม่นยำ พูดใหม่อีกครั้ง”สองบทที่แล้วแต่ละบทแก้ครึ่งหนึ่งของปัญหาจากบทแรกสุดของ module นี้
- workspace เก็บโค้ด
.tfชุดเดียว backend เดียว และ provider configuration เดียว — DRY จริง ๆ — แต่ทุก workspace ใช้ backend เดียวกันและ GCPprojectเดียวกัน ทำให้ isolation ระหว่าง environment อ่อนพอที่การเลือกterraform workspace selectผิดจะทำให้ apply การเปลี่ยนแปลงขนาด prod เข้าไปในสิ่งที่ใครบางคนคิดว่าเป็น environment ที่ทิ้งได้ - directory-per-environment ให้แต่ละ environment มี backend ของตัวเองและ GCP project ของตัวเอง — isolate จริง ๆ — แต่ block backend, block provider, และการต่อสาย
sourceกับ version ของ module จบลงด้วยการถูก copy-paste แทบทุกตัวอักษรเข้าไปในทุก environment directory ซึ่งคือความเสี่ยงจากการทำซ้ำมือแบบเดียวกับที่บทแรกของ module นี้เปิดเรื่องไว้เป๊ะ
Terraform เปล่า ๆ เองไม่มี primitive ตัวเดียวที่ให้ทั้ง isolation ที่แข็งแรงและ configuration ที่ไม่ซ้ำซ้อนข้าม environment พร้อมกัน คุณเลือกได้แค่ฝั่งใดฝั่งหนึ่งของความตึงสองอันข้างบน แต่หนีทั้งคู่พร้อมกันด้วยโค้ด .tf อย่างเดียวไม่ได้
Terragrunt คืออะไรจริง ๆ
หัวข้อที่มีชื่อว่า “Terragrunt คืออะไรจริง ๆ”Terragrunt คือ thin wrapper และ orchestrator ที่นั่งอยู่บน binary terraform (หรือ tofu) ตัวจริง ไม่ได้เพิ่ม resource syntax ใหม่ และไม่ได้ compile ไปเป็นภาษา infrastructure-as-code แบบอื่น โค้ด .tf ของ module ที่คุณเขียนอยู่แล้ว รวมถึงทุกอย่างใน directory modules/app จากบทที่แล้ว ยังคงเหมือนเดิมทุกประการและยังเป็น Terraform ธรรมดา ทุกคำสั่งของ Terragrunt สุดท้ายแล้ว shell out ไปเรียกคำสั่ง terraform (หรือ tofu) ตัวจริงข้างใต้
# What you typeterragrunt apply
# What actually eventually runs, per unit, with generated config already in placeterraform applyสิ่งที่ Terragrunt เพิ่มเข้ามาคือ layer ที่จัดการ boilerplate ที่ซ้ำซ้อนที่คุณเพิ่งเห็นกองสูงขึ้นรอบ ๆ การเรียก Terraform หนึ่งครั้งต่อ environment
- generate backend configuration จากคำนิยามศูนย์กลางเดียว แทนที่จะ copy-paste block
backend "gcs" { ... }เดียวกันเข้าไปในทุก environment directory - generate provider configuration แบบเดียวกัน จากที่เดียว แทนที่จะมี block
provider "google" { ... }ที่แทบเหมือนกันต่อ environment - ส่ง input variable ของแต่ละ environment เข้าไปใน shared module ทำให้
dev,staging,prodยังเรียก sourcemodules/appเดียวกันด้วยค่าที่ต่างกัน โดยไม่ต้องต่อสายmoduleblock แยกมือต่อ environment - เข้าใจลำดับ dependency เมื่อ infrastructure ถูกแบ่งเป็นหลาย unit ที่ apply แยกกันเป็นอิสระ เช่น unit
networkที่ unitgkeต้องพึ่งพา ทำให้ Terragrunt คิดออกว่าnetworkต้อง apply ก่อนgkeแทนที่คุณต้อง track ลำดับนั้นเองด้วยมือข้ามterraform applyที่รันแยกกัน
จุดสุดท้ายนี้สำคัญเป็นพิเศษ เพราะ directory-per-environment แบ่ง infrastructure เป็น state file ที่แยกกันจริงต่อ environment (และตามที่ module ถัดไปจะพูดถึง มักแยกต่อ component ด้วย) Terraform configuration เดียวมี dependency graph ภายในของตัวเองระหว่าง resource ใน state file เดียว แต่ไม่มี concept ของการเรียงลำดับข้าม configuration ที่แยกกันทั้งหมดและการเรียก terraform apply แยกกันเลย การเรียงลำดับข้าม configuration แบบนี้แหละคือปัญหาที่ Terragrunt ถูกสร้างมาเพื่อแก้ ซ้อนอยู่บน graph ของ Terraform เอง ไม่ใช่มาแทนที่
Terragrunt ไม่ใช่อะไร
หัวข้อที่มีชื่อว่า “Terragrunt ไม่ใช่อะไร”คุ้มค่าที่จะพูดให้แม่นยำตรง boundary นี้ เพราะพูดเกินจริงเกี่ยวกับสิ่งที่ Terragrunt ทำได้ง่ายมาก
- Terragrunt ไม่ใช่ fork ของ Terraform และ ไม่ใช่ ตัวแทนของ Terraform การ plan และ apply resource จริง ๆ ยังทำโดย
terraformหรือtofuทั้งหมด - Terragrunt ไม่ใช่ ภาษา configuration ใหม่สำหรับอธิบาย GCP resource resource
google_compute_instanceถูก define แบบเดียวกันเป๊ะไม่ว่าจะมี Terragrunt เกี่ยวข้องหรือไม่ - Terragrunt ทำให้การเปลี่ยนแปลง Terraform ที่ไม่ปลอดภัยกลายเป็นปลอดภัยเองไม่ได้ Terragrunt เอา boilerplate ที่ copy-paste ออกไปและจัดลำดับให้ แต่ logic ของ infrastructure จริง ๆ และความถูกต้องยังเป็นความรับผิดชอบของโค้ด module
.tfของคุณทั้งหมด
module ถัดไปเริ่มต่อจากตรงนี้เป๊ะ ด้วย building block จริงของ Terragrunt คือ root.hcl, remote_state, generate, และ terragrunt.hcl ของแต่ละ unit ที่เปลี่ยนตำแหน่งนี้ให้กลายเป็น setup ที่ใช้งานได้จริง
flowchart TD workspaces["Terraform workspaces: DRY, weak isolation"] --> gap["The gap: isolation AND zero duplication together"] directoryPerEnv["Directory-per-environment: isolated, duplicated boilerplate"] --> gap gap --> terragrunt["Terragrunt: generates backend, provider, inputs, manages unit ordering"] terragrunt -->|shells out to| terraformBinary["Real terraform / tofu binary"]