TLDR: Tài nguyên tạo và quản lý cơ sở hạ tầng (máy chủ, cơ sở dữ liệu, mạng). Nguồn dữ liệu truy vấn cơ sở hạ tầng hiện có mà không cần quản lý nó. Sử dụng các phụ thuộc tài nguyên để kiểm soát thứ tự tạo. Áp dụng các quy tắc vòng đời để ngăn chặn việc vô tình xóa hoặc kiểm soát hành vi cập nhật.
Tài nguyên là trái tim của Terraform - chúng đại diện cho các thành phần cơ sở hạ tầng mà bạn muốn tạo và quản lý. Nguồn dữ liệu cho phép bạn tham khảo thông tin về cơ sở hạ tầng hiện có. Hiểu cả hai là chìa khóa để xây dựng cấu hình thực sự.
Tạo tài nguyên
Khối tài nguyên khai báo một phần cơ sở hạ tầng cần tạo. Cú pháp chung là:
resource "provider_type" "local_name" {
argument1 = "value1"
argument2 = "value2"
}
Hãy tạo một ví dụ hoàn chỉnh với phiên bản VPC, mạng con và EC2 trên AWS:
# VPC to isolate our resources
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "main-vpc"
}
}
# Public subnet in the VPC
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
availability_zone = "us-east-1a"
map_public_ip_on_launch = true
tags = {
Name = "public-subnet"
}
}
# Internet gateway for external access
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = {
Name = "main-igw"
}
}
# Route table for public internet access
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}
tags = {
Name = "public-rt"
}
}
# Associate route table with subnet
resource "aws_route_table_association" "public" {
subnet_id = aws_subnet.public.id
route_table_id = aws_route_table.public.id
}
# Security group
resource "aws_security_group" "web" {
name = "web-sg"
description = "Allow web traffic"
vpc_id = aws_vpc.main.id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "web-sg"
}
}
# EC2 instance
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
subnet_id = aws_subnet.public.id
vpc_security_group_ids = [aws_security_group.web.id]
tags = {
Name = "web-server"
}
}
Khi bạn chạy terraform apply, Terraform sẽ tạo các tài nguyên này theo đúng thứ tự dựa trên sự phụ thuộc của chúng.
Hiểu sự phụ thuộc tài nguyên
Terraform tự động xác định thứ tự tạo tài nguyên bằng cách phân tích tài liệu tham khảo. Trong ví dụ của chúng tôi:
aws_vpc.main
↓
├── aws_subnet.public
│ ↓
│ aws_instance.web ← aws_security_group.web
│
└── aws_internet_gateway.main
↓
aws_route_table.public
↓
aws_route_table_association.public
VPC phải tồn tại trước khi tạo mạng con hoặc cổng internet. Mạng con phải tồn tại trước khi tạo cá thể. Terraform tự động xử lý việc này.
Đôi khi bạn cần các phần phụ thuộc rõ ràng mà Terraform không thể suy ra. Sử dụng depends_on:
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
subnet_id = aws_subnet.public.id
# Explicit dependency - make sure internet gateway exists
depends_on = [aws_internet_gateway.main]
}
Sử dụng depends_on một cách tiết kiệm. Terraform thường tìm ra các phụ thuộc một cách chính xác. Bạn cần nó khi:
- Tài nguyên có sự phụ thuộc ẩn mà Terraform không thể phát hiện
- Một hệ thống bên ngoài cần thời gian để sẵn sàng
- Bạn cần kiểm soát thứ tự tạo vì lý do hợp lý
Thuộc tính tài nguyên
Sau khi Terraform tạo tài nguyên, nó sẽ hiển thị các thuộc tính mà bạn có thể tham khảo. Một số thuộc tính bạn cung cấp (như cidr_block), một số thuộc tính khác do nhà cung cấp đặt (như id).
Kiểm tra tài liệu của nhà cung cấp để xem các thuộc tính có sẵn. Đối với phiên bản AWS:
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
subnet_id = aws_subnet.public.id
}
# Reference the instance's attributes
output "instance_id" {
value = aws_instance.web.id # Set by AWS
}
output "private_ip" {
value = aws_instance.web.private_ip # Set by AWS
}
output "public_ip" {
value = aws_instance.web.public_ip # Set by AWS
}
output "instance_type" {
value = aws_instance.web.instance_type # What we set
}
Nguồn dữ liệu: Truy vấn cơ sở hạ tầng hiện có
Nguồn dữ liệu cho phép bạn tìm nạp thông tin về các tài nguyên tồn tại bên ngoài cấu hình Terraform của bạn. Họ không tạo hay sửa đổi bất cứ điều gì - họ chỉ truy vấn.
Cú pháp cho nguồn dữ liệu:
data "provider_type" "local_name" {
# Query parameters
}
Các trường hợp sử dụng phổ biến:
Tìm AMI
Thay vì mã hóa cứng ID AMI, hãy tự động tìm phiên bản mới nhất:
# Find the latest Ubuntu 22.04 AMI
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"] # Canonical's AWS account ID
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
}
# Use it in a resource
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id # Always the latest
instance_type = "t3.micro"
}
Điều này đảm bảo bạn luôn sử dụng Ubuntu image mới nhất mà không cần cập nhật thủ công.
Tham chiếu các VPC hiện có
Nếu VPC của bạn được tạo bên ngoài Terraform:
# Find VPC by tag
data "aws_vpc" "main" {
tags = {
Name = "main-vpc"
}
}
# Find subnets in that VPC
data "aws_subnets" "public" {
filter {
name = "vpc-id"
values = [data.aws_vpc.main.id]
}
tags = {
Tier = "public"
}
}
# Use the VPC and subnets
resource "aws_security_group" "web" {
vpc_id = data.aws_vpc.main.id
# ... rules ...
}
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
subnet_id = data.aws_subnets.public.ids[0]
}
Lấy thông tin tài khoản và khu vực
# Get current AWS account ID
data "aws_caller_identity" "current" {}
# Get current region
data "aws_region" "current" {}
# Get available availability zones
data "aws_availability_zones" "available" {
state = "available"
}
# Use them
locals {
account_id = data.aws_caller_identity.current.account_id
region = data.aws_region.current.name
azs = data.aws_availability_zones.available.names
}
output "deployment_info" {
value = "Deploying in account ${local.account_id}, region ${local.region}"
}
Đọc dữ liệu bên ngoài
Nguồn dữ liệu local_file đọc các tệp cục bộ:
data "local_file" "ssh_key" {
filename = "${path.module}/keys/id_rsa.pub"
}
resource "aws_key_pair" "deployer" {
key_name = "deployer-key"
public_key = data.local_file.ssh_key.content
}
Vòng đời tài nguyên
Kiểm soát cách Terraform xử lý các cập nhật tài nguyên bằng các quy tắc vòng đời.
Ngăn chặn xóa
Bảo vệ các tài nguyên quan trọng khỏi việc vô tình xóa:
resource "aws_db_instance" "production" {
identifier = "prod-db"
engine = "postgres"
instance_class = "db.t3.medium"
allocated_storage = 100
lifecycle {
prevent_destroy = true # Terraform will error if you try to destroy this
}
}
Nếu ai đó cố gắng phá hủy tài nguyên này, Terraform sẽ từ chối:
Error: Instance cannot be destroyed
on main.tf line 10:
10: resource "aws_db_instance" "production" {
Resource aws_db_instance.production has lifecycle.prevent_destroy set, but
the plan calls for this resource to be destroyed.
Tạo trước khi hủy
Một số tài nguyên không thể ngừng hoạt động trong quá trình cập nhật. Tạo sự thay thế trước:
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
lifecycle {
create_before_destroy = true # New instance before deleting old one
}
}
Khi bạn thay đổi AMI, Terraform:
- Tạo một phiên bản mới với AMI mới
- Cập nhật các tham chiếu để trỏ đến phiên bản mới
- Xóa phiên bản cũ
Nếu không có cài đặt này, Terraform sẽ xóa trước (gây ra thời gian ngừng hoạt động) sau đó mới tạo.
Bỏ qua các thay đổi
Đôi khi các hệ thống bên ngoài sửa đổi tài nguyên bên ngoài Terraform. Ngăn Terraform hoàn nguyên những thay đổi này:
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
tags = {
Name = "web-server"
}
lifecycle {
ignore_changes = [
tags["LastModified"], # Ignore changes to specific tag
user_data, # Ignore user_data modifications
]
}
}
Hoặc bỏ qua mọi thay đổi:
lifecycle {
ignore_changes = all
}
Điều này rất hữu ích khi:
- Tự động hóa bên ngoài thêm thẻ
- Tự động điều chỉnh tỷ lệ sửa đổi một số thuộc tính nhất định
- Bạn đang dần áp dụng Terraform cho cơ sở hạ tầng hiện có
Thay thế được kích hoạt bởi
Buộc thay thế khi giá trị cụ thể thay đổi:
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
lifecycle {
replace_triggered_by = [
aws_security_group.web.id # Replace instance if security group changes
]
}
}
Đếm và For_Each: Nhiều trường hợp
Tạo nhiều tài nguyên tương tự bằng count hoặc for_each.
Sử dụng Đếm
Tạo một số lượng tài nguyên cố định:
resource "aws_instance" "web" {
count = 3
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
subnet_id = aws_subnet.public.id
tags = {
Name = "web-${count.index}" # web-0, web-1, web-2
}
}
# Access specific instances
output "first_instance_ip" {
value = aws_instance.web[0].public_ip
}
# Access all instances
output "all_instance_ips" {
value = aws_instance.web[*].public_ip
}
count.index cung cấp cho bạn số lần lặp hiện tại (bắt đầu từ 0).
Sử dụng For_Each
Tạo tài nguyên dựa trên bản đồ hoặc tập hợp, đặt cho mỗi tài nguyên một cái tên có ý nghĩa:
variable "instances" {
type = map(object({
instance_type = string
ami = string
}))
default = {
web = {
instance_type = "t3.micro"
ami = "ami-0c55b159cbfafe1f0"
}
api = {
instance_type = "t3.small"
ami = "ami-0c55b159cbfafe1f0"
}
worker = {
instance_type = "t3.medium"
ami = "ami-0c55b159cbfafe1f0"
}
}
}
resource "aws_instance" "server" {
for_each = var.instances
ami = each.value.ami
instance_type = each.value.instance_type
subnet_id = aws_subnet.public.id
tags = {
Name = each.key # "web", "api", or "worker"
}
}
# Access specific instance
output "web_server_ip" {
value = aws_instance.server["web"].public_ip
}
# Access all instances
output "all_server_ips" {
value = { for k, v in aws_instance.server : k => v.public_ip }
}
Với for_each:
each.keylà khóa bản đồ hoặc giá trị cài đặteach.valuelà giá trị bản đồ (đối với các bộ, giống nhưeach.key)
for_each nhìn chung tốt hơn count vì:
- Tài nguyên có tên có ý nghĩa thay vì số
- Thêm/xóa các mục không đánh số lại mọi thứ
- Ý định rõ ràng hơn
Sử dụng count khi bạn chỉ cần N tài nguyên giống hệt nhau. Sử dụng for_each khi mỗi tài nguyên được cấu hình khác nhau.
Ví dụ thực tế: Ứng dụng nhiều tầng
Đây là một ví dụ hoàn chỉnh về việc kết hợp các tài nguyên, nguồn dữ liệu và các khái niệm sau:
# Find latest Amazon Linux 2 AMI
data "aws_ami" "amazon_linux" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["amzn2-ami-hvm-*-x86_64-gp2"]
}
}
# Get available AZs
data "aws_availability_zones" "available" {
state = "available"
}
# VPC
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
tags = {
Name = "main-vpc"
}
}
# Create subnets in multiple AZs
resource "aws_subnet" "public" {
for_each = toset(slice(data.aws_availability_zones.available.names, 0, 2))
vpc_id = aws_vpc.main.id
cidr_block = "10.0.${index(data.aws_availability_zones.available.names, each.value) + 1}.0/24"
availability_zone = each.value
map_public_ip_on_launch = true
tags = {
Name = "public-${each.value}"
}
}
# Security groups for different tiers
resource "aws_security_group" "web" {
name = "web-sg"
vpc_id = aws_vpc.main.id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_security_group" "app" {
name = "app-sg"
vpc_id = aws_vpc.main.id
ingress {
from_port = 8080
to_port = 8080
protocol = "tcp"
security_groups = [aws_security_group.web.id] # Only from web tier
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
# Launch template for web servers
resource "aws_launch_template" "web" {
name_prefix = "web-"
image_id = data.aws_ami.amazon_linux.id
instance_type = "t3.micro"
vpc_security_group_ids = [aws_security_group.web.id]
user_data = base64encode(<<-EOF
#!/bin/bash
yum update -y
yum install -y httpd
systemctl start httpd
systemctl enable httpd
echo "<h1>Web Server</h1>" > /var/www/html/index.html
EOF
)
lifecycle {
create_before_destroy = true
}
}
# Auto-scaling group
resource "aws_autoscaling_group" "web" {
desired_capacity = 2
max_size = 4
min_size = 1
vpc_zone_identifier = [for s in aws_subnet.public : s.id]
launch_template {
id = aws_launch_template.web.id
version = "$Latest"
}
tag {
key = "Name"
value = "web-server"
propagate_at_launch = true
}
}
Ví dụ này chứng minh:
- Sử dụng nguồn dữ liệu để tìm AMI và vùng sẵn sàng
- Tạo nhiều mạng con với
for_each - Thiết lập nhóm bảo mật với quyền truy cập theo từng tầng
- Sử dụng mẫu khởi chạy cho các phiên bản có thể mở rộng
- Quy tắc vòng đời để cập nhật không có thời gian ngừng hoạt động
Hiểu các tài nguyên và nguồn dữ liệu sẽ cung cấp cho bạn nền tảng cho mọi cơ sở hạ tầng. Tiếp theo, chúng ta sẽ khám phá quản lý trạng thái - cách Terraform theo dõi những gì nó đã tạo và cách xử lý trạng thái một cách an toàn.