Terragrunt Discovery and DAG
ไอเดียในหนึ่งประโยค
หัวข้อที่มีชื่อว่า “ไอเดียในหนึ่งประโยค”Terragrunt สร้าง dependency graph ของตัวเอง — หนึ่ง node ต่อ unit (directory) edge มาจาก block dependency/dependencies จริง แยกขาดจาก Terraform graph ของ unit เดี่ยว ๆ โดยสิ้นเชิง (The Graph and the Walk) — และ run --all walk graph นั้นแบบเดียวกับที่ Terraform walk graph ของตัวเอง: dependency ก่อน dependent เสมอ พิสูจน์ด้านล่างด้วย log จริงที่มี timestamp และ serial เคร่งครัด
live tree จริง 3 unit
หัวข้อที่มีชื่อว่า “live tree จริง 3 unit”variable "name" { type = string }resource "local_file" "vpc_marker" { filename = "${path.module}/${var.name}.vpc.json" content = jsonencode({ vpc_id = "vpc-${var.name}" })}output "vpc_id" { value = "vpc-${var.name}" }remote_state { backend = "local" generate = { path = "backend.tf" if_exists = "overwrite" } config = { path = "${get_terragrunt_dir()}/terraform.tfstate" }}generate "provider" { path = "provider.tf" if_exists = "overwrite" contents = "terraform {\n required_version = \">= 1.11.0\"\n}\n"}include "root" { path = find_in_parent_folders("root.hcl")}terraform { source = "../../../modules/vpc"}inputs = { name = "dev"}include "root" { path = find_in_parent_folders("root.hcl")}terraform { source = "../../../modules/vpc"}dependency "vpc" { config_path = "../vpc" mock_outputs = { vpc_id = "vpc-mock" } mock_outputs_allowed_terraform_commands = ["plan", "validate"]}inputs = { name = "compute-in-${dependency.vpc.outputs.vpc_id}"}include "root" { path = find_in_parent_folders("root.hcl")}terraform { source = "../../../modules/vpc"}dependency "vpc" { config_path = "../vpc" mock_outputs = { vpc_id = "vpc-mock" } mock_outputs_allowed_terraform_commands = ["plan", "validate"]}inputs = { name = "db-in-${dependency.vpc.outputs.vpc_id}"}dependent อิสระ 2 ตัว (compute, db) ที่ต่างขึ้นกับ unit vpc เดียวกัน — ตั้งใจให้เป็นแบบนี้ เพื่อให้ log จริงด้านล่างแยกแยะ “รันหลัง dependency ของตัวเอง” กับ “รันตามลำดับตายตัวแบบอื่น” ได้
terragrunt find: discovery จริง ไม่มี DAG ordering
หัวข้อที่มีชื่อว่า “terragrunt find: discovery จริง ไม่มี DAG ordering”$ terragrunt findlive/dev/computelive/dev/dblive/dev/vpcfind เฉย ๆ list unit ที่เจอ (การ scan directory จริง รองรับ --no-hidden หา terragrunt.hcl/stack file) ตามลำดับ ตัวอักษร — compute มาก่อน vpc ซึ่งไม่ใช่ลำดับ apply ที่ถูกต้องสำหรับ tree นี้ (compute ขึ้นกับ vpc) discovery กับ ordering เป็นคนละเรื่องกัน
terragrunt list --dag / --dependencies: output ที่รู้ dependency จริง
หัวข้อที่มีชื่อว่า “terragrunt list --dag / --dependencies: output ที่รู้ dependency จริง”$ terragrunt list --daglive/dev/vpc live/dev/compute live/dev/db
$ terragrunt list --long --dependenciesType Path Dependenciesunit live/dev/compute live/dev/vpcunit live/dev/db live/dev/vpcunit live/dev/vpc
$ terragrunt find --dag --dependencies --format json[ { "type": "unit", "path": "live/dev/vpc" }, { "type": "unit", "path": "live/dev/compute", "dependencies": ["live/dev/vpc"] }, { "type": "unit", "path": "live/dev/db", "dependencies": ["live/dev/vpc"] }]--dag เรียง output ของ list ใหม่ ให้ vpc (ไม่มี dependency) มาก่อน; --dependencies (บน list --long หรือ find --format json) ทำให้ edge จริงต่อ unit ชัดเจน นี่คือ dependency graph ของ Terragrunt เอง สร้างมาจาก block dependency/dependencies ในไฟล์ .hcl เหล่านี้ล้วน ๆ — ไม่เกี่ยวอะไรกับ terraform graph ของ unit เดี่ยว ๆ ที่ข้างใน modules/vpc เลย
run --all --parallelism 1: log จริง มี timestamp เรียงลำดับ
หัวข้อที่มีชื่อว่า “run --all --parallelism 1: log จริง มี timestamp เรียงลำดับ”$ terragrunt run --all --parallelism 1 --non-interactive -- planlog จริง หนึ่งบรรทัดต่อ unit กรองเอาแค่ครั้งแรกที่แต่ละตัวปรากฏ:
04:07:40.998 STDOUT [live/dev/vpc] terraform: Terraform used the selected providers to generate the following execution04:07:41.031 WARN [live/dev/compute] Config .../live/dev/vpc/terragrunt.hcl is a dependency of .../live/dev/compute/terragrunt.hcl that has no outputs, but mock outputs provided and returning those in dependency output.04:07:41.134 WARN [live/dev/db] Config .../live/dev/vpc/terragrunt.hcl is a dependency of .../live/dev/db/terragrunt.hcl that has no outputs, but mock outputs provided and returning those in dependency output.
❯❯ Run Summary 3 units 322ms ──────────────────────────── Succeeded 33 ข้อเท็จจริงจริงใน timestamp พวกนี้: plan ของ vpc เองเริ่มและเสร็จ (04:07:40.998) ก่อน บรรทัด resolve dependency ของ compute จะโผล่มาด้วยซ้ำ (04:07:41.031); compute กับ db — ที่ไม่ขึ้นต่อกันเองจริง ๆ ทั้งคู่ขึ้นกับ vpc เท่านั้น — ก็ยังรันทีละตัวเคร่งครัด (.031 แล้ว .134) เพราะตั้ง --parallelism 1 ไว้ พิสูจน์ว่า --parallelism ของ Terragrunt เองจำกัด unit พร้อมกันแบบเดียวกับที่ -parallelism ของ Terraform จำกัด resource พร้อมกัน (The Graph and the Walk) — 2 ปุ่มแยกกัน 2 graph แยกกัน แต่แนวคิดพื้นฐานเดียวกัน
mock_outputs ระหว่าง plan: warning จริง พิสูจน์แล้ว
หัวข้อที่มีชื่อว่า “mock_outputs ระหว่าง plan: warning จริง พิสูจน์แล้ว”block dependency ทุกตัวข้างบนตั้ง mock_outputs_allowed_terraform_commands = ["plan", "validate"] บรรทัด log จริงพิสูจน์เป๊ะ ๆ ว่าเมื่อไหร่และทำไม mock ถึงถูกใช้: vpc ไม่เคยถูก apply ใน tree นี้เลย (ไม่มี state จริง ไม่มี output จริง) ดังนั้น plan ของ compute กับ db — คำสั่งที่อยู่ใน allowed list — ดำเนินต่อโดยใช้ vpc_id = "vpc-mock" แทนที่จะ fail ไปเลย ถ้า vpc ไม่เคยมี output จริงและไม่มี mock_outputs plan เดียวกันนี้จะ fail ทันทีเพราะต้องการ output ของ dependency จริงที่ยังไม่มีอยู่ นี่คือกลไกเป๊ะ ๆ ที่ทำให้ plan ของ tree หลาย unit ถูก review ได้ทีละ unit หรือทั้งหมดพร้อมกัน ก่อนจะมีอะไร apply จริงเลย
.terragrunt-cache: layout จริง
หัวข้อที่มีชื่อว่า “.terragrunt-cache: layout จริง”$ find live/dev/vpc/.terragrunt-cache -maxdepth 4live/dev/vpc/.terragrunt-cachelive/dev/vpc/.terragrunt-cache/agVjb7UHwdx6LnyffjKdnln63rklive/dev/vpc/.terragrunt-cache/.../SG_cjn-kou3UtRZrXhlds3NpcrMlive/dev/vpc/.terragrunt-cache/.../SG_cjn-kou3UtRZrXhlds3NpcrM/.terragrunt-module-manifestlive/dev/vpc/.terragrunt-cache/.../SG_cjn-kou3UtRZrXhlds3NpcrM/main.tflive/dev/vpc/.terragrunt-cache/.../SG_cjn-kou3UtRZrXhlds3NpcrM/terragrunt.hcllive/dev/vpc/.terragrunt-cache/.../SG_cjn-kou3UtRZrXhlds3NpcrM/.terraformlive/dev/vpc/.terragrunt-cache/.../SG_cjn-kou3UtRZrXhlds3NpcrM/.terragrunt-source-versionlive/dev/vpc/.terragrunt-cache/.../SG_cjn-kou3UtRZrXhlds3NpcrM/.terragrunt-source-manifestlive/dev/vpc/.terragrunt-cache/.../SG_cjn-kou3UtRZrXhlds3NpcrM/provider.tflive/dev/vpc/.terragrunt-cache/.../SG_cjn-kou3UtRZrXhlds3NpcrM/backend.tflive/dev/vpc/.terragrunt-cache/.../SG_cjn-kou3UtRZrXhlds3NpcrM/.terraform.lock.hcllive/dev/vpc/.terragrunt-cache/.../SG_cjn-kou3UtRZrXhlds3NpcrM/.terraform/terraform.tfstatelive/dev/vpc/.terragrunt-cache/.../SG_cjn-kou3UtRZrXhlds3NpcrM/.terraform/providersdirectory hash 2 ชั้นจริง (ชั้นแรก key ด้วย source ชั้นที่สองด้วย module version/manifest ที่ resolve แล้ว) ข้างใน unit แต่ละตัว — Terragrunt copy main.tf จริงของ modules/vpc เข้ามาใน cache directory นี้ พร้อมกับไฟล์ที่ generate เอง (provider.tf, backend.tf — เนื้อหาเป๊ะ ๆ จาก generate block ของ root.hcl ข้างบน) และ terragrunt.hcl จริงที่สร้างมัน แล้วรัน Terraform ตรงนั้น ไม่ใช่ใน live/dev/vpc/ เอง นี่คือเหตุผลที่ .terraform/ จริง (พร้อม providers/ และสำหรับ local backend คือ terraform.tfstate) อยู่ข้างใน .terragrunt-cache/<hash>/<hash>/ ไม่ใช่ข้าง ๆ terragrunt.hcl
--filter, --queue-*: flag จริง จาก run --help
หัวข้อที่มีชื่อว่า “--filter, --queue-*: flag จริง จาก run --help”$ terragrunt run --help... --filter value Filter components using filter syntax. Can be specified multiple times for union (OR) semantics. [$TG_FILTER] --filter-affected Filter components affected by changes between main and HEAD. Equivalent to --filter=[main...HEAD]. [$TG_FILTER_AFFECTED] --queue-ignore-dag-order Ignore DAG order for --all commands. [$TG_QUEUE_IGNORE_DAG_ORDER] --queue-ignore-errors Continue processing Units even if a dependency fails. [$TG_QUEUE_IGNORE_ERRORS] --parallelism value Parallelism for --all commands. [$TG_PARALLELISM]--filter (“filter syntax” จริง document ไว้ ใส่ซ้ำได้เป็น OR — ตัวสืบทอดจริงของ flag แบบ --terragrunt-include-dir เก่าที่โมดูล terragrunt-fundamentals ก่อนหน้าของคอร์สนี้ไม่ได้ใช้) กำหนดขอบเขตว่า run --all แตะ unit ไหนบ้างโดยไม่ต้องแก้ tree; --queue-ignore-dag-order คือทางออกจริงที่ explicit สำหรับตอนที่ ไม่ ต้องการ DAG ordering (พิสูจน์ข้างบน) — พิสูจน์ว่า DAG order เป็น default ที่ตั้งใจ ไม่ใช่ข้อจำกัดที่หนีไม่ได้; --queue-ignore-errors เปลี่ยนการส่งต่อความล้มเหลวข้าม queue เดียวกัน ทั้ง 3 ชื่ออ้างจาก CLI --help จริงของ 1.1.0 — ไม่เคยเดา
Mental Model
หัวข้อที่มีชื่อว่า “Mental Model”DAG ของ Terragrunt คือ graph ที่สองที่หยาบกว่า ซ้อนอยู่เหนือของ Terraform เอง: หนึ่ง node ต่อ unit edge จาก block dependency/dependencies จริง (พิสูจน์ข้างบนผ่าน list --dag/find --dependencies) walk โดย run --all แบบเดียวกับที่ Terraform walk graph ของตัวเอง คือ dependency ก่อนเสมอ (The Graph and the Walk) จำกัดด้วย --parallelism ของตัวเอง (พิสูจน์ข้างบน: serial เคร่งครัดที่ 1 แม้ 2 unit ที่ไม่เกี่ยวกันเองเลยก็ตาม เพราะทั้งคู่แชร์ dependency ที่ queue กำลัง resolve อยู่) mock_outputs มีไว้เพราะ walk นี้อาจถูกขอให้ plan unit ที่ dependency จริงยังไม่ถูก apply — พิสูจน์ข้างบนด้วย warning จริงที่ระบุเงื่อนไขนั้นตรง ๆ layout .terragrunt-cache (พิสูจน์ข้างบน) คือจุดที่ abstraction กลายเป็นรูปธรรม: Terragrunt ไม่เคยรัน Terraform ใน source directory ของคุณเลย มันประกอบ copy ที่ generate และ hash แล้วต่อ unit แล้วรัน Terraform ตรงนั้น
Trade-offs
หัวข้อที่มีชื่อว่า “Trade-offs”| ตัวเลือก | ข้อดี | ต้นทุน |
|---|---|---|
run --all ที่เรียงด้วย DAG (default) | พิสูจน์ข้างบน: dependency เสร็จก่อน dependent เริ่มเสมอ แม้ --parallelism สูง | unit อิสระยังต่อคิวหลัง unit ที่ไม่เกี่ยวกันได้ ถ้าแชร์ dependency เดียวกัน พิสูจน์ข้างบนที่ --parallelism 1 |
--queue-ignore-dag-order | ทางออกสำหรับ apply ที่อิสระจริง ๆ แต่บังเอิญมี dependency แค่เพื่อดึงค่า output | เสียการรับประกัน ordering ไปทั้งหมด — footgun จริงถ้า dependency ไม่ได้เป็นแค่ output-only จริง ๆ |
mock_outputs จำกัดแค่ plan/validate (tree ของบทนี้) | plan ทั้ง tree ทำงานได้ก่อนมีอะไร apply จริงเลย พิสูจน์ข้างบน | apply ตั้งใจไม่อยู่ใน allowed list ตรงนี้ — ข้อจำกัดจริงที่ถูกต้อง หยุดค่า mock ไม่ให้ถูกเขียนราวกับเป็นค่าจริง |
ความเข้าใจผิดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ความเข้าใจผิดที่พบบ่อย”- “
terragrunt findคืน unit เรียงลำดับ apply ที่ปลอดภัยอยู่แล้ว” พิสูจน์ข้างบน:findเฉย ๆ list ตามลำดับตัวอักษร (computeมาก่อนvpc) ซึ่งไม่ถูกต้องสำหรับ tree นี้ — มีแค่--dag(บนfind/list) หรือrun --allเองเท่านั้นที่ใช้ dependency ordering จริง - “
--parallelismบนrun --allสำคัญแค่ตอน unit ไม่เกี่ยวข้องกันเลย” พิสูจน์ข้างบน:computeกับdbไม่ได้ขึ้นต่อกันตรง ๆ เลย แต่ยังรัน serial เคร่งครัดที่--parallelism 1— flag นี้จำกัด concurrency ของ queue ทั้งหมด ไม่ใช่แค่คู่ที่เกี่ยวข้องกันตรง ๆ - “
mock_outputsทำให้ Terragrunt ข้าม dependency check ไปเลยทั้งหมด” พิสูจน์ข้างบน: log จริงยัง emit warning ชัดเจนทุกครั้งที่ mock ถูกใช้แทน ระบุ unit และเหตุผลตรง ๆ — เป็นการแทนที่ที่มองเห็นได้และ document ไว้ ไม่ใช่การข้ามแบบเงียบ ๆ - “Terraform รันจริงข้างใน
live/dev/vpc/สำหรับ unit นั้น” พิสูจน์ข้างบนด้วยfindจริงของ.terragrunt-cache: Terraform รันข้างในไดเรกทอรี cache ที่ generate และ hash แล้ว ที่มี source copy มาพร้อมgenerateblock ของ Terragrunt เอง (provider.tf,backend.tf) — ไม่ใช่modules/vpc/หรือlive/dev/vpc/ต้นฉบับตรง ๆ เลย
อ่านโค้ดที่ AI เขียน
หัวข้อที่มีชื่อว่า “อ่านโค้ดที่ AI เขียน”- CI script หรือ doc ที่ AI สร้าง ที่สมมติว่า
terragrunt find(ไม่มี--dag) เรียงลำดับ apply-safe อยู่แล้ว — พิสูจน์ข้างบนfindเฉย ๆ เรียงตามตัวอักษร ไม่รู้จัก dependency; pipe ตรง ๆ เข้า loop apply ทีละ unit มือ อาจ apply dependent ก่อน dependency ของมันจริง ๆ ได้ - คำแนะนำ “เร่ง CI ให้เร็วขึ้น” ที่บอกให้เพิ่ม
--parallelismบนrun --allมั่ว ๆ โดยไม่เช็ครูปร่าง dependency จริงของ tree — พิสูจน์ข้างบน unit ที่แชร์ dependency เดียวกันยัง serialize หลังมันอยู่ดี ไม่ว่า--parallelismจะเท่าไหร่ เพดานความเร็วจริงคือรูปร่างของ tree ไม่ใช่แค่ค่า flag - โค้ดหรือ doc ที่สมมติว่า
mock_outputs_allowed_terraform_commandsที่มี"plan"อนุญาตapplyโดยปริยายด้วย — พิสูจน์ข้างบนและโดยการออกแบบapplyไม่ควรอยู่ใน list นั้นเลยสำหรับ dependency ที่ค่าจริงสำคัญ เพราะ apply กับค่า mock จะเขียนข้อมูลผิดแบบเงียบ ๆ
🐛 หา bug ในโค้ดนี้
# ci-deploy.sh -- AI-generated deploy scriptfor unit in $(terragrunt find --working-dir live/dev); do(cd "live/dev/$unit" && terragrunt apply -auto-approve)doneเฉลย
script นี้วน output ของ terragrunt find ตรง ๆ แล้ว apply ทีละ unit ตามลำดับนั้น พิสูจน์ข้างบน find เฉย ๆ คืน unit ตามลำดับ ตัวอักษร (compute, db, vpc สำหรับ tree ของบทนี้) — ไม่ใช่ dependency order การ apply compute ก่อน vpc ใน tree ที่ compute ต้องการ output จริง (ไม่ mock) ของ vpc จะ fail ตรง ๆ หรือแย่กว่านั้นคือสำเร็จเงียบ ๆ ด้วยค่า mock/เก่า ถ้า mock_outputs_allowed_terraform_commands ถูก config ผิดให้รวม apply ด้วย ทางแก้จริง พิสูจน์ข้างบน คือ terragrunt run --all -- apply (ที่ walk DAG จริงที่สร้างจาก block dependency/dependencies เรียง dependency ก่อนเสมอ) หรือ terragrunt find --dag เพื่อได้ list ที่เรียงถูกต้องก่อนจะ loop มือ