Destroy Worlds với Terraform #2: Values & Variables
Terraform có ba cách chính để quản lý các giá trị (values):
- Input Variables – Các biến đầu vào.
- Local Values (Locals) – Các giá trị nội bộ được tính toán trong configuration.
- Outputs – Các giá trị đầu ra. Mỗi loại có một mục đích sử dụng khác nhau. Việc hiểu rõ khi nào nên sử dụng từng loại sẽ giúp configuration của bạn trở nên rõ ràng, dễ bảo trì và dễ mở rộng hơn.
Bài hướng dẫn này sẽ giải thích cả ba loại thông qua các ví dụ thực tế trên AWS.
1. Input Variables
Input Variables là các tham số được người gọi (caller) cung cấp cho Module hoặc Configuration của bạn.
Chúng được khai báo bằng variable block và được tham chiếu thông qua cú pháp: var.name
1.1. Các kiểu Variable cơ bản
Terraform hỗ trợ nhiều kiểu dữ liệu khác nhau cho Variable.
String Variable:
Khai báo một biến kiểu String với giá trị mặc định (default).
variable "environment" {
description = "Deployment environment (dev, staging, production)"
type = string
default = "dev"
}
Number Variable:
Khai báo biến kiểu Number để xác định số lượng EC2 instances cần tạo.
variable "instance_count" {
description = "Number of EC2 instances to create"
type = number
default = 2
}
Boolean Variable:
Khai báo biến kiểu Boolean để xác định có bật detailed monitoring hay không.
variable "enable_monitoring" {
description = "Whether to enable detailed monitoring"
type = bool
default = true
}
List Variable:
Khai báo biến kiểu List để xác định danh sách Availability Zones cần triển khai resource.
variable "availability_zones" {
description = "AZs to deploy resources in"
type = list(string)
default = ["us-east-1a", "us-east-1b"]
}
Map Variable:
Khai báo biến kiểu Map để xác định các Instance Types tương ứng với từng environment.
variable "instance_types" {
description = "Instance types per environment"
type = map(string)
default = {
dev = "t3.micro"
staging = "t3.small"
production = "t3.medium"
}
}
1.2. Các kiểu Variable phức tạp
Đối với dữ liệu có cấu trúc (structured data), bạn nên sử dụng kiểu object.
Object Variable với cấu trúc cụ thể:
Ví dụ dưới đây khai báo một Database Configuration với các thuộc tính được xác định rõ ràng.
variable "database_config" {
description = "Database configuration"
type = object({
engine = string
instance_class = string
storage_gb = number
multi_az = bool
backup_days = number
})
default = {
engine = "postgres"
instance_class = "db.t3.medium"
storage_gb = 20
multi_az = false
backup_days = 7
}
}
List of Objects:
Sử dụng List of Objects để định nghĩa danh sách các Security Group Rules.
variable "security_group_rules" {
description = "Ingress rules for the security group"
type = list(object({
port = number
protocol = string
cidr_blocks = list(string)
}))
default = []
}
Map of Objects:
Sử dụng Map of Objects để định nghĩa configuration cho nhiều services.
variable "services" {
description = "Service configurations"
type = map(object({
cpu = number
memory = number
port = number
environment = string
}))
default = {}
}
1.3. Sensitive Variables
Đánh dấu các Variable chứa thông tin nhạy cảm bằng sensitive = true để ngăn chúng hiển thị trong output thông thường của Terraform CLI.
Lưu ý: Terraform vẫn lưu các giá trị nhạy cảm trong State File, trừ khi bạn sử dụng những tính năng được thiết kế để không lưu các giá trị đó.
Ví dụ:
variable "db_password" {
description = "Database master password"
type = string
sensitive = true
}
variable "api_key" {
description = "Third-party API key"
type = string
sensitive = true
}
Trong ví dụ này, cả Database Password và API Key đều được đánh dấu là Sensitive Variables.
1.4. Cách truyền giá trị cho Variables
Có một số cách để cung cấp giá trị cho Variables trong Terraform.
Cách 1: Sử dụng Command Line Flags
terraform plan -var="environment=production" -var="instance_count=4"
Bạn có thể truyền trực tiếp giá trị của Variable thông qua flag -var.
Cách 2: Sử dụng Variable File
terraform plan -var-file="production.tfvars"
Terraform sẽ lấy các giá trị được định nghĩa trong file production.tfvars.
Cách 3: Sử dụng Environment Variables
Các Environment Variables phải có tiền tố TF_VAR_.
export TF_VAR_environment="production"
export TF_VAR_instance_count=4
terraform plan
Cách 4: Sử dụng Auto-loaded Files
Các file có tên terraform.tfvars hoặc *.auto.tfvars sẽ được Terraform tự động load.
Ví dụ, file production.tfvars:
environment = "production"
instance_count = 4
enable_monitoring = true
database_config = {
engine = "postgres"
instance_class = "db.r6g.large"
storage_gb = 100
multi_az = true
backup_days = 30
}
2. Local Values (Locals)
Local Values là các giá trị được tính toán bên trong Module.
Bạn có thể hiểu chúng giống như những biến nội bộ (private variables): chúng được tính toán dựa trên Input Variables, Data Sources hoặc các Local Values khác, nhưng không được expose ra bên ngoài dưới dạng Input.
Nói cách khác:
- Variables: Nhận giá trị đầu vào từ bên ngoài.
- Locals: Tính toán và lưu trữ các giá trị nội bộ dựa trên những dữ liệu đã có.
2.1. Local Values cơ bản
Ví dụ:
locals {
# Derived from variables
name_prefix = "${var.project}-${var.environment}"
# Computed values
is_production = var.environment == "production"
# Common tags applied to all resources
common_tags = {
Project = var.project
Environment = var.environment
ManagedBy = "terraform"
Team = var.team
}
# Conditional values
instance_type = local.is_production ? "t3.large" : "t3.micro"
multi_az = local.is_production ? true : false
# Merged or transformed data
all_tags = merge(local.common_tags, var.extra_tags)
}
Giải thích các thành phần:
| Local Value | Ý nghĩa |
|---|---|
| name_prefix | Kết hợp project và environment để tạo naming convention thống nhất. |
| name_prefix | Kiểm tra environment hiện tại có phải production hay không. |
| name_prefix | Định nghĩa các Tags dùng chung cho tất cả Resources. |
| name_prefix | Nếu là production thì sử dụng t3.large, ngược lại sử dụng t3.micro. |
| name_prefix | Nếu là production thì bật Multi-AZ, ngược lại tắt. |
| name_prefix | Kết hợp Common Tags với các Extra Tags thông qua hàm merge(). |
Một điểm quan trọng: Khi tham chiếu Local Values, bạn phải sử dụng tiền tố local. thay vì var..
Ví dụ:
- local.name_prefix
- local.common_tags
- local.instance_type
2.2. Khi nào nên sử dụng Locals thay vì Variables?
Đây là phần rất quan trọng khi thiết kế Terraform Modules.
Sử dụng Variables khi:
- Giá trị đến từ bên ngoài Module (được caller cung cấp).
- Các Environment khác nhau cần những giá trị khác nhau.
- Giá trị đó thực sự là một Parameter của Configuration.
Sử dụng Locals khi:
- Giá trị được tính toán dựa trên những giá trị khác.
- Bạn muốn tránh phải viết lặp lại cùng một Expression.
- Bạn cần tính toán những giá trị phức tạp.
- Bạn muốn đặt một cái tên có ý nghĩa cho một phép tính hoặc Expression.
Ví dụ thực tế kết hợp Variables và Locals
Giả sử bạn cần triển khai AWS Infrastructure cho nhiều Environment khác nhau như dev, staging và production.
Đầu tiên, khai báo Variables để nhận Input từ bên ngoài:
# Variables - inputs from the caller
variable "project" {
type = string
}
variable "environment" {
type = string
}
variable "vpc_cidr" {
type = string
default = "10.0.0.0/16"
}
Tiếp theo, sử dụng Locals để tính toán dựa trên các Input đã nhận:
# Locals - computed from inputs
locals {
# Naming convention
name_prefix = "${var.project}-${var.environment}"
common_tags = {
Project = var.project
Environment = var.environment
ManagedBy = "terraform"
}
# Subnet CIDR calculation
public_subnets = [
cidrsubnet(var.vpc_cidr, 8, 1),
cidrsubnet(var.vpc_cidr, 8, 2),
]
private_subnets = [
cidrsubnet(var.vpc_cidr, 8, 10),
cidrsubnet(var.vpc_cidr, 8, 11),
]
# Environment-specific settings
settings = {
dev = {
instance_type = "t3.micro"
min_size = 1
max_size = 2
}
staging = {
instance_type = "t3.small"
min_size = 2
max_size = 4
}
production = {
instance_type = "t3.medium"
min_size = 3
max_size = 10
}
}
# Look up settings for the current environment
current_settings = local.settings[var.environment]
}
Giải thích:
- name_prefix: Tự động tạo tên dựa trên Project và Environment.
- common_tags: Định nghĩa Tags dùng chung.
- public_subnets: Tính toán CIDR cho Public Subnets.
- private_subnets: Tính toán CIDR cho Private Subnets.
- settings: Lưu trữ cấu hình riêng cho từng Environment.
- current_settings: Lấy cấu hình tương ứng với Environment hiện tại. Cuối cùng, sử dụng các Locals này trong Resource:
# Use locals in resources
resource "aws_instance" "app" {
count = local.current_settings.min_size
ami = data.aws_ami.app.id
instance_type = local.current_settings.instance_type
tags = merge(local.common_tags, {
Name = "${local.name_prefix}-app-${count.index}"
})
}
Ở đây:
- count: Số lượng EC2 Instances được tạo dựa trên min_size của Environment hiện tại.
- ami: Lấy AMI ID từ Data Source.
- instance_type: Lấy Instance Type tương ứng với Environment.
- tags: Kết hợp Common Tags với Name Tag riêng cho từng Instance.
2.3. Các mẫu sử dụng Local Values nâng cao
Bạn có thể sử dụng Locals để thực hiện Data Transformation và xử lý các Logic phức tạp.
Ví dụ:
locals {
# Flatten nested structures
subnet_configs = flatten([
for az_index, az in var.availability_zones : [
{
az = az
cidr = cidrsubnet(var.vpc_cidr, 8, az_index + 1)
type = "public"
},
{
az = az
cidr = cidrsubnet(var.vpc_cidr, 8, az_index + 10)
type = "private"
},
]
])
# Create a map from a list for use with for_each
subnet_map = {
for s in local.subnet_configs :
"${s.type}-${s.az}" => s
}
# Filter a collection
production_services = {
for name, config in var.services :
name => config
if config.environment == "production"
}
}
Có ba kỹ thuật chính trong ví dụ trên:
1. Flatten nested structures
Sử dụng flatten() để làm phẳng các cấu trúc List lồng nhau.
Trong ví dụ này, Terraform duyệt qua từng Availability Zone và tạo ra hai cấu hình Subnet:
- Một Public Subnet.
- Một Private Subnet.
2. Create a Map from a List
Chuyển đổi một List thành Map để có thể sử dụng với for_each.
subnet_map = {
for s in local.subnet_configs :
"${s.type}-${s.az}" => s
}
Mỗi Subnet sẽ có một Key duy nhất được tạo từ loại Subnet và Availability Zone.
Ví dụ:
- public-us-east-1a
- private-us-east-1a
- public-us-east-1b
- private-us-east-1b
3. Filter a Collection
Lọc một Collection để chỉ lấy những Services thuộc Environment production.
production_services = {
for name, config in var.services :
name => config
if config.environment == "production"
}
Như vậy, bạn có thể sử dụng Locals để chuẩn hóa dữ liệu, chuyển đổi cấu trúc và lọc các Resources trước khi sử dụng chúng trong Terraform.
3. Output Values
Outputs được sử dụng để expose các giá trị từ Module.
Các giá trị này sẽ hiển thị trong CLI sau khi chạy terraform apply và có thể được tham chiếu bởi những Modules hoặc Configurations khác.
Nói đơn giản:
- Variables: Nhận dữ liệu đầu vào.
- Locals: Xử lý và tính toán dữ liệu nội bộ.
- Outputs: Xuất dữ liệu ra bên ngoài.
3.1. Các loại Output cơ bản
Basic Output:
Lấy ID của VPC vừa được tạo.
output "vpc_id" {
description = "ID of the created VPC"
value = aws_vpc.main.id
}
Sensitive Output:
Output chứa thông tin nhạy cảm sẽ bị ẩn trong output mặc định của CLI.
output "db_password" {
description = "Database master password"
value = random_password.db.result
sensitive = true
}
Computed Output:
Tạo một URL hoàn chỉnh dựa trên DNS Name của Application Load Balancer.
output "api_url" {
description = "Full URL of the API"
value = "https://${aws_lb.api.dns_name}/api/v1"
}
Conditional Output:
Chỉ trả về Public IP của Bastion Host nếu Resource này được tạo.
output "bastion_ip" {
description = "Public IP of the bastion host (if created)"
value = var.create_bastion ? aws_instance.bastion[0].public_ip : null
}
Nếu create_bastion = false, Output sẽ trả về null.
Map Output:
Xuất danh sách Subnet IDs theo từng loại Subnet.
output "subnet_ids" {
description = "Map of subnet type to subnet IDs"
value = {
public = aws_subnet.public[*].id
private = aws_subnet.private[*].id
}
}
3.2. Cách đọc Outputs
Sau khi chạy terraform apply, bạn có thể lấy các giá trị Output bằng những câu lệnh sau.
Hiển thị tất cả Outputs:
terraform output
Hiển thị một Output cụ thể:
terraform output vpc_id
Lấy giá trị Raw (hữu ích khi sử dụng trong Scripts):
terraform output -raw vpc_id
Lấy Output dưới định dạng JSON (hữu ích với các kiểu dữ liệu phức tạp):
terraform output -json subnet_ids
4. Kết hợp tất cả
Dưới đây là một ví dụ hoàn chỉnh kết hợp Variables, Locals và Outputs trong cùng một Terraform Configuration.
Giả sử bạn muốn tạo một VPC trên AWS. File 1: variables.tf Khai báo các Variables để nhận dữ liệu đầu vào.
# variables.tf
variable "project" {
type = string
description = "Project name"
}
variable "environment" {
type = string
description = "Environment name"
}
File 2: locals.tf Định nghĩa các Local Values để xử lý và chuẩn hóa dữ liệu.
# locals.tf
locals {
name_prefix = "${var.project}-${var.environment}"
common_tags = {
Project = var.project
Environment = var.environment
ManagedBy = "terraform"
}
}
File 3: main.tf Sử dụng Variables và Locals để tạo AWS Resource.
# main.tf
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
tags = merge(local.common_tags, {
Name = "${local.name_prefix}-vpc"
})
}
File 4: outputs.tf Expose VPC ID sau khi Resource được tạo thành công.
# outputs.tf
output "vpc_id" {
value = aws_vpc.main.id
description = "VPC ID"
}
Với cách tổ chức này, mỗi file có một nhiệm vụ riêng:
| File | Nhiệm vụ |
|---|---|
| variables.tf | Khai báo các Input Variables. |
| locals.tf | Xử lý và tính toán các Local Values. |
| main.tf | Khai báo và triển khai Resources. |
| outputs.tf | Expose các giá trị đầu ra. |
5. Tổng kết
Đây là bảng mình khuyên bạn nên ghi nhớ khi làm việc với Terraform Modules.
| Tiêu chí | Variable | Local | Output |
|---|---|---|---|
| Nhiệm vụ | Nhận giá trị theo Environment | Tính toán nội bộ | Xuất dữ liệu |
| Khai báo | variable | locals | output |
| Cách tham chiếu | var.name | local.name | module.name.output |
| Ai cung cấp giá trị? | Root Module hoặc Module (bên ngoài) gọi nó (caller) | var, resource, data source, module output, hoặc các local khác trong cùng Module | Terraform lấy giá trị từ provider như aws, azure, gcp |
All Rights Reserved