Khi “click qua console” không còn đủ nữa
Hồi đầu năm ngoái, team mình gặp một sự cố khá đau: môi trường staging bị xóa nhầm, mất gần 3 ngày để rebuild lại từ đầu. Không phải vì infrastructure phức tạp, mà vì chẳng ai biết chính xác đã cấu hình những gì — mỗi người click mỗi kiểu, không có documentation rõ ràng. Đó là lúc mình bắt đầu tìm hiểu Infrastructure as Code nghiêm túc.
6 tháng sau, production đang chạy ổn — và mình có khá nhiều thứ để kể lại.
Phân tích: Tại sao “ClickOps” là cái bẫy nguy hiểm
AWS Console trực quan, kết quả ngay lập tức — ai mới bắt đầu cũng thấy tiện. Cái giá phải trả thì đến muộn hơn, và thường rất đắt:
- Configuration drift: Dev, staging, production dần dần khác nhau mà không ai biết. Mình từng phát hiện security group ở prod có rule mở port 22 từ
0.0.0.0/0— do ai đó “test nhanh” rồi quên đóng lại, tồn tại gần 2 tháng. - Không reproducible: Muốn tạo môi trường mới giống hệt production? Câu trả lời phụ thuộc vào trí nhớ của người đã setup ban đầu.
- Disaster recovery chậm: Nếu region bị sự cố và cần migrate, mất bao lâu? Với ClickOps, câu trả lời thường là “chưa biết được”.
- Không có audit trail có nghĩa: CloudTrail ghi lại được ai đã thay đổi gì, nhưng không cho biết tại sao — và quan trọng hơn, không cho phép rollback dễ dàng.
Vấn đề cốt lõi: hạ tầng đang tồn tại trong đầu người, không phải trong code.
Các lựa chọn IaC — và tại sao Terraform thắng
Trước khi commit với Terraform, mình đã thử qua các lựa chọn khác:
AWS CloudFormation
Native của AWS, không cần cài thêm. Nhưng YAML/JSON syntax cực kỳ verbose — một stack đơn giản có thể 300+ dòng. Quan trọng hơn: bị lock-in hoàn toàn với AWS.
Ansible
Tốt cho configuration management (cài package, chỉnh config file trên server đã có), nhưng không phải công cụ phù hợp để provision infrastructure. Ansible thiếu khái niệm “state” — không track được resource đang có là gì.
Pulumi
Viết infrastructure bằng Python/TypeScript thật sự — nghe hấp dẫn. Nhưng learning curve cao hơn, ecosystem còn nhỏ, và mình mất khá nhiều thời gian tìm ví dụ cụ thể so với Terraform.
Terraform
Multi-cloud (AWS, GCP, Azure…), HCL syntax dễ đọc hơn CloudFormation, state management tốt. Cộng đồng rất lớn — hầu như bất kỳ resource AWS nào cũng có ví dụ sẵn trên Terraform Registry.
Cài đặt Terraform và setup ban đầu
Cài trên Ubuntu/Debian — mình dùng HashiCorp’s official apt repo để dễ update sau này:
wget -O- https://apt.releases.hashicorp.com/gpg | gpg --dearmor | \
sudo tee /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] \
https://apt.releases.hashicorp.com $(lsb_release -cs) main" | \
sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install terraform
terraform --version
Tiếp theo là AWS credentials. Nếu chưa tạo IAM user, đây là lúc nên làm — least-privilege, chỉ cấp đúng quyền cần thiết:
aws configure
# AWS Access Key ID: AKIA...
# AWS Secret Access Key: ...
# Default region name: ap-northeast-1
# Default output format: json
Terraform config đầu tiên: Tạo EC2 instance
Đây là config tối giản nhất để khởi đầu — một EC2 instance trên Tokyo region, khai báo đủ để Terraform hiểu bạn muốn gì:
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "ap-northeast-1" # Tokyo
}
resource "aws_instance" "web_server" {
ami = "ami-0d52744d6551d851e" # Ubuntu 22.04 LTS Tokyo
instance_type = "t3.micro"
tags = {
Name = "web-server-production"
Environment = "production"
ManagedBy = "terraform"
}
}
output "instance_ip" {
value = aws_instance.web_server.public_ip
}
Workflow cơ bản chỉ có 3 lệnh. Cũng 3 lệnh này mình dùng mỗi ngày, suốt 6 tháng:
# Khởi tạo — download providers về local
terraform init
# Xem trước thay đổi (KHÔNG thực thi)
terraform plan
# Áp dụng thay đổi
terraform apply
Output của terraform plan hiện rõ: resource nào sẽ được tạo (+), xóa (-), hay thay đổi (~). Mình luôn review plan kỹ trước khi apply — đây là safety net quan trọng nhất.
State File — Phần quan trọng nhất mà hầu hết tutorial bỏ qua
Terraform lưu trạng thái infrastructure vào file terraform.tfstate. File này là “nguồn sự thật” để Terraform biết đang quản lý những resource nào.
Sai lầm phổ biến nhất: lưu state file trên máy local. Hậu quả:
- Người khác trong team không thể chạy Terraform
- Mất máy = mất state = Terraform không còn biết đang quản lý gì nữa
- Hai người chạy
terraform applycùng lúc gây conflict — và không có gì ngăn điều đó xảy ra
Giải pháp: Remote backend với S3 + DynamoDB để lock state:
terraform {
backend "s3" {
bucket = "my-terraform-state-bucket"
key = "production/terraform.tfstate"
region = "ap-northeast-1"
encrypt = true
dynamodb_table = "terraform-state-lock"
}
}
Tạo S3 bucket và DynamoDB table trước (lần đầu làm thủ công một lần):
aws s3 mb s3://my-terraform-state-bucket --region ap-northeast-1
aws s3api put-bucket-versioning \
--bucket my-terraform-state-bucket \
--versioning-configuration Status=Enabled
aws dynamodb create-table \
--table-name terraform-state-lock \
--attribute-definitions AttributeName=LockID,AttributeType=S \
--key-schema AttributeName=LockID,KeyType=HASH \
--billing-mode PAY_PER_REQUEST \
--region ap-northeast-1
Tổ chức code Terraform cho dự án thực tế
Sau vài lần refactor, đây là cấu trúc mình đang dùng — đơn giản nhưng scale được khi cần thêm environment:
infrastructure/
├── environments/
│ ├── dev/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── terraform.tfvars
│ └── production/
│ ├── main.tf
│ ├── variables.tf
│ └── terraform.tfvars
└── modules/
├── vpc/
├── ec2/
└── rds/
Variables giúp tránh hardcode và dễ khác biệt hóa giữa dev và production mà không cần duplicate code:
# variables.tf
variable "instance_type" {
description = "EC2 instance type"
type = string
default = "t3.micro"
}
variable "environment" {
description = "Environment name"
type = string
}
# terraform.tfvars (production)
instance_type = "t3.medium"
environment = "production"
Những bài học sau 6 tháng chạy production
Đây là những thứ mình học được — phần lớn sau khi đã làm sai ít nhất một lần:
- Chạy
terraform fmtvàterraform validatetrước mỗi commit — tích hợp vào pre-commit hook hoặc CI để tránh push code lỗi cú pháp. - Không bao giờ edit state file thủ công — nếu cần can thiệp, dùng
terraform state mvhoặcterraform state rm. - Tag tất cả resources:
ManagedBy = "terraform",Environment,Project. Sau 3 tháng nhìn lại AWS Console, biết ngay resource nào đang được quản lý bởi ai. - Review plan trong CI/CD: Setup GitHub Actions để auto-run
terraform plankhi có PR. Output tự động comment vào PR — reviewer thấy ngay thay đổi infrastructure trước khi merge, không cần hỏi nhau. - Tách state theo environment:
dev/terraform.tfstatevàproduction/terraform.tfstateriêng nhau — tránh một lệnh nhầm destroy production.
Một thứ nhỏ nhưng tiện: khi cần debug Terraform output hay kiểm tra JSON từ terraform output -json, mình hay paste vào toolcraft.app/vi/tools/developer/json-formatter để format nhanh cho dễ đọc — tiện hơn nhiều so với mở IDE chỉ để xem một đoạn JSON.
Nên bắt đầu từ đâu?
Nếu bạn đang chạy bất cứ thứ gì trên cloud và chưa dùng IaC, đó là technical debt đang tích lũy ngầm mỗi ngày. Terraform nên được setup từ ngày đầu — không phải sau khi đã có 50 resource click bằng tay, lúc đó import lại rất mệt.
Cách dễ nhất để bắt đầu: chọn một resource đơn giản trong dự án hiện tại — một S3 bucket, một security group — và viết Terraform cho nó. Cảm giác khi chạy terraform destroy rồi terraform apply và thấy mọi thứ tái tạo y chang sẽ thuyết phục bạn tốt hơn bất kỳ bài viết nào.
