Terraform IaC: Quản Lý Hạ Tầng AWS Thực Chiến
Có bao giờ bạn dựng xong một hạ tầng AWS hoàn chỉnh, rồi một tuần sau cần clone sang môi trường khác và phải ngồi click lại từ đầu? Hoặc tệ hơn — cả team không ai biết tại sao security group lại có rule kỳ lạ đó, vì người cấu hình đã nghỉ việc từ lâu. Infrastructure as Code ra đời để chấm dứt những tình huống đó, và Terraform hiện là công cụ IaC phổ biến nhất trong hệ sinh thái AWS.
Tại Sao Terraform Thay Vì AWS CloudFormation?
Câu hỏi này mình hay gặp khi onboard team mới. CloudFormation là native tool của AWS — không cần cài thêm gì, IAM tích hợp sẵn, rollback tự động. Nhưng nếu bạn đang chạy multi-cloud hoặc muốn cùng một syntax để quản lý cả Kubernetes lẫn AWS, thì CloudFormation là con đường cụt.
Terraform dùng HCL (HashiCorp Configuration Language) — đọc như YAML nhưng có type system và function. Provider ecosystem rộng hơn nhiều: AWS, GCP, Azure, Datadog, GitHub, Cloudflare đều có provider chính thức. Quan trọng hơn, state file của Terraform minh bạch — bạn biết chính xác resource nào đang được quản lý, không như CloudFormation ẩn hết vào stack.
Khổ nỗi là Terraform cũng có nhược điểm riêng: state drift dễ xảy ra nếu ai đó chỉnh tay trên Console, và concurrent apply trong team cần cơ chế lock cẩn thận. Nhưng khi đã setup đúng, lợi ích vượt trội hẳn.
Cấu Trúc Project Terraform Thực Tế
Mình thấy nhiều người bắt đầu với một file main.tf duy nhất rồi nhét tất cả vào đó. Vài tuần sau file đó dài 800 dòng và không ai muốn đọc nữa. Cấu trúc mình đang dùng cho production:
project/
├── environments/
│ ├── dev/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── terraform.tfvars
│ ├── staging/
│ └── prod/
├── modules/
│ ├── vpc/
│ ├── ecs-cluster/
│ ├── rds/
│ └── alb/
└── shared/
└── backend.tfMỗi module là một đơn vị độc lập — VPC module nhận vào CIDR block và trả ra subnet IDs, route table IDs. ECS cluster module nhận vào VPC outputs và trả ra cluster ARN. Các module không phụ thuộc nhau trực tiếp mà thông qua outputs — dependency graph rõ ràng, Terraform tự biết plan theo đúng thứ tự.
Nguyên tắc mình tuân theo: module không nên quá 200 dòng. Nếu vượt, đó là dấu hiệu cần tách thêm.
State Management — Phần Quan Trọng Nhất Bạn Hay Bỏ Qua
State file (terraform.tfstate) là "bộ nhớ" của Terraform — nó ghi nhớ mọi resource đang được quản lý. Để local file này trên máy là recipe cho thảm họa: commit nhầm lên Git lộ ARN và resource ID, hoặc đồng nghiệp apply song song gây conflict.
Setup remote state với S3 + DynamoDB là standard:
terraform {
backend "s3" {
bucket = "my-terraform-state-prod"
key = "vpc/terraform.tfstate"
region = "ap-northeast-1"
dynamodb_table = "terraform-lock"
encrypt = true
}
}DynamoDB table dùng để lock state khi có người đang apply — tránh race condition. Bật encrypt = true để state file được mã hóa at rest bằng KMS. Tạo S3 bucket với versioning enabled — khi state bị corrupt, bạn có thể rollback về version trước.
Workflow Thực Tế: Plan → Review → Apply
Mình không bao giờ apply thẳng mà không qua review. Quy trình trong team:
# Developer tạo PR, CI tự chạy
terraform init -backend-config=prod.hcl
terraform plan -out=tfplan -var-file=prod.tfvars
# Plan output được post vào PR comment
terraform apply tfplanLưu file plan (-out=tfplan) đảm bảo apply chính xác những gì đã được review, không có sự khác biệt giữa lúc plan và lúc apply. Nếu bỏ bước này, ai đó có thể change resource ở Console trong khoảng thời gian đó và apply sẽ có diff khác với plan.
Mẹo: dùng
terraform plan -detailed-exitcodetrong CI — exit code 2 nghĩa là có changes, exit code 0 là no-op. Script dựa vào exit code này để quyết định có cần tạo PR comment hay không.
Lỗi Thường Gặp Và Cách Thoát
State drift — ai đó xóa resource trên Console nhưng state file vẫn ghi nhận nó. Chạy terraform refresh để sync state với reality, hoặc terraform state rm <resource> nếu resource thực sự đã bị xóa và không cần quản lý nữa.
Provider version conflict — lock file (.terraform.lock.hcl) sinh ra sau terraform init phải được commit vào Git. Không commit file này, mỗi người trong team có thể dùng provider version khác nhau, plan output sẽ khác nhau dù code giống hệt.
Circular dependency — thường xảy ra khi security group A tham chiếu security group B và ngược lại. Giải pháp: tách ra aws_security_group_rule riêng thay vì inline rules trong aws_security_group.
Sensitive data trong state — RDS password, API key có thể bị lưu plain text trong state file. Dùng sensitive = true cho variable và output, đồng thời bật encryption trên S3 bucket chứa state.
FAQ
Q: Có nên dùng Terraform Cloud hay self-manage?A: Terraform Cloud free tier đủ cho team nhỏ — remote state, remote execution, basic SSO. Khi cần custom runner, self-hosted agent, hoặc cost control chặt hơn thì tự chạy với Atlantis + GitHub Actions sẽ linh hoạt hơn. Mình đang dùng Atlantis cho production vì cần custom approval workflow.
Q: Terraform hay Pulumi?A: Nếu team quen TypeScript/Python thì Pulumi có learning curve thấp hơn vì viết code thực thụ. Nhưng Terraform HCL dễ đọc hơn với người không phải developer, và community lớn hơn nhiều — tìm module sẵn trên Terraform Registry rất nhanh. Mình chọn Terraform cho AWS infrastructure, Pulumi cho những phần cần logic phức tạp.
Q: Làm sao xử lý secret trong Terraform?A: Không hardcode secret trong
.tfvars. Dùng AWS Secrets Manager hoặc SSM Parameter Store, rồi reference trong Terraform qua data source. Với database password, tạo random password bằngrandom_passwordresource và store vào Secrets Manager — Terraform quản lý lifecycle mà không lộ secret trong code.
Đúc Kết Sau Nhiều Dự Án Production
Terraform không phải silver bullet — vẫn cần người hiểu AWS đủ sâu để viết module tốt. Nhưng khi đã có nền tảng vững, việc spin up môi trường mới từ vài ngày xuống còn vài phút là hoàn toàn thực tế. Mình đã từng clone toàn bộ production stack sang disaster recovery region trong chưa đầy 20 phút — điều đó không thể làm được nếu hạ tầng vẫn là "click by hand".
Nói thật thì phần khó nhất không phải học HCL syntax, mà là discipline của cả team — không ai được tay đụng Console để thay đổi resource đang được Terraform quản lý. Một khi culture đó được thiết lập, phần còn lại sẽ tự nhiên theo.
