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

DRY Configuration with include

include block คือวิธีที่ terragrunt.hcl ของ unit หนึ่งดึงทุกอย่างที่ประกาศไว้ใน root.hcl หรือใน intermediate config file ไหนก็ตามเข้ามาใช้ โดยไม่ต้องพิมพ์ซ้ำสักบรรทัด

vpc/terragrunt.hcl ในบทที่แล้วเปิดด้วย block นี้ ตอนนี้ถึงเวลาอธิบายให้ชัดเจน

prod/us-east-1/vpc/terragrunt.hcl
include "root" {
path = find_in_parent_folders("root.hcl")
}

find_in_parent_folders("root.hcl") เดินขึ้น directory tree จากตำแหน่งของ unit ปัจจุบัน ทีละชั้น จนกว่าจะเจอไฟล์ที่ชื่อ root.hcl เป๊ะ ๆ จากนั้น include "root" จะ merge ทุกอย่างที่ไฟล์นั้นประกาศไว้เข้ามาใน config ของ unit ปัจจุบัน เหมือนกับพิมพ์ไว้ตรงนั้นเอง ทั้ง remote_state block, backend.tf ที่ generate ไว้, provider generate block ทั้งหมด

ที่การเดินหาแบบนี้สำคัญก็เพราะ include บรรทัดเดียวกันเป๊ะใช้ได้ทั้งกับ unit ที่อยู่ต่ำกว่า root.hcl แค่ชั้นเดียวหรือลึกไปห้าชั้น ย้าย unit ไปอยู่ subdirectory ใหม่ตอน reorganize โครงสร้าง บรรทัดนี้ก็ยังทำงานเหมือนเดิมไม่ต้องแก้ เพราะไม่เคย hard-code relative path แบบ ../../../root.hcl ที่จะพังทันทีที่โครงสร้าง tree เปลี่ยนไป

include ไม่ใช่กลไก inherit เดียวที่มี และไม่ใช่ตัวเลือกที่ถูกเสมอไป บางครั้งคุณอยากแชร์ค่าที่เล็กกว่าและเจาะจงกว่าลงไปแค่บางส่วนของ tree ไม่ใช่ทั้งหมด ลองนึกภาพไฟล์ region.hcl ตัวกลางที่อยู่ต่ำกว่า root จริงหนึ่งชั้น ครอบคลุมเฉพาะ unit ที่อยู่ใน AWS region หนึ่งเจาะจง

prod/us-east-1/region.hcl
locals {
aws_region = "us-east-1"
}

unit ที่อยู่ใต้ directory ของ region นั้นอ่านไฟล์นั้นด้วย read_terragrunt_config() ผสมกับ find_in_parent_folders() ตัวเดิมที่คุณรู้จักแล้ว

prod/us-east-1/vpc/terragrunt.hcl
include "root" {
path = find_in_parent_folders("root.hcl")
}
locals {
region_vars = read_terragrunt_config(find_in_parent_folders("region.hcl"))
}
terraform {
source = "git::[email protected]:acme/infrastructure-modules.git//vpc?ref=v1.4.0"
}
inputs = {
aws_region = local.region_vars.locals.aws_region
cidr_block = "10.0.0.0/16"
}

read_terragrunt_config() parse Terragrunt config ไฟล์อื่นแล้วส่งเนื้อหากลับมาเป็นค่า และ locals block ของไฟล์นั้นจะซ้อนอยู่ใต้ .locals ในผลลัพธ์ จึงเขียนเป็น local.region_vars.locals.aws_region unit ที่อยู่ใต้ prod/ap-southeast-1/ ก็จะมี region.hcl ของตัวเองที่ประกาศ aws_region = "ap-southeast-1" ส่วน unit ไหนก็ตามในทั้ง tree ยังคง share root.hcl เดียวกันสำหรับ backend และ provider root เดียวกัน แต่ region ต่างกันในแต่ละ subtree โดยไม่มี unit ไหนต้อง hard-code region ของตัวเองไว้ตรง ๆ เลย

ทุกตัวอย่างที่ผ่านมาจบด้วย inputs = { ... } block ทั้งนั้น และสมควรมีคำอธิบายของตัวเอง นี่คือวิธีที่ unit ส่ง input variable ของ Terraform module ที่อยู่ข้างใต้เข้าไป ทำหน้าที่เหมือน .tfvars file ที่ใช้กับ Terraform root module ธรรมดาทุกประการ เพียงแต่เขียนตรง ๆ เป็น Terragrunt HCL แทนที่จะเป็นไฟล์แยกที่ต้องบอก Terraform ให้โหลด แต่ละ key ใน inputs แม็พไปที่ variable ที่ module ประกาศไว้ และ Terragrunt ส่ง block ทั้งหมดผ่านไปให้ Terraform เป็นค่า -var อยู่เบื้องหลัง

เพราะ inputs คือ HCL จริง ไม่ใช่ .tfvars file แบบ static จึงอ้างอิงอะไรก็ได้ในไฟล์ config เดียวกัน local.region_vars.locals.aws_region ข้างบนคือตัวอย่างหนึ่ง และบทถัดไปจะแนะนำการอ้างอิง output ของ unit อื่นด้วยวิธีเดียวกันนี้

flowchart TD
  root["root.hcl (remote_state + generate)"]
  region["prod/us-east-1/region.hcl (locals.aws_region)"]
  unit["prod/us-east-1/vpc/terragrunt.hcl"]
  root -->|include + find_in_parent_folders| unit
  region -->|read_terragrunt_config + find_in_parent_folders| unit
  unit --> inputs["inputs = aws_region, cidr_block"]
root.hcl กับ region.hcl รวมกันเป็น config ของ unit หนึ่งตัว
include block ที่มี find_in_parent_folders ทำอะไรจริง ๆ
read_terragrunt_config ใช้ทำอะไรกับไฟล์ตัวกลางอย่าง region.hcl
inputs block ใน terragrunt.hcl ของ unit หนึ่งเทียบเท่ากับอะไร
ทำไม find_in_parent_folders ยังทำงานได้ปกติหลังจาก unit ถูกย้ายให้ลึกขึ้นไปอยู่ใน subdirectory ใหม่