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

Module Versioning and Registries

module source ที่ไม่ได้ pin version ไว้ สามารถ resolve ไปเป็น version ใหม่กว่าเดิมแบบเงียบ ๆ ในครั้งถัดไปที่มีคนรัน init ดังนั้นการ pin version constraint คือวินัยแบบเดียวกับที่คุณใช้กับ provider lock file อยู่แล้ว เพียงแค่เล็งไปที่ module code แทน provider code

ทำไม module version ที่ไม่ pin ถึงเป็นความเสี่ยงแบบเงียบ

หัวข้อที่มีชื่อว่า “ทำไม module version ที่ไม่ pin ถึงเป็นความเสี่ยงแบบเงียบ”

module ที่มาจาก Registry หรืออ้างอิงผ่าน Git tag ไม่ได้ตายตัวเหมือน local relative path ถ้า module block ไม่มี version constraint การรัน terraform init -upgrade — หรือแม้แต่ init ครั้งแรกใน CI pipeline ที่ยังไม่เคยรันมาก่อน — ก็สามารถดึง version ใหม่กว่าของ module นั้นเข้ามาได้ โดยไม่มีใครได้อ่าน changelog ก่อนเลย การ upgrade ที่ดูเหมือนเล็กน้อยอาจ rename variable, เปลี่ยนค่า default หรือปรับโครงสร้าง resource ที่สร้างขึ้น และทั้งหมดนี้จะไม่โผล่ให้เห็นจนกว่า plan หรือ apply จะเริ่มทำงานต่างจากเมื่อวาน

# unpinned - always resolves to the latest version on init -upgrade
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
}

นี่คือ failure mode เดียวกับที่ provider lock file มีไว้ป้องกัน แค่ขยับขึ้นมาอีกชั้นหนึ่ง แทนที่ provider plugin จะเปลี่ยนไปโดยที่คุณไม่รู้ตัว กลายเป็น implementation ภายในของ module เองที่เปลี่ยนไปโดยที่คุณไม่รู้ตัวแทน

การ pin module จาก Registry หรือ Git tag ด้วย version argument แก้ปัญหานี้ได้ pattern ที่ใช้กันทั่วไปคือ pessimistic constraint operator หรือ ~>

# pinned - allows 5.x patch and minor upgrades, blocks a 6.0 major bump
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.0"
}

~> 5.0 แปลว่า “อนุญาต version ใดก็ได้ที่มากกว่าหรือเท่ากับ 5.0 ไปจนถึงแต่ไม่รวม 6.0” พูดอีกแบบคือ ยอมให้ Terraform ดึง patch และ minor release ภายในสาย 5.x ได้ — bug fix กับของใหม่ที่ backward-compatible — แต่ปฏิเสธการกระโดดไป 6.0 แบบเงียบ ๆ เพราะ major version bump คือสัญญาณที่คนเขียน module บอกว่าให้เตรียมรับ breaking change คุณยังเป็นคนเลือกเองว่าจะกระโดดตอนไหน และอ่าน changelog ของ module ก่อนได้เสมอ

ไม่ใช่ทุก module ที่ควรอยู่บน public Registry module ที่เข้ารหัส networking layout, naming convention หรือ compliance requirement เฉพาะของบริษัทหนึ่ง ไม่ควรถูก publish ให้คนทั้งโลกเห็น สำหรับกรณีนี้ องค์กรมักใช้ private Git repository หรือ private module registry — private registry ของ HCP Terraform หรือตัวที่ self-host เอง — เพื่อให้ module ยังคงมี version และค้นหาได้ภายในองค์กร โดยไม่ต้อง public เลย

# private Git repository
module "internal_networking" {
source = "git::ssh://[email protected]/acme-corp/terraform-modules.git//networking?ref=v2.1.0"
}
# private module registry (e.g. HCP Terraform)
module "internal_networking" {
source = "app.terraform.io/acme-corp/networking/aws"
version = "~> 2.1"
}

ถ้าคุณต้องการ publish module ของตัวเองแบบ public จริง ๆ public Terraform Registry คาดหวัง naming convention ของ repository แบบเฉพาะ คือ terraform-<PROVIDER>-<NAME> เช่น terraform-aws-vpc Registry ใช้ชื่อนี้ในการแยกว่า module นี้เล็งไปที่ provider ไหนและควรเรียกว่าอะไร ดังนั้น repository ที่ไม่ตาม pattern นี้จะ publish ไม่ผ่าน

flowchart TB
  subgraph pinned["Pinned version"]
    p1["version = ~> 5.0"] --> p2["terraform init always resolves within 5.x"]
  end
  subgraph unpinned["Unpinned version"]
    u1["no version constraint"] --> u2["terraform init -upgrade may resolve 6.0"]
    u2 --> u3["breaking changes applied without changelog review"]
  end
A pinned module version versus an unpinned one that can silently resolve to a newer release
การปล่อย module source จาก Registry หรือ Git tag ไว้โดยไม่มี version constraint ทำให้เกิดความเสี่ยงอะไร
version = "~> 5.0" อนุญาตอะไร และกันอะไร
ทำไมองค์กรอาจใช้ private Git repository หรือ private module registry แทน public Terraform Registry สำหรับ module หนึ่ง
public Terraform Registry คาดหวัง naming convention ของ repository แบบไหนสำหรับการ publish module ของตัวเอง