Terraform basics là kỹ năng DevOps không thể thiếu — viết hạ tầng dạng code thay vì click Console. Bài này code đầy đủ deploy S3 + Cloudflare DNS, kèm best practices state management.
Infrastructure as Code — vì sao quan trọng?
Click Console: nhanh nhưng không reproducible. Server crash → setup lại 4 tiếng. Team mới khó onboard. Audit "ai đổi gì" không có.
Terraform: hạ tầng = file Git. Review qua PR. Rollback bằng git revert. Disaster recovery 30 phút. Đó là lý do mọi team production từ 5 người trở lên đều dùng IaC.
3 khái niệm cốt lõi
| Object | Vai trò |
|---|---|
| Provider | Plugin nói chuyện với cloud (aws, google, cloudflare, kubernetes) |
| Resource | Tài nguyên cloud cụ thể (S3 bucket, DNS record, VM) |
| State | JSON file lưu trạng thái thực tế cloud, mapping resource → real ID |
| Module | Bundle resource tái sử dụng |
| Variable | Input có thể thay đổi (env, region, instance type) |
| Output | Giá trị export sau apply (URL, ARN) |
Hello World: S3 bucket
# main.tf
terraform {
required_version = ">= 1.6"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "ap-southeast-1"
}
resource "aws_s3_bucket" "uploads" {
bucket = "alodev-uploads"
tags = {
Project = "alodev"
Env = "production"
}
}
resource "aws_s3_bucket_public_access_block" "uploads" {
bucket = aws_s3_bucket.uploads.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
output "bucket_name" {
value = aws_s3_bucket.uploads.id
}
terraform init # tải provider, init backend
terraform plan # xem thay đổi
terraform apply # thực thi (gõ 'yes')
terraform destroy # xoá toàn bộ (cẩn thận)
State backend remote
Local state (terraform.tfstate trong Git) — KHÔNG bao giờ. Lý do:
- Chứa output secret (password, ARN) plain text
- 2 dev apply cùng lúc → race, corrupt state
- Mất file = mất khả năng manage hạ tầng
S3 backend chuẩn:
terraform {
backend "s3" {
bucket = "alodev-tf-state"
key = "production/terraform.tfstate"
region = "ap-southeast-1"
encrypt = true
dynamodb_table = "alodev-tf-locks" # lock chống concurrent apply
}
}
S3 versioning ON → recover state cũ khi cần. DynamoDB table có 1 attribute LockID (string) làm primary key.
Variable + tfvars
variable "env" {
description = "Environment (dev, staging, production)"
type = string
default = "dev"
}
variable "instance_type" {
type = string
default = "t3.small"
}
resource "aws_instance" "api" {
ami = "ami-0c..."
instance_type = var.instance_type
tags = { Env = var.env }
}
# production.tfvars
env = "production"
instance_type = "t3.large"
terraform apply -var-file=production.tfvars
Module — tái sử dụng pattern
# modules/web-service/main.tf
variable "name" { type = string }
variable "image" { type = string }
resource "aws_ecs_service" "this" {
name = var.name
# ... 50 dòng config
}
output "url" {
value = aws_lb.this.dns_name
}
# root main.tf — gọi module 2 lần
module "api" {
source = "./modules/web-service"
name = "api"
image = "ghcr.io/alodev/api:v1.2.3"
}
module "worker" {
source = "./modules/web-service"
name = "worker"
image = "ghcr.io/alodev/worker:v1.2.3"
}
Khi pattern thay đổi (e.g. thêm CloudWatch alarm), sửa module — cả api + worker auto cập nhật.
Multi-provider: AWS + Cloudflare
provider "cloudflare" {
api_token = var.cloudflare_token
}
resource "aws_lb" "api" {
name = "api-lb"
# ...
}
resource "cloudflare_record" "api" {
zone_id = var.cf_zone_id
name = "api"
value = aws_lb.api.dns_name
type = "CNAME"
proxied = true
}
Terraform tạo LB AWS, lấy DNS name, tạo CNAME Cloudflare trỏ vào — tất cả trong 1 apply.
Workspace cho multi-env
terraform workspace new staging
terraform workspace new production
terraform workspace select production
terraform apply -var-file=production.tfvars
Mỗi workspace có state riêng — không nhầm staging vs prod. Hoặc dùng repo riêng cho mỗi env (an toàn hơn).
10 best practices
- ✅ Pin version provider (~> 5.0) — không bị break khi major update
- ✅ Remote state với encryption + locking
- ✅
terraform fmt+tflinttrong pre-commit - ✅ Plan trong PR, apply qua CI sau review
- ✅ Tag mọi resource (Env, Project, Owner)
- ✅ Đặt secret ngoài code — pass qua TF_VAR env
- ✅ Drift detection cron hằng ngày
- ✅ Module versioned (Git tag) cho production
- ✅
terraform importresource đã tồn tại thay vì tạo mới - ✅
prevent_destroycho production database
Kết luận
Terraform basics đủ cho 90% use case — provider, resource, state, module. Bắt đầu nhỏ với 1-2 resource quan trọng, mở rộng dần. Khi production lớn, đầu tư vào Atlantis hoặc Terraform Cloud cho workflow CI/CD chuẩn. Đọc CI/CD GitHub Actions để tự động plan + apply.