Thiết kế Terraform Module như một Software Engineer
Terraform Module không chỉ là cách tái sử dụng code. Một module được thiết kế tốt sẽ giúp hạ tầng dễ bảo trì, mở rộng và sử dụng trong nhiều môi trường. Bài viết này chia sẻ tư duy thiết kế Terraform Module từ góc nhìn của một Software Engineer.

Thiết kế Terraform Module như một Software Engineer
Ở ba bài trước, chúng ta đã cùng nhau tìm hiểu ba khái niệm quan trọng nhất của Terraform:
- Infrastructure as Code là gì.
- Terraform hoạt động như thế nào.
- Terraform State đóng vai trò gì trong toàn bộ quá trình.
Đến đây, có lẽ bạn đã có thể viết được những file Terraform đầu tiên.
Nhưng sẽ rất nhanh thôi, bạn sẽ gặp một câu hỏi khác.
"Mình nên tổ chức Terraform như thế nào?"
Đây là lúc nhiều người bắt đầu đi chệch hướng.
Họ nghĩ rằng Terraform chỉ là một công cụ khai báo tài nguyên.
Họ viết tất cả mọi thứ vào một vài file .tf, copy chúng sang project khác, rồi tiếp tục chỉnh sửa từng nơi một.
Ban đầu mọi thứ vẫn hoạt động.
Cho đến khi hệ thống bắt đầu lớn lên.
Đó cũng là lúc bạn nhận ra rằng Infrastructure cũng cần được thiết kế giống như Software.
Terraform Module được sinh ra để giải quyết chính vấn đề này.
Chúng ta không copy Terraform
Hãy tưởng tượng công ty của bạn có ba dự án.
project-a/
main.tf
project-b/
main.tf
project-c/
main.tfMỗi project đều cần:
- VPC
- Subnet
- Security Group
- EC2
- IAM Role
Lúc đầu, cách đơn giản nhất là copy toàn bộ thư mục Terraform.
Project mới?
Copy.
Thêm môi trường mới?
Copy.
Khách hàng mới?
Copy tiếp.
Sau vài tháng, bạn sẽ có hàng chục thư mục gần giống hệt nhau.
Một ngày nào đó công ty quyết định:
"Tất cả EC2 phải bật Instance Metadata Service Version 2."
Nghe có vẻ đơn giản.
Nhưng lúc này bạn phải mở từng project.
Sửa từng file.
Review từng Pull Request.
Deploy từng môi trường.
Nếu bỏ sót một nơi, hệ thống sẽ không còn đồng nhất nữa.
Đây chính là vấn đề mà Software Engineering đã giải quyết từ nhiều năm trước.
Infrastructure cũng không ngoại lệ.
DRY cũng tồn tại trong Infrastructure
Có một nguyên tắc rất nổi tiếng trong lập trình.
DRY — Don't Repeat Yourself.
Nếu cùng một đoạn logic xuất hiện ở nhiều nơi, sớm hay muộn bạn cũng sẽ phải trả giá.
Terraform cũng vậy.
Ví dụ dưới đây hoàn toàn hợp lệ.
resource "aws_instance" "app1" {
...
}
resource "aws_instance" "app2" {
...
}
resource "aws_instance" "app3" {
...
}Terraform vẫn Apply bình thường.
Nhưng nếu ba EC2 này gần như giống nhau, việc copy resource nhiều lần sẽ khiến code ngày càng khó bảo trì.
Đó cũng là lý do chúng ta tạo Function trong Java.
Hay Component trong React.
Hay Service trong Spring Boot.
Terraform Module cũng là một cách để tái sử dụng những phần Infrastructure giống nhau.
Nhưng nếu chỉ nghĩ Module là "copy cho đỡ dài" thì chúng ta vẫn đang bỏ lỡ ý nghĩa thực sự của nó.
Terraform Module thực sự là gì?
Nhiều người mới học Terraform thường nghe một định nghĩa như sau:
Terraform Module là một thư mục chứa các file
.tf.
Định nghĩa này không sai.
Nhưng cũng không giúp chúng ta hiểu được bản chất.
Một cách nhìn đúng hơn là:
Terraform Module là một đơn vị đóng gói Infrastructure với một interface rõ ràng.
Ví dụ.
module "network"Bên trong module có thể bao gồm:
- VPC
- Public Subnet
- Private Subnet
- Route Table
- Internet Gateway
- NAT Gateway
Nhưng người sử dụng module không cần quan tâm.
Họ chỉ cần biết:
Input là gì.
Output là gì.
Còn cách triển khai bên trong hoàn toàn có thể thay đổi mà không ảnh hưởng đến nơi sử dụng.
Đây chính là khái niệm Encapsulation mà chúng ta vẫn gặp trong lập trình hướng đối tượng.
Terraform Module giống Class hơn là Folder
Đây là cách nhìn đã thay đổi hoàn toàn cách mình thiết kế Infrastructure.
Hãy so sánh.
Một Java Class.
public class DatabaseService {
}Có:
- Constructor
- Public Method
- Private Method
- Internal State
Terraform Module cũng vậy.
module "database" {
source = "./modules/database"
...
}Module cũng có:
- Input (
variables.tf) - Output (
outputs.tf) - Internal Resources (
main.tf) - Internal Logic (
locals.tf)
Người sử dụng chỉ tương tác với Interface.
Không ai cần biết bên trong tạo bao nhiêu Resource.
Nếu bạn bắt đầu xem Terraform Module giống như một Class thay vì một thư mục, cách thiết kế Infrastructure sẽ thay đổi hoàn toàn.
Một Module tốt nên có gì?
Hầu hết các Terraform Module đều có cấu trúc tương tự.
modules/
└── network
├── main.tf
├── variables.tf
├── outputs.tf
├── locals.tf
├── versions.tf
├── providers.tf
└── README.mdĐiều quan trọng không phải là số lượng file.
Mà là mỗi file chỉ nên có một trách nhiệm rõ ràng.
Ví dụ:
variables.tfđịnh nghĩa Input.outputs.tfđịnh nghĩa Output.locals.tfxử lý các giá trị trung gian.main.tfchứa Resource.versions.tfquản lý phiên bản Provider và Terraform.
Đây chính là tư duy Separation of Concerns mà chúng ta vẫn áp dụng trong Software Engineering.
Interface mới là thứ quan trọng nhất
Nhiều người dành rất nhiều thời gian tối ưu phần bên trong Module.
Trong khi điều quan trọng nhất lại là Interface.
Ví dụ.
Một Module có hơn 30 biến đầu vào.
module "ec2" {
...
}Để sử dụng được, người khác phải đọc hàng chục dòng tài liệu.
Trong khi một Module tốt thường chỉ expose những gì thực sự cần thiết.
Ví dụ.
module "web_server" {
name = "frontend"
instance_type = "t3.micro"
tags = local.common_tags
}Càng ít Input cần thiết.
Module càng dễ sử dụng.
Một Interface đơn giản luôn có giá trị hơn một Interface "linh hoạt" nhưng quá phức tạp.
Đừng để Module biết quá nhiều
Một lỗi mình gặp khá nhiều trong các dự án thực tế là tạo ra những "Mega Module".
Ví dụ.
Infrastructure Module
├── VPC
├── Subnet
├── EC2
├── IAM
├── ALB
├── Route53
├── CloudWatch
├── Auto Scaling
└── RDSNghe có vẻ tiện.
Chỉ cần gọi một Module là có toàn bộ hệ thống.
Nhưng thực tế, đây lại là một anti-pattern.
Module bắt đầu có quá nhiều trách nhiệm.
Mỗi thay đổi nhỏ đều có nguy cơ ảnh hưởng đến toàn bộ hệ thống.
Nếu bạn từng nghe đến khái niệm God Object trong OOP, thì đây chính là phiên bản Infrastructure của nó.
Hãy Compose thay vì nhồi nhét
Thay vì tạo một Module khổng lồ, hãy chia Infrastructure thành nhiều khối nhỏ.
Ví dụ.
network
database
compute
monitoringSau đó kết hợp chúng lại.
production
├── network
├── database
├── compute
└── monitoringĐây gọi là Composition.
Mỗi Module chỉ làm một việc.
Việc kết hợp chúng thành một hệ thống hoàn chỉnh được thực hiện ở tầng cao hơn.
Giống như việc chúng ta ghép nhiều Lego lại để tạo thành một mô hình lớn.
Chứ không phải đúc nguyên một khối bê tông ngay từ đầu.
Đừng quên Versioning
Một Module được nhiều project sử dụng sẽ sớm trở thành một thư viện dùng chung.
Lúc đó, Versioning là điều bắt buộc.
Ví dụ.
module "network" {
source = "git::https://github.com/company/iac-modules.git//network?ref=v1.2.0"
}Thay vì:
ref=mainSử dụng Tag hoặc Release giúp mỗi môi trường luôn biết chính xác mình đang dùng phiên bản nào.
Điều này cũng giúp việc nâng cấp, rollback hoặc kiểm thử trở nên an toàn hơn.
Những sai lầm mình từng gặp
Sau nhiều dự án Terraform, có một số lỗi mình gặp đi gặp lại rất nhiều lần.
- Module quá generic nên rất khó sử dụng.
- Module có quá nhiều Input.
- Nested Module quá sâu.
- Copy Module thay vì tái sử dụng.
- Hardcode Provider hoặc Region bên trong Module.
- Không expose Output cần thiết.
- Không có README hướng dẫn sử dụng.
Những lỗi này không khiến Terraform chạy sai.
Nhưng chúng khiến việc bảo trì Infrastructure ngày càng trở nên khó khăn.
Giống như Software, một hệ thống không chỉ cần chạy được.
Nó còn phải dễ đọc, dễ mở rộng và dễ bảo trì.
Kết luận
Terraform Module không tồn tại để giảm số dòng code.
Nó tồn tại để đóng gói kiến thức về hạ tầng thành những thành phần có thể tái sử dụng.
Một Module tốt không phải là Module có nhiều tính năng nhất.
Mà là Module có:
- Trách nhiệm rõ ràng.
- Interface đơn giản.
- Dễ kết hợp với Module khác.
- Dễ nâng cấp và bảo trì theo thời gian.
Nếu bạn bắt đầu nhìn Terraform Module giống như cách chúng ta thiết kế Class trong Software Engineering, bạn sẽ nhận ra rằng Infrastructure cũng có những nguyên tắc thiết kế không khác gì phần mềm.
Ở bài tiếp theo, chúng ta sẽ tiếp tục tìm hiểu một khái niệm rất quan trọng nhưng cũng rất dễ gây nhầm lẫn:
Terraform Provider hoạt động như thế nào? Và tại sao Provider không chỉ đơn giản là "plugin của Terraform".
Tiếp tục series
Bình luận
Đang tải bình luận…