Module Composition and Sources
ไอเดียในหนึ่งประโยค
หัวข้อที่มีชื่อว่า “ไอเดียในหนึ่งประโยค”source argument ใน module block บอกว่าโค้ดของ module นั้นอยู่ที่ไหน — local path, public Terraform Registry หรือ Git URL — และ infrastructure จริงถูกสร้างขึ้นด้วยการเอา output ของ module หนึ่งไปต่อเป็น variable ของอีก module ไม่ใช่เขียน configuration แบนราบก้อนเดียว
source argument: โค้ดของ module มาจากไหน
หัวข้อที่มีชื่อว่า “source argument: โค้ดของ module มาจากไหน”module block ทุกตัวต้องมี source argument และ Terraform รองรับรูปแบบหลักสามแบบ แบบง่ายที่สุดคือ local relative path ชี้ไปที่ directory ของไฟล์ .tf ที่อยู่ใน repository เดียวกับตัวที่เรียกใช้
module "vpc" { source = "./modules/vpc"
project_id = var.project_id network_name = "app-network"}local path เหมาะตอนที่ module ยังผูกกับ repository เดียวอยู่ หรือตอนที่คุณกำลังพัฒนาไปพร้อมกับโค้ดที่เรียกใช้ แต่พอ module reuse ได้จริงข้ามหลาย configuration คุณมักอยากเผยแพร่ไว้ที่ที่ทุก caller เข้าถึงได้เองโดยไม่ต้องผูกกับ repository ใดเป็นพิเศษ — ตรงนี้แหละที่ public Terraform Registry กับ Git source เข้ามามีบทบาท
public Terraform Registry เก็บ module จากชุมชนและ vendor ต่าง ๆ ภายใต้ address รูปแบบ namespace/name/provider ซึ่ง terraform init resolve ให้อัตโนมัติโดยไม่ต้องใช้ URL เลย
module "vpc" { source = "terraform-google-modules/network/google" version = "~> 9.0"
project_id = var.project_id network_name = "app-network"}Git URL คือรูปแบบที่สาม เหมาะกับ module ส่วนตัวหรือภายในองค์กรที่ไม่ได้เผยแพร่ขึ้น registry ไหนเลย การต่อ ?ref= ท้าย URL คือการ pin source ไว้ที่ tag, branch หรือ commit ที่ระบุ เหมือนกับที่ version pin module จาก Registry
module "vpc" { source = "git::https://github.com/acme-corp/terraform-modules.git//vpc?ref=v2.3.0"
project_id = var.project_id network_name = "app-network"}สังเกต double slash ก่อนคำว่า vpc ใน Git URL — ทุกอย่างก่อนหน้านั้นคือ repository ที่จะ clone และทุกอย่างหลังจากนั้นคือ subdirectory ภายใน repository นั้นที่ module ตัวจริงอยู่ เรื่องนี้สำคัญเพราะ repository เดียวมักเก็บหลาย module ไว้ด้วยกัน ไม่ใช่แค่ตัวเดียว
ประกอบ module เข้าด้วยกัน: ส่ง output ไปเป็น input
หัวข้อที่มีชื่อว่า “ประกอบ module เข้าด้วยกัน: ส่ง output ไปเป็น input”infrastructure ที่ซับซ้อนจริง ๆ แทบไม่เคยเป็น configuration แบนราบก้อนเดียว แต่เป็น module เล็ก ๆ ที่ focus ชัดเจนหลายตัวต่อกัน โดย output ของ module หนึ่งกลายเป็น input variable ของ module ถัดไปโดยตรง ลองดู vpc module ที่สร้าง network กับ subnet ของตัวเอง และ compute-instance module ที่ต้องรู้ว่าจะ attach instance เข้ากับ network และ subnetwork ไหน
output "network_id" { description = "Self link of the VPC network created by this module" value = google_compute_network.this.id}
output "subnet_ids" { description = "Map of region to subnetwork ID" value = { for s in google_compute_subnetwork.this : s.region => s.id }}variable "network_id" { description = "ID of the VPC network to attach the instance to" type = string}
variable "subnet_id" { description = "ID of the subnetwork to attach the instance to" type = string}root configuration instantiate module ทั้งสองตัวและส่ง output ของตัวแรกเข้าไปเป็น input ของตัวที่สองตรง ๆ โดยไม่ต้อง copy ID ด้วยมือระหว่างทาง
# root main.tfmodule "vpc" { source = "./modules/vpc" project_id = var.project_id network_name = "app-network"}
module "compute_instance" { source = "./modules/compute-instance" project_id = var.project_id network_id = module.vpc.network_id subnet_id = module.vpc.subnet_ids["us-central1"]}Terraform resolve dependency ตรงนี้ให้อัตโนมัติ เพราะ module.compute_instance อ้างอิงถึง module.vpc.network_id Terraform จึงรู้ว่าต้องสร้าง network ก่อน instance โดยที่คุณไม่ต้องเขียน depends_on เพิ่มเลย
ใช้ public Terraform Registry ให้เป็นประโยชน์
หัวข้อที่มีชื่อว่า “ใช้ public Terraform Registry ให้เป็นประโยชน์”การเขียน vpc module เองตั้งแต่ศูนย์เป็นแบบฝึกหัดที่ดี แต่สำหรับงาน production มักไม่ใช่การใช้เวลาที่คุ้มค่าที่สุด องค์กร terraform-google-modules บน public Registry ดูแล module อย่าง terraform-google-modules/network/google ที่จัดการรายละเอียดยิบย่อยของ GCP networking ไว้แล้ว — custom subnet range, secondary range สำหรับ GKE, Shared VPC, Cloud NAT — ผ่านการใช้งานจริงจากชุมชนจำนวนมากที่เจอ edge case เดียวกับที่คุณจะเจอเองในที่สุด
แต่ module จาก Registry ก็ยังเป็นโค้ดของคนอื่นที่รันใน project ของคุณด้วย credential ของคุณ ก่อนจะพึ่งพาใน production ควรอ่านให้รู้ว่า module สร้างอะไรจริง ๆ — main.tf, ค่า default, IAM role หรือ API ที่เปิดใช้แทนคุณ module จาก Registry ที่ดูแลดีคือ productivity lever จริง ไม่ใช่ตัวแทนของการเข้าใจว่าอะไรถูก deploy เข้า GCP project ของคุณ
flowchart LR
subgraph root["Root module"]
rvpc["module.vpc"]
rci["module.compute_instance"]
end
subgraph vpcmod["Child module: modules/vpc"]
vout1["output: network_id"]
vout2["output: subnet_ids"]
end
subgraph cimod["Child module: modules/compute-instance"]
cin1["variable: network_id"]
cin2["variable: subnet_id"]
end
rvpc --> vout1
rvpc --> vout2
vout1 -->|module.vpc.network_id| rci
vout2 -->|module.vpc.subnet_ids| rci
rci --> cin1
rci --> cin2