Bỏ qua menu, vào nội dung chính

Terraform cơ bản: hạ tầng dưới dạng mã cho người mới

Terraform basics: provider, resource, state, module. Code mẫu deploy AWS S3, Cloudflare DNS. State backend, drift detection, best practices 2026.

3 phút đọc

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.

Trang giới thiệu Terraform của HashiCorp
Trang giới thiệu Terraform của HashiCorp (developer.hashicorp.com), ảnh chụp ngày 27/09/2026.

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

ObjectVai trò
ProviderPlugin nói chuyện với cloud (aws, google, cloudflare, kubernetes)
ResourceTài nguyên cloud cụ thể (S3 bucket, DNS record, VM)
StateJSON file lưu trạng thái thực tế cloud, mapping resource -> real ID
ModuleBundle resource tái sử dụng
VariableInput có thể thay đổi (env, region, instance type)
OutputGiá trị export sau apply (URL, ARN)

Hello world: S3 bucket

hcl# 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
}
bashterraform 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:

hclterraform {
  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

hclvariable "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 }
}
text# production.tfvars
env           = "production"
instance_type = "t3.large"
bashterraform apply -var-file=production.tfvars

Module - tái sử dụng pattern

hcl# 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
}
hcl# 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

hclprovider "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

bashterraform 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 + tflint trong 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 import resource đã tồn tại thay vì tạo mới
  • prevent_destroy cho production database

Kết luận

Terraform basics đủ cho phần lớn nhu cầu - 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.

Nhận bài mới

Để lại email để nhận bài mới của series Kiến thức lập trình, hoặc theo dõi bằng RSS.

RSS Kiến thức lập trình

Email chỉ dùng để gửi bài mới, xem Chính sách bảo mật.

Trao đổi về dự án

Cần tư vấn?