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

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 ไม่ได้ถูก freeze ไว้กับที่เหมือน local relative path ถ้า module block ไม่มี version constraint การรัน terraform init -upgrade — หรือแม้แต่ init ใหม่ใน CI pipeline ที่ไม่เคยรันมาก่อน — สามารถดึง release ใหม่กว่าของ module นั้นเข้ามาโดยไม่มีใคร review changelog ก่อน upgrade ที่ดูเหมือนเล็กน้อยสามารถเปลี่ยนชื่อ variable เปลี่ยน default หรือปรับโครงสร้าง resource ที่สร้างขึ้นใหม่ทั้งหมด และไม่มีอะไรแสดงให้เห็นจนกว่า plan หรือ apply จะเริ่มทำงานต่างจากเมื่อวาน

# unpinned - always resolves to the latest version on init -upgrade
module "vnet" {
source = "Azure/vnet/azurerm"
}

นี่คือ 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 "vnet" {
source = "Azure/vnet/azurerm"
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 changes คุณยังเป็นคนเลือกเองว่าจะกระโดดตอนไหน และอ่าน changelog ของ module ก่อนได้

ไม่ใช่ทุก module ที่ควรอยู่บน public Registry module ที่ encode networking layout, naming convention หรือ compliance requirement เฉพาะขององค์กรหนึ่งไม่ควรถูก publish ให้คนทั้งโลกเห็น สำหรับกรณีแบบนี้ องค์กรมักใช้ private Git repository หรือ private module registry — private registry ของ HCP Terraform หรือของที่ self-hosted เอง — เพื่อให้ 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/azurerm"
version = "~> 2.1"
}

ถ้าคุณต้องการ publish module ออกสู่สาธารณะ public Terraform Registry ต้องการ repository naming convention เฉพาะแบบนี้: terraform-<PROVIDER>-<NAME> เช่น terraform-azurerm-vnet Registry parse ชื่อนี้เพื่อดูว่า 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
การปล่อย Registry หรือ Git-tag module source ไว้โดยไม่มี version constraint สร้างความเสี่ยงอะไร
version = "~> 5.0" อนุญาตอะไรและบล็อกอะไร
ทำไมองค์กรอาจใช้ private Git repository หรือ private module registry แทน public Terraform Registry สำหรับ module หนึ่ง
public Terraform Registry ต้องการ repository naming convention แบบไหนสำหรับการ publish module ของตัวเอง