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

Dependencies Between Units

dependency block ทำให้ unit หนึ่งอ่าน output ของอีก unit ข้าม state boundary ที่แยกทั้งสองออกจากกันได้ โดย mock_outputs ทำหน้าที่แทนค่าจริงไปก่อน จนกว่าจะ apply unit อีกฝั่งจริง

โมดูล Modules ก่อนหน้านี้แสดงให้เห็นว่า output ของ module หนึ่งไหลตรงเข้าไปเป็น input ของอีก module ได้ ทั้งหมดอยู่ใน main.tf เดียว ถูก resolve โดย Terraform ใน plan เดียวกัน กับ state ที่แชร์กันชุดเดียว วิธีนี้ทำงานได้เพราะทั้งสอง module เป็นส่วนหนึ่งของ Terraform run เดียวกัน Terraform จึงเห็น dependency graph ทั้งหมดในครั้งเดียว

unit ตั้งใจทำลายสมมติฐานนั้น unit vnet และ unit vm ต่างมี terragrunt.hcl และ state file แยกกันของตัวเอง apply แยกกันอย่างอิสระ unit vm ยังต้องการ subnet ID ที่ vnet สร้างขึ้นอยู่ดี แต่ไม่มี Terraform run เดียวที่เห็นทั้งสอง unit นี้อีกต่อไป จากมุมมองของ Terraform ทั้งสอง unit เป็นโปรเจกต์คนละเรื่องกันโดยสิ้นเชิง Terragrunt จึงต้องมีกลไกของตัวเองสำหรับส่งค่าจาก state ของ unit หนึ่ง ข้าม boundary นั้นเข้าไปใน inputs ของอีก unit และกลไกนั้นคือ dependency block

dependency block ระบุชื่อ unit อื่นด้วย directory ของ unit นั้น แล้ว expose ทุก output ของ unit นั้นออกมา

vm/terragrunt.hcl
include "root" {
path = find_in_parent_folders("root.hcl")
}
dependency "vnet" {
config_path = "../vnet"
}
inputs = {
subnet_id = dependency.vnet.outputs.subnet_id
}

config_path = "../vnet" ชี้ไปที่ directory ของ unit vnet เบื้องหลัง Terragrunt จะรัน terragrunt output กับ unit นั้น แล้ว expose ผลลัพธ์ออกมาภายใต้ dependency.vnet.outputs ทำให้ dependency.vnet.outputs.subnet_id เข้าถึงสิ่งที่ Terraform module ของ unit vnet ประกาศไว้เป็น output "subnet_id" ได้โดยตรง จาก terragrunt.hcl ของ unit vm เอง โค้ดตรงนี้อ่านได้เกือบเหมือนกับการใช้ output ของ module ภายใน main.tf เดียวกันเลย ความต่างมองไม่เห็นในตัว syntax แม้ว่าจริง ๆ แล้วมี apply และ state file แยกกันโดยสิ้นเชิงอยู่เบื้องหลัง dependency.vnet.outputs

มีปัญหาชัดเจนอยู่ในโครงสร้างข้างต้น ครั้งแรกที่ใครก็ตามรัน terragrunt plan กับ unit vm unit vnet อาจจะยังไม่ถูก apply เลยก็ได้ ทำให้ dependency.vnet.outputs.subnet_id ไม่มีค่าจริงให้อ่าน mock_outputs แก้ปัญหานี้ได้พอดี

dependency "vnet" {
config_path = "../vnet"
mock_outputs = {
subnet_id = "mock-subnet-id"
}
mock_outputs_allowed_terraform_commands = ["plan"]
}

เมื่อ output จริงยังไม่พร้อมใช้งาน Terragrunt จะแทนที่ด้วยค่าใน mock_outputs แทนการ fail ไปเลย ทำให้ plan แสดงสิ่งที่ unit vm จะสร้างได้ แม้ว่า vnet จะยังไม่มีอยู่จริงก็ตาม mock_outputs_allowed_terraform_commands จำกัดว่า command ไหนบ้างที่อนุญาตให้ fallback ไปใช้ mock ได้ การระบุแค่ plan ในตัวอย่างนี้ หมายความว่า apply ยัง fail อย่างชัดเจนถ้า dependency จริงยังไม่ถูก apply นั่นคือ safety net ที่ต้องการพอดี การ preview plan กับ subnet ID ปลอมนั้นไม่มีปัญหา แต่การสร้าง virtual machine จริงกับค่าปลอมนั้นไม่ควรเกิดขึ้นเด็ดขาด

ไม่มีตรงไหนใน terragrunt.hcl ของ unit vm ที่บอกว่า “apply vnet ก่อน” Terragrunt คำนวณเรื่องนี้ให้เองทั้งหมด ทุก dependency block ที่เจอระหว่างสแกน tree ของ unit ต่าง ๆ จะกลายเป็น edge หนึ่งใน dependency graph และเมื่อรัน operation ข้าม unit หลายตัวพร้อมกัน Terragrunt จะใช้ graph นั้นเป็นเงื่อนไขลำดับ vnet รับประกันว่าจะ apply เสร็จก่อนที่ vm จะเริ่ม เพราะ vm ประกาศ dependency ชี้ไปหา ไม่ต้อง hard-code ลำดับนี้หรือจำเพื่อรัน command ตามลำดับที่ถูกต้องด้วยมือเลย บทถัดไปจะพูดถึง command ที่ใช้ trigger การรันข้ามหลาย unit จริง ๆ

flowchart LR
  vnetunit["vnet unit (own state)"]
  vnetout["outputs.subnet_id"]
  dep["vm/terragrunt.hcl dependency vnet"]
  vminputs["vm inputs.subnet_id"]
  vnetunit --> vnetout --> dep --> vminputs
  mock["mock_outputs (plan only, before vnet is applied)"] -.-> dep
output ของ vnet ไหลเข้า vm ผ่าน dependency block โดย mock_outputs เชื่อมช่องว่างก่อน apply vnet
ทำไม module composition จาก main.tf เดียวถึงไม่พอ เมื่อ vnet กับ vm กลายเป็น Terragrunt unit แยกกัน
mock_outputs ให้ unit ทำอะไรได้
Terragrunt กำหนดลำดับ apply ที่ถูกต้องข้ามหลาย unit ที่พึ่งพากันได้อย่างไร
ใน dependency "vnet" { config_path = "../vnet" } นั้น config_path ชี้ไปที่อะไร