How Terraform Works
โมดูลนี้มีไว้ทำไม
หัวข้อที่มีชื่อว่า “โมดูลนี้มีไว้ทำไม”ทุกโมดูลก่อนหน้านี้ในคอร์สสอนให้คุณ ใช้ Terraform กับ Terragrunt: เขียน HCL, รัน init/plan/apply, เก็บ state แบบ remote, ต่อ variable กับ module, สร้าง live tree แบบ multi-environment, และ ship ผ่าน CI พร้อม policy check ไม่มีข้อไหนเลยที่ต้องรู้ว่า terraform plan จริง ๆ แล้ว สร้าง อะไรก่อนจะ print diff ออกมา, terraform.tfstate จริง ๆ แล้ว คือ อะไรในระดับ byte, -out=tfplan เขียนอะไรลงดิสก์จริง ๆ, hashicorp/aws (หรือ azurerm, หรือ google) คุยกับ terraform ในฐานะ process อย่างไร, หรือ terragrunt run --all ตัดสินใจอย่างไรว่า unit ไหนรันก่อนไหน
ช่องว่างนี้แหละคือจุดที่ Terraform tooling หรือ review ที่ AI เขียนพังแบบที่ดูดีบนผิวหน้า: script “policy check” ที่ grep ข้อความ output ของ terraform plan แทนที่จะอ่าน array resource_changes[].change.actions ที่มีโครงสร้างจริงจาก terraform show -json; CI step ที่สมมติว่า terraform apply -parallelism=50 เร็วกว่าเสมอโดยไม่รู้ว่า flag นี้จำกัดอะไร; เพื่อนร่วมทีมที่ “แก้” state lock ที่ค้างด้วยการแก้ field serial ใน terraform.tfstate มือ โดยไม่รู้ว่า state push จะปฏิเสธค่าที่ stale ตรง ๆ; block dev_overrides ที่ copy-paste มาจาก blog แล้วทำให้ init พังเงียบ ๆ โดยไม่มีใครอธิบายได้จาก error text อย่างเดียว ไม่มีข้อไหนเลยที่เป็น HCL syntax error ไม่มีข้อไหนเลยที่ terraform validate จับได้ ทุกข้อคือข้อเท็จจริงเกี่ยวกับ runtime ที่โมดูลนี้พิสูจน์ให้ดูจริง ๆ
โมดูลนี้มีหน้าที่เดียว: ให้ความรู้เรื่อง runtime 5 ชิ้นที่ทำให้คุณมอง output ของ Terraform หรือ Terragrunt แบบไหนก็ได้ — graph, state file, plan -out, provider crash, run --all log — แล้วรู้ทันทีว่าอะไรสร้างมันขึ้นมาและทำไม
terraform plan/applyจริง ๆ walk อะไร และตามลำดับไหน? (The Graph and the Walk —terraform graphจริง ทั้ง DOT และ Mermaid,-parallelism,depends_onเทียบกับ implicit reference)terraform.tfstateจริง ๆ คืออะไรในระดับ schema? (State File Anatomy — JSON จริงจากstate pull, การบังคับใช้serial/lineageพิสูจน์ด้วยการถูกปฏิเสธจริง,state replace-provider,show -jsonเทียบกับ state ดิบ)- plan file ที่ save ไว้คืออะไร และ CI/policy tooling อ่านอะไรจากมันจริง ๆ? (Plan File and JSON Output —
-outคือ zip จริง พิสูจน์ด้วยunzip -l; matrix เต็มของresource_changes[].change.actions;-detailed-exitcode) - Terraform คุยกับ provider จริง ๆ ได้อย่างไร? (Provider Plugin Protocol — go-plugin บน gRPC, handshake จริงจาก
TF_LOG=TRACEกับ callGetProviderSchema, ทำไมinitต้องโหลด binary ต่อ OS/arch, provider crash ที่ทำให้เกิดขึ้นจริง,dev_overrides) - Terragrunt ตัดสินใจว่าจะรันอะไรอย่างไร และตามลำดับไหน? (Terragrunt Discovery and DAG —
terragrunt find/list --dagจริง, log ของrun --all --parallelism 1ที่เรียงลำดับจริง,.terragrunt-cache,mock_outputs)
ทุก claim ในโมดูลนี้มีหลักฐานจากหนึ่งใน 3 อย่าง: การรันจริงบนเครื่อง ทำซ้ำได้ กับ Terraform 1.16.4 และ Terragrunt 1.1.0 (เวอร์ชันที่คอร์สนี้ pin ไว้) — วาง output ไว้ใต้ claim นั้น ตัด path เฉพาะเครื่องออกแต่ไม่เคยแต่งขึ้นเอง; เอกสารทางการที่ developer.hashicorp.com หรือ terragrunt.gruntwork.io; หรือถ้าทำทั้งสองอย่างแบบ offline ไม่ได้ ก็บอกตรง ๆ ว่าทำไม่ได้
โมดูลนี้ครอบคลุมอะไรบ้าง
หัวข้อที่มีชื่อว่า “โมดูลนี้ครอบคลุมอะไรบ้าง”| บทเรียน | ตอบคำถามอะไร |
|---|---|
| The Graph and the Walk | Terraform สร้างอะไรจากไฟล์ .tf ของคุณก่อนจะทำอะไรเลย และ -parallelism เปลี่ยนสิ่งที่เกิดขึ้นตอน apply อย่างไร? |
| State File Anatomy | ข้างใน terraform.tfstate มีอะไรบ้างจริง ๆ และอะไรที่หยุดไม่ให้สองคนเขียนทับ state กันเงียบ ๆ? |
| Plan File and JSON Output | plan file ที่ -out สร้างประกอบด้วยอะไรบ้าง และ policy engine หรือเครื่องมือคำนวณ cost อ่านอะไรจาก show -json จริง ๆ? |
| Provider Plugin Protocol | ระหว่าง terraform init กับ provider พร้อม plan เกิดอะไรขึ้นจริง ๆ และ provider crash หน้าตาเป็นอย่างไร? |
| Terragrunt Discovery and DAG | Terragrunt หา unit ของคุณเจอได้อย่างไร สร้างลำดับ dependency ของตัวเองอย่างไร และตัดสินใจว่า run --all ทำอะไรก่อนอย่างไร? |
The State File แนะนำ state ไปแล้วว่าเป็น “ไฟล์ JSON ที่แมป configuration เข้ากับ infrastructure จริง” โมดูลนี้ไปไกลกว่านั้น เข้าสู่ schema จริง, อัลกอริทึม graph จริง, byte จริงบนดิสก์, และขอบเขต process จริงระหว่าง Terraform Core กับ provider
flowchart TD hcl["Your .tf files"] --> graphbuild["Build the dependency graph\n(The Graph and the Walk)"] graphbuild --> walk["Walk the graph, up to -parallelism nodes concurrently\n(default 10)"] walk --> rpc["Per node: gRPC call to the provider plugin\n(Provider Plugin Protocol)"] rpc --> plan["Plan result assembled"] plan --> outfile["terraform plan -out=tfplan\n(a real zip -- Plan File and JSON Output)"] outfile --> apply["terraform apply tfplan"] apply --> state["Written to state\n(State File Anatomy)"] state --> tg["Terragrunt units: their own DAG over many state files\n(Terragrunt Discovery and DAG)"]
Mental Model
หัวข้อที่มีชื่อว่า “Mental Model”ทุกอย่างในโมดูลนี้คือเรื่องเดียวกัน เล่าจาก 5 มุมของ pipeline เดียวกัน: Terraform ไม่เคย execute configuration ของคุณจากบนลงล่าง — มัน compile มันเป็น graph แล้วทุกคำสั่ง (plan, apply, destroy, แม้แต่ graph เอง) คือการ walk บน graph นั้น ส่งแต่ละ node ไปให้ provider plugin ที่เป็นเจ้าของมันผ่าน gRPC แล้วบันทึกผลลงเอกสาร state ที่มีเวอร์ชันเดียว Terragrunt อยู่อีกชั้นเหนือขึ้นไปและทำแบบเดียวกันแต่หยาบกว่า: แทนที่จะเป็น resource, node ของมันคือทั้ง unit Terraform (directory), edge มาจาก dependency/dependencies block แทน resource reference, และ --parallelism ของมันเองก็จำกัดว่า unit กี่ตัวรันพร้อมกัน — แยกขาดจาก -parallelism ที่ terraform apply แต่ละตัวข้างใน unit ใช้โดยสิ้นเชิง พอคุณวางข้อเท็จจริงไหนก็ตามในโมดูลนี้ลงบน “graph, walk, gRPC call, state write” (แล้วอีกชั้น “graph ของ Terragrunt เอง, walk, การรัน unit”) การอ่าน diff ของ plan ที่ไม่คุ้น, state file, CI policy step, หรือ log ของ run --all ก็จะไม่ใช่ปริศนา 5 อย่างแยกกันอีกต่อไป
ความเข้าใจผิดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ความเข้าใจผิดที่พบบ่อย”- “
terraform applyรัน resource ตามลำดับที่เขียนในไฟล์” The Graph and the Walk พิสูจน์ตรงข้ามด้วยterraform graphจริง: ลำดับมาจาก dependency graph ทั้งหมด (implicit reference กับdepends_onที่ชัดเจน) และ resource ที่ไม่เกี่ยวกันจะถูกส่งพร้อมกัน สูงสุดตาม-parallelism(default 10) — พิสูจน์ด้วย wall-clock จริงที่ operation อิสระ 2 วินาที 5 ตัวเสร็จใน ~2 วินาทีตาม default และ ~10 วินาทีที่-parallelism=1 - “state เป็นแค่ cache แก้มือได้ถ้าคำสั่ง
terraform stateไม่สะดวก” State File Anatomy พิสูจน์ว่า state มีเวอร์ชันและถูกบังคับใช้จริง:terraform state pushจริงที่มีserialเก่าหรือlineageไม่ตรงกันจะถูกปฏิเสธตรง ๆ ด้วย error text จริง ไม่ merge เงียบ ๆ - “plan file ที่ save ไว้ก็คือ output ที่มนุษย์อ่านได้ของ
planแค่ cache ไว้” Plan File and JSON Output พิสูจน์ว่า-outสร้าง zip archive จริง (unzip -lแสดงสด ๆ) ที่มี plan, state ก่อนและหลัง refresh, และ config snapshot เต็ม — พอให้applyทำงานได้โดยไม่ต้อง re-evaluate ไฟล์.tfของคุณเลย - “Terraform ‘รู้เอง’ วิธีคุยกับ AWS, Azure, GCP built-in” Provider Plugin Protocol พิสูจน์ว่า provider ทุกตัวคือ subprocess แยกที่ Terraform สั่งรันแล้วคุยผ่าน gRPC บน Unix socket พร้อม call
GetProviderSchemaจริงที่จับได้ด้วยTF_LOG=TRACE - “
terragrunt run --allwalk unit ตามลำดับที่findแสดง” Terragrunt Discovery and DAG พิสูจน์ว่าลำดับมาจากdependency/dependenciesblock ที่สร้าง DAG ของ Terragrunt เอง แยกขาดจากลำดับตัวอักษรหรือลำดับ discovery — พิสูจน์ด้วย log จริงที่--parallelism 1แสดงplanของ dependency unit เสร็จก่อนที่ dependent ทั้งสองตัวจะเริ่มด้วยซ้ำ
อ่านโค้ดที่ AI เขียน
หัวข้อที่มีชื่อว่า “อ่านโค้ดที่ AI เขียน”ประเด็นทั้งหมดของโมดูลนี้คือ section นี้ 5 รอบ — บทละรอบ ตัวอย่างระดับภาพรวมก่อน: script “policy gate” ของ CI ที่ดูสมเหตุสมผลแต่จริง ๆ ไม่ได้เช็คอะไรเลยเงียบ ๆ
🐛 หา bug ในโค้ดนี้
#!/usr/bin/env bash# A teammate's "policy gate" before terraform apply in CIterraform plan -out=tfplan > plan_output.txtif grep -q "Plan: 0 to add, 0 to change, 0 to destroy" plan_output.txt; thenecho "no changes, skipping approval"elseecho "changes detected, needs approval"fiterraform apply tfplanเฉลย
script นี้ grep บรรทัดสรุปที่มนุษย์อ่าน ซึ่งเป็น string ที่เปราะบางต่อ localization และ formatting ไม่ใช่ interface ที่มั่นคง — Terraform ไม่เคย document ข้อความนั้นเป๊ะ ๆ ว่าเป็น API Plan File and JSON Output พิสูจน์ว่า interface ที่มั่นคงและเป็นทางการคือ terraform show -json tfplan ที่ expose resource_changes[].change.actions เป็น array จริง (["no-op"], ["create"], ["update"], ["delete","create"], ["forget"], …) ออกแบบมาเพื่อ use case นี้เป๊ะ ๆ — และ terraform plan -detailed-exitcode (พิสูจน์ในบทเดียวกัน: exit code 2 สำหรับ “มีการเปลี่ยนแปลง”, 0 สำหรับ “ไม่มีการเปลี่ยนแปลง”, 1 สำหรับ error จริง) แทนที่ grep ทั้งหมดด้วย exit code ที่ document ไว้ให้ script branch ได้ตรง ๆ โดยไม่ต้อง parse text เลย