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

Dependencies Between Units

dependency block ทำให้ unit หนึ่งอ่าน output ของอีก unit ข้าม state boundary ที่แยกทั้งสองออกจากกันได้ และ Terragrunt ก็ใช้ block เดียวกันนี้คำนวณลำดับที่ unit ต่าง ๆ ต้องถูก apply

ใน module Modules คุณเห็น output-to-input composition แล้ว output ของ module หนึ่งป้อนตรงเข้า input argument ของอีก module ภายใน main.tf เดียว evaluate เป็น Terraform plan เดียวกับ state ไฟล์เดียว วิธีนั้นทำงานได้เพราะทั้งสอง module อยู่ใน apply เดียวกัน

Terragrunt unit ตั้งใจทำลาย assumption นั้นทิ้ง unit vpc กับ unit ec2 ต่างมี terragrunt.hcl ของตัวเองและ state ไฟล์แยกของตัวเอง apply กันอย่างอิสระ module.vpc.outputs.vpc_id ที่อ้างอิงตรง ๆ แบบตอนอยู่ใน Terraform config เดียว จากมุมมองของ unit ec2 reference นั้นไม่มีอยู่จริงเลย เพราะไม่มี config ไฟล์ที่ share กันให้ reference นั้นอยู่ข้างใน ต้องมีอะไรสักอย่างอ่านข้าม state boundary นั้นตรง ๆ และสิ่งนั้นคือ dependency block

ec2/terragrunt.hcl
include "root" {
path = find_in_parent_folders("root.hcl")
}
terraform {
source = "git::[email protected]:acme/infrastructure-modules.git//ec2?ref=v1.4.0"
}
dependency "vpc" {
config_path = "../vpc"
mock_outputs = {
vpc_id = "mock-vpc-id"
}
mock_outputs_allowed_terraform_commands = ["plan"]
}
inputs = {
vpc_id = dependency.vpc.outputs.vpc_id
}

config_path ชี้ไปที่ directory ของอีก unit หนึ่ง ไม่ใช่ Terraform module แต่เป็น Terragrunt unit ก่อนรัน Terragrunt จะรัน terraform output กับ state จริงของ unit นั้น แล้ว expose ทุก output ที่เจอเป็น dependency.vpc.outputs.<name> ให้ใช้ได้ทุกที่ใน config ของ unit นี้ รวมถึงข้างใน inputs ด้วย

dependency.vpc.outputs.vpc_id จะมีค่าจริงก็ต่อเมื่อ unit vpc ถูก apply ไปแล้วอย่างน้อยหนึ่งครั้ง มีสองสถานการณ์ที่ทำลาย assumption นี้ คือตอนตั้ง environment ใหม่เอี่ยมครั้งแรก ที่ยังไม่มีอะไรถูก apply เลย และตอน CI validate pull request ที่แก้แค่ไฟล์ของ unit ec2 ซึ่งไม่มีใครอยากให้ plan fail แค่เพราะ unit อื่นที่ไม่เกี่ยวข้องยังไม่ถูก apply ใน environment นั้น

mock_outputs มาปิด gap นี้พอดี โดยให้ค่า placeholder ที่ Terragrunt สลับใช้แทนทุกครั้งที่ output จริงยังไม่มี และ mock_outputs_allowed_terraform_commands จำกัดว่า command ไหนถึง fallback ไปใช้ค่านั้นได้ ["plan"] ข้างบนแปลว่า plan บน ec2 สำเร็จได้ด้วย vpc_id = "mock-vpc-id" แม้ยังไม่มี state จริงของ vpc เลย ส่วน apply ยังคงปฏิเสธไม่ยอมรันกับค่าปลอม และบังคับให้ dependency จริงถูก apply ก่อนเสมอ

Terraform เองก็คำนวณลำดับการรันภายใน state เดียวอยู่แล้ว คือ dependency graph ระหว่าง resource แต่ละตัว dependency block ส่งข้อมูลแบบเดียวกันนี้ให้ Terragrunt ในระดับที่สูงขึ้นมาอีกชั้น คือระหว่าง unit ทั้งก้อน เพราะ ec2 ประกาศ dependency ไปที่ vpc Terragrunt ก็รู้ว่าต้อง apply vpc ก่อน ขยายแบบนี้ไปทั่ว tree ที่มีหลายสิบ unit จริง ๆ Terragrunt จะสร้างกราฟเต็มรูปแบบจากทุก dependency block ที่เจอ แล้วเดินตามลำดับที่ถูกต้องเวลามี command ไหนก็ตามที่แตะมากกว่าหนึ่ง unit พร้อมกัน ซึ่งจะพูดถึงเต็ม ๆ ในบทถัดไป โดยไม่ต้องมีใครจำหรือ maintain ลำดับนั้นด้วยมือเอง

flowchart LR
  vpcstate["vpc unit state (applied separately)"] --> vpcout["vpc outputs: vpc_id"]
  vpcout --> dep["ec2 terragrunt.hcl: dependency vpc block"]
  dep --> ecin["ec2 inputs: vpc_id = dependency.vpc.outputs.vpc_id"]
  mock["mock_outputs: vpc_id = mock-vpc-id"] -.->|lets plan succeed before vpc is applied| dep
dependency block ส่ง output ของ vpc เข้าไปเป็น inputs ของ ec2
ทำไม output-to-input module composition จาก module Modules ถึงไม่พอสำหรับ Terragrunt unit สองตัว
mock_outputs ให้อะไร
Terragrunt คำนวณลำดับ apply ที่ถูกต้องข้าม unit ที่พึ่งพากันหลายตัวได้ยังไง
mock_outputs_allowed_terraform_commands ควบคุมอะไร