TLDR: Các mô-đun là cấu hình Terraform có thể tái sử dụng. Đặt các tài nguyên liên quan vào một thư mục có các biến và kết quả đầu ra, sau đó gọi mô-đun đó nhiều lần với các đầu vào khác nhau. Sử dụng các mô-đun để tránh lặp lại, thực thi các tiêu chuẩn và chia sẻ mô hình cơ sở hạ tầng giữa các nhóm.
Khi cấu hình Terraform của bạn phát triển, các mô-đun sẽ giúp sắp xếp mã thành các thành phần có thể tái sử dụng. Thay vì sao chép các định nghĩa về cơ sở hạ tầng, hãy đóng gói chúng dưới dạng mô-đun và sử dụng chúng khi cần thiết.
Mô-đun là gì?
Mỗi cấu hình Terraform là một mô-đun. Các tệp trong thư mục làm việc của bạn tạo thành "mô-đun gốc". Khi bạn tạo các thư mục con có cấu hình riêng, chúng sẽ trở thành "mô-đun con" mà mô-đun gốc có thể gọi.
Một mô-đun chỉ là một thư mục chứa:
- Định nghĩa tài nguyên (tệp
.tf) - Khai báo biến (đầu vào)
- Khai báo đầu ra (giá trị trả về)
- Tùy chọn: tài liệu, ví dụ, bài kiểm tra
project/
├── main.tf # Root module
├── variables.tf
├── outputs.tf
└── modules/ # Child modules
├── vpc/
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
└── web_server/
├── main.tf
├── variables.tf
└── outputs.tf
Tạo mô-đun đầu tiên của bạn
Hãy tạo một mô-đun cho máy chủ web có nhóm bảo mật.
Tạo modules/web_server/main.tf:
# modules/web_server/main.tf
resource "aws_security_group" "web" {
name = "${var.name}-sg"
description = "Security group for ${var.name}"
vpc_id = var.vpc_id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
description = "HTTP from anywhere"
}
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
description = "HTTPS from anywhere"
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
description = "All outbound traffic"
}
tags = merge(
var.tags,
{
Name = "${var.name}-sg"
}
)
}
resource "aws_instance" "web" {
ami = var.ami_id
instance_type = var.instance_type
subnet_id = var.subnet_id
vpc_security_group_ids = [aws_security_group.web.id]
user_data = var.user_data
root_block_device {
volume_size = var.disk_size
}
tags = merge(
var.tags,
{
Name = var.name
}
)
}
Xác định đầu vào trong modules/web_server/variables.tf:
# modules/web_server/variables.tf
variable "name" {
description = "Name for the web server"
type = string
}
variable "vpc_id" {
description = "VPC ID where resources will be created"
type = string
}
variable "subnet_id" {
description = "Subnet ID for the instance"
type = string
}
variable "ami_id" {
description = "AMI ID for the instance"
type = string
}
variable "instance_type" {
description = "EC2 instance type"
type = string
default = "t3.micro"
}
variable "disk_size" {
description = "Root disk size in GB"
type = number
default = 20
}
variable "user_data" {
description = "User data script"
type = string
default = ""
}
variable "tags" {
description = "Tags to apply to resources"
type = map(string)
default = {}
}
Xác định đầu ra trong modules/web_server/outputs.tf:
# modules/web_server/outputs.tf
output "instance_id" {
description = "ID of the EC2 instance"
value = aws_instance.web.id
}
output "instance_public_ip" {
description = "Public IP address"
value = aws_instance.web.public_ip
}
output "instance_private_ip" {
description = "Private IP address"
value = aws_instance.web.private_ip
}
output "security_group_id" {
description = "ID of the security group"
value = aws_security_group.web.id
}
Sử dụng mô-đun
Bây giờ hãy sử dụng mô-đun này trong cấu hình gốc của bạn:
# main.tf
module "web_server" {
source = "./modules/web_server"
name = "production-web"
vpc_id = aws_vpc.main.id
subnet_id = aws_subnet.public.id
ami_id = data.aws_ami.ubuntu.id
instance_type = "t3.small"
disk_size = 30
user_data = <<-EOF
#!/bin/bash
apt-get update
apt-get install -y nginx
systemctl start nginx
EOF
tags = {
Environment = "production"
Project = "web-app"
}
}
# Access module outputs
output "web_server_ip" {
value = module.web_server.instance_public_ip
}
Đối số source cho Terraform biết nơi tìm mô-đun. Nó có thể là:
- Đường dẫn cục bộ:
./modules/web_server - Kho lưu trữ Git:
git::https://github.com/user/terraform-modules.git//web_server?ref=v1.0.0 - Sổ đăng ký Terraform:
terraform-aws-modules/vpc/aws(hiển thị sau) - URL HTTP:
https://example.com/terraform-modules/web_server.zip
Khi bạn chạy terraform init, Terraform sẽ tải xuống và lưu trữ các mô-đun từ các nguồn từ xa.
Nhiều phiên bản mô-đun
Sử dụng cùng một mô-đun nhiều lần với các cấu hình khác nhau:
module "web_server_1" {
source = "./modules/web_server"
name = "web-1"
vpc_id = aws_vpc.main.id
subnet_id = aws_subnet.public_a.id
ami_id = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
tags = {
Environment = "dev"
Role = "web"
}
}
module "web_server_2" {
source = "./modules/web_server"
name = "web-2"
vpc_id = aws_vpc.main.id
subnet_id = aws_subnet.public_b.id
ami_id = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
tags = {
Environment = "dev"
Role = "web"
}
}
module "api_server" {
source = "./modules/web_server"
name = "api"
vpc_id = aws_vpc.main.id
subnet_id = aws_subnet.private.id
ami_id = data.aws_ami.ubuntu.id
instance_type = "t3.small"
tags = {
Environment = "dev"
Role = "api"
}
}
Mỗi lệnh gọi mô-đun tạo ra tập hợp tài nguyên riêng.
Số lượng mô-đun và For_Each
Tạo nhiều phiên bản của toàn bộ mô-đun:
variable "web_servers" {
type = map(object({
subnet_id = string
instance_type = string
}))
default = {
web-1 = {
subnet_id = "subnet-abc123"
instance_type = "t3.micro"
}
web-2 = {
subnet_id = "subnet-def456"
instance_type = "t3.micro"
}
api = {
subnet_id = "subnet-ghi789"
instance_type = "t3.small"
}
}
}
module "servers" {
for_each = var.web_servers
source = "./modules/web_server"
name = each.key
vpc_id = aws_vpc.main.id
subnet_id = each.value.subnet_id
ami_id = data.aws_ami.ubuntu.id
instance_type = each.value.instance_type
tags = {
Environment = "production"
}
}
# Access a specific module's output
output "web_1_ip" {
value = module.servers["web-1"].instance_public_ip
}
# Access all module outputs
output "all_server_ips" {
value = { for k, v in module.servers : k => v.instance_public_ip }
}
Sử dụng mô-đun công cộng
Sổ đăng ký Terraform lưu trữ hàng nghìn mô-đun cộng đồng. Hãy sử dụng mô-đun AWS VPC phổ biến:
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.1.2"
name = "my-vpc"
cidr = "10.0.0.0/16"
azs = ["us-east-1a", "us-east-1b", "us-east-1c"]
private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
public_subnets = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]
enable_nat_gateway = true
enable_vpn_gateway = false
tags = {
Environment = "dev"
Project = "web-app"
}
}
# Use module outputs
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
subnet_id = module.vpc.public_subnets[0]
vpc_security_group_ids = [aws_security_group.web.id]
}
resource "aws_security_group" "web" {
name = "web-sg"
vpc_id = module.vpc.vpc_id
# ... rules ...
}
Luôn ghim các phiên bản mô-đun để tránh những thay đổi không mong muốn:
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.0" # Allow 5.x but not 6.0
# ...
}
Tìm mô-đun tại đăng ký.terraform.io.
Mô-đun lồng nhau
Các mô-đun có thể gọi các mô-đun khác. Tạo một mô-đun cho một ứng dụng hoàn chỉnh:
# modules/web_app/main.tf
module "vpc" {
source = "../vpc"
cidr_block = var.vpc_cidr
name = "${var.name}-vpc"
tags = var.tags
}
module "web_servers" {
source = "../web_server"
for_each = var.server_configs
name = "${var.name}-${each.key}"
vpc_id = module.vpc.vpc_id
subnet_id = module.vpc.public_subnet_ids[0]
ami_id = var.ami_id
instance_type = each.value.instance_type
tags = merge(var.tags, {
Server = each.key
})
}
module "database" {
source = "../rds"
name = "${var.name}-db"
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.private_subnet_ids
instance_class = var.db_instance_class
allocated_storage = var.db_allocated_storage
tags = var.tags
}
Mô-đun "web_app" này phối hợp các mô-đun VPC, máy chủ web và cơ sở dữ liệu.
Thực tiễn tốt nhất về mô-đun
Giữ các mô-đun tập trung
Mỗi mô-đun nên có một mục đích rõ ràng:
- Tốt:
vpc,web_server,rds_database - Xấu:
entire_infrastructure
Các mô-đun nhỏ, tập trung sẽ dễ kiểm tra, hiểu và tái sử dụng hơn.
Ghi lại các mô-đun của bạn
Thêm README.md giải thích chức năng của mô-đun, đầu vào và đầu ra bắt buộc:
# Web Server Module
Creates an EC2 instance with a security group configured for web traffic.
## Usage
\`\`\`hcl
module "web" {
source = "./modules/web_server"
name = "my-web-server"
vpc_id = "vpc-abc123"
subnet_id = "subnet-def456"
ami_id = "ami-0c55b159cbfafe1f0"
}
\`\`\`
## Requirements
- AWS provider >= 5.0
- VPC must already exist
- Subnet must be in the VPC
## Inputs
| Name | Description | Type | Default | Required |
|------|-------------|------|---------|----------|
| name | Server name | string | - | yes |
| vpc_id | VPC ID | string | - | yes |
| instance_type | Instance type | string | t3.micro | no |
## Outputs
| Name | Description |
|------|-------------|
| instance_id | EC2 instance ID |
| instance_public_ip | Public IP address |
Cung cấp ví dụ
Bao gồm thư mục examples/ hiển thị cách sử dụng mô-đun:
modules/web_server/
├── main.tf
├── variables.tf
├── outputs.tf
├── README.md
└── examples/
├── basic/
│ └── main.tf
└── complete/
└── main.tf
Phiên bản mô-đun của bạn
Nếu chia sẻ mô-đun giữa các dự án, hãy phiên bản chúng bằng thẻ Git:
git tag -a v1.0.0 -m "Initial release"
git push --tags
Tham khảo các phiên bản cụ thể:
module "web_server" {
source = "git::https://github.com/yourorg/terraform-modules.git//web_server?ref=v1.0.0"
# ...
}
Tránh các giá trị được mã hóa cứng
Làm cho các mô-đun có thể định cấu hình bằng các biến thay vì các giá trị mã hóa cứng:
# Bad - hardcoded
resource "aws_instance" "web" {
instance_type = "t3.micro"
# ...
}
# Good - configurable
resource "aws_instance" "web" {
instance_type = var.instance_type
# ...
}
Sử dụng mặc định hợp lý
Cung cấp giá trị mặc định cho các biến tùy chọn:
variable "instance_type" {
description = "EC2 instance type"
type = string
default = "t3.micro"
}
variable "disk_size" {
description = "Root disk size in GB"
type = number
default = 20
validation {
condition = var.disk_size >= 8 && var.disk_size <= 1000
error_message = "Disk size must be between 8 and 1000 GB."
}
}
Điều này làm cho các mô-đun dễ sử dụng hơn trong khi vẫn cho phép tùy chỉnh.
Ví dụ thực tế: Đơn đăng ký hoàn chỉnh
Đây là một thiết lập đa mô-đun thực tế:
# Root main.tf
terraform {
required_version = "~> 1.6.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.aws_region
}
# VPC module
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.1.2"
name = "${var.project}-${var.environment}-vpc"
cidr = var.vpc_cidr
azs = data.aws_availability_zones.available.names
private_subnets = var.private_subnet_cidrs
public_subnets = var.public_subnet_cidrs
enable_nat_gateway = true
single_nat_gateway = var.environment != "prod"
enable_dns_hostnames = true
tags = local.common_tags
}
# Web server module
module "web_servers" {
source = "./modules/web_server"
for_each = var.web_server_configs
name = "${var.project}-${var.environment}-${each.key}"
vpc_id = module.vpc.vpc_id
subnet_id = module.vpc.public_subnets[each.value.subnet_index]
ami_id = data.aws_ami.ubuntu.id
instance_type = each.value.instance_type
tags = merge(local.common_tags, {
Role = "web"
})
}
# Database module
module "database" {
source = "./modules/rds"
identifier = "${var.project}-${var.environment}-db"
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.private_subnets
instance_class = var.environment == "prod" ? "db.t3.medium" : "db.t3.micro"
allocated_storage = var.environment == "prod" ? 100 : 20
allowed_security_groups = [
for server in module.web_servers : server.security_group_id
]
tags = merge(local.common_tags, {
Role = "database"
})
}
# Load balancer module
module "load_balancer" {
source = "./modules/alb"
name = "${var.project}-${var.environment}-alb"
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.public_subnets
target_instance_ids = [
for server in module.web_servers : server.instance_id
]
tags = merge(local.common_tags, {
Role = "load-balancer"
})
}
# Outputs
output "load_balancer_endpoint" {
description = "Application endpoint"
value = "http://${module.load_balancer.dns_name}"
}
output "web_server_ips" {
description = "Web server IP addresses"
value = { for k, v in module.web_servers : k => v.instance_public_ip }
}
output "database_endpoint" {
description = "Database connection string"
value = module.database.endpoint
sensitive = true
}
Thiết lập này thể hiện:
- Sử dụng mô-đun đăng ký công cộng (VPC)
- Sử dụng các mô-đun cục bộ tùy chỉnh (máy chủ web, cơ sở dữ liệu, bộ cân bằng tải)
- Mô-đun for_each cho nhiều phiên bản
- Truyền đầu ra giữa các mô-đun
- Cấu hình dành riêng cho môi trường
- Gắn thẻ nhất quán trên tất cả các mô-đun
Mô-đun là cách bạn mở rộng quy mô Terraform từ cấu hình đơn giản đến cơ sở hạ tầng phức tạp, có thể bảo trì. Tiếp theo, chúng ta sẽ khám phá việc quản lý nhiều môi trường bằng cùng một mã.