Kết Hợp Module, Terraform Registry, và Pin Phiên Bản
Bài trước ta viết một module và gọi nó nhiều lần. Nhưng sức mạnh thật của module đến từ hai chỗ: nối nhiều module thành tầng lớn hơn, và dùng lại module người khác đã viết tốt thay vì tự làm từ đầu. Bài này làm cả hai: kết hợp module qua giao diện output/input, và kéo module từ Terraform Registry về với phiên bản pin chặt.
Mục tiêu
Biết cách nối các module (output của cái này thành input của cái kia), lấy module từ Registry với cú pháp source/version đúng, và hiểu vì sao pin phiên bản module là bắt buộc cho dự án ổn định.
Composition: nối module qua giao diện
Module có giao diện vào-ra rõ ràng (bài 12), nên nối chúng cũng gọn: output của module A đưa vào input của module B. Dây nối nằm ở root.
Ví dụ: một module tạo bucket an toàn (secure-bucket từ bài trước), một module khác nhận id bucket đó rồi đặt một object vào:
module "data" {
source = "./modules/secure-bucket"
name_prefix = "tf-series-bai13-data-"
force_destroy = true
}
module "seed" {
source = "./modules/seed-object"
bucket_id = module.data.id # nối module.data -> module.seed
}
Dòng bucket_id = module.data.id là chỗ kết hợp xảy ra. Output id của module data trở thành input bucket_id của module seed. Đây cũng tạo ra phụ thuộc ngầm (bài 5) ở mức module: Terraform biết phải dựng xong bucket của data trước khi seed đặt object vào.
module "data" module "seed"
┌──────────────┐ ┌──────────────────┐
│ secure-bucket│ │ seed-object │
│ │ │ │
│ output id ──┼────────►│ input bucket_id │
└──────────────┘ │ -> aws_s3_object │
└──────────────────┘
root nối dây: bucket_id = module.data.id
Apply rồi xem object đúng nằm trong bucket của module kia:
$ terraform state list
module.data.aws_s3_bucket.this
module.seed.aws_s3_object.readme
Cách nối này cho phép xây hạ tầng theo tầng: một module mạng cung cấp subnet id, một module compute nhận subnet id đó để đặt instance vào — chính là cấu trúc của bài capstone về sau.
Terraform Registry: module dùng chung
Bạn không phải tự viết mọi module. Terraform Registry chứa hàng nghìn module công khai, trong đó bộ terraform-aws-modules do cộng đồng duy trì cho AWS được dùng rộng rãi. Lấy một module từ Registry chỉ cần source đúng định dạng và version:
module "registry_bucket" {
source = "terraform-aws-modules/s3-bucket/aws"
version = "~> 5.0"
bucket_prefix = "tf-series-bai13-reg-"
force_destroy = true
}
source của module Registry có dạng <namespace>/<tên>/<provider>. Khác với module local (đường dẫn ./...), module Registry bắt buộc đi kèm version. Khi init, Terraform tải nó về:
$ terraform init
Initializing modules...
- seed in modules/seed-object
Downloading registry.terraform.io/terraform-aws-modules/s3-bucket/aws 5.13.0 for registry_bucket...
- registry_bucket in .terraform/modules/registry_bucket
- data in modules/secure-bucket
Ràng buộc ~> 5.0 được giải thành bản 5.x mới nhất, ở đây là 5.13.0. Phiên bản đã chọn ghi vào .terraform/modules/modules.json:
$ cat .terraform/modules/modules.json # rút gọn
data -> (local)
registry_bucket -> 5.13.0
seed -> (local)
Module local hiển thị (local) vì chúng không có khái niệm phiên bản — chúng là code trong dự án bạn. Chỉ module từ Registry (hoặc git có tag) mới có version để khóa.
Vì sao phải pin phiên bản
Pin phiên bản dễ bị bỏ qua mà gây sự cố lâu dài. Module công khai thay đổi: tác giả vá lỗi, thêm tính năng, và đôi khi đổi cấu trúc gây vỡ (breaking change) ở bản major. Nếu bạn không pin version, một ngày nào đó init trên máy khác hoặc trong CI sẽ kéo về bản mới hơn, và hạ tầng của bạn thay đổi mà bạn không hề sửa dòng code nào.
Pin bằng ~> 5.0 cho phép nhận mọi bản 5.x (vá lỗi, tính năng nhỏ) nhưng chặn nhảy sang 6.0 (có thể phá vỡ) — cùng triết lý với pin provider ở bài 2. Khi muốn nâng major, bạn đổi ràng buộc một cách có chủ đích, đọc changelog, test, rồi mới merge. Quy tắc: mọi module Registry và mọi nguồn git đều phải pin version; không bao giờ để module bên ngoài chạy "bản mới nhất" một cách tự động.
Module còn lấy được từ git (source = "git::https://...//modules/x?ref=v1.2.0"), từ S3, hay từ một registry riêng của công ty. Với git, ?ref= đóng vai trò pin phiên bản (trỏ tới tag). Dù nguồn nào, nguyên tắc khóa phiên bản không đổi.
🧹 Dọn dẹp
$ terraform destroy -auto-approve
Destroy complete! Resources: 7 destroyed.
Tổng kết
Kết hợp module bằng cách nối output của module này vào input của module kia ngay tại root (bucket_id = module.data.id), tạo phụ thuộc ngầm ở mức module và cho phép xây hạ tầng theo tầng. Terraform Registry cung cấp module dùng chung với source dạng <namespace>/<tên>/<provider>, bắt buộc kèm version; init tải về và khóa phiên bản đã chọn trong modules.json. Pin phiên bản (~> 5.0 cho Registry, ?ref=tag cho git) là bắt buộc để dự án không thay đổi sau lưng bạn khi module bên ngoài cập nhật.
Bài tới ráp mọi thứ của Part IV thành một module thực tế và hữu dụng: một module mạng dựng VPC với subnet, internet gateway và route table, rồi đặt EC2 vào trong đó — đúng kiểu module hạ tầng nền mà gần như dự án nào cũng cần.