Những sai lầm phổ biến khi dùng Terraform (và cách tránh chúng)
Tổng hợp những sai lầm phổ biến mà hầu hết mọi người đều gặp khi sử dụng Terraform, từ quản lý state, module, workflow cho đến collaboration trong production.

Những sai lầm phổ biến khi dùng Terraform (và cách tránh chúng)
Đến thời điểm này của series, chúng ta đã đi qua gần như toàn bộ những khái niệm quan trọng nhất của Terraform.
Chúng ta đã biết:
- Terraform hoạt động như thế nào
- Terraform State là gì
- Plan & Apply
- Variables và Outputs
- Data Sources
- Các tính năng thường dùng như
for_each,count,locals,depends_on,lifecycle - Thiết kế repository
- Import infrastructure
- Remote State Backend
Nếu chỉ làm các project nhỏ, chừng đó kiến thức gần như đã đủ.
Nhưng khi bắt đầu làm việc với production, bạn sẽ nhận ra một điều:
Hầu hết sự cố không đến từ việc "không biết Terraform", mà đến từ việc sử dụng Terraform sai cách.
Rất nhiều team đã từng gặp những tình huống như:
- Terraform muốn recreate cả hệ thống chỉ vì đổi tên resource.
- Hai người cùng apply khiến state bị conflict.
- Một thay đổi nhỏ nhưng lại ảnh hưởng tới hàng chục resource khác.
- Terraform xóa mất resource đang chạy production.
- Repository ngày càng lớn và không ai dám sửa.
Điều thú vị là gần như tất cả những vấn đề này đều lặp đi lặp lại ở rất nhiều công ty.
Trong bài viết này, chúng ta sẽ cùng nhìn lại những sai lầm phổ biến nhất khi sử dụng Terraform và cách tránh chúng trước khi chúng trở thành sự cố trong production.
Sai lầm #1 — Lưu Terraform State trong Git
Đây có lẽ là sai lầm phổ biến nhất của người mới.
Repository:
terraform/
├── main.tf
├── variables.tf
├── outputs.tf
└── terraform.tfstate
Sau đó:
git add .
git commit
git push
Nghe có vẻ vô hại.
Nhưng thực tế lại rất nguy hiểm.
Terraform State không phải source code.
Terraform State là dữ liệu runtime.
Ví dụ:
terraform.tfstate
Bên trong sẽ chứa:
- Resource IDs
- Network IDs
- Database IDs
- ARN
- IP Address
- Dependencies
- Metadata
Thậm chí có thể chứa:
- Password
- Connection string
- Secrets
- Access token
Tùy provider.
Điều này đồng nghĩa:
Git Repository
↓
Terraform State
↓
Production Information
Nếu repository bị public hoặc bị leak:
Toàn bộ thông tin infrastructure cũng bị lộ theo.
State luôn thay đổi
Khác với source code.
Terraform State thay đổi sau gần như mỗi lần Apply.
Ví dụ:
Developer A
terraform apply
State:
Version 10
Developer B:
git pull
terraform apply
State:
Version 11
Developer A:
git push
Conflict.
Git không sinh ra để merge Terraform State.
State không thể merge bằng tay giống source code.
Không có Lock
Một vấn đề khác.
Developer A:
terraform apply
Đồng thời.
Developer B:
terraform apply
Hai người cùng ghi vào cùng một file.
Nếu dùng local state.
Terraform hoàn toàn không biết.
Kết quả có thể là:
- Corrupted state
- Lost update
- Drift
- Resource mapping sai
Đó là lý do Remote Backend luôn hỗ trợ State Locking.
Best Practice
Không commit State.
Thay vào đó:
Git
main.tf
variables.tf
outputs.tf
✔ Commit
terraform.tfstate
✘ Never
State nên nằm ở:
- Terraform Cloud
- S3 + DynamoDB (legacy)
- Azure Blob Storage
- Google Cloud Storage
Tất cả đều có:
- Versioning
- Locking
- Backup
- Collaboration
Đây cũng là lý do chúng ta đã dành cả bài trước để nói về Remote State Backend.
Sai lầm #2 — Một repository quản lý toàn bộ infrastructure
Đây là lỗi mà rất nhiều team mắc phải khi project bắt đầu lớn lên.
Ban đầu.
Repository rất đơn giản.
terraform/
main.tf
network.tf
database.tf
ec2.tf
dns.tf
Mọi thứ vẫn ổn.
Sau vài tháng.
terraform/
main.tf
network.tf
database.tf
cache.tf
lambda.tf
ecs.tf
eks.tf
monitoring.tf
cloudfront.tf
route53.tf
s3.tf
iam.tf
security.tf
vpn.tf
...
Sau một năm.
terraform/
150 files
900 resources
25 modules
1 terraform apply
Lúc này, mỗi lần chạy:
terraform plan
Terraform phải đọc toàn bộ graph.
900 Resources
↓
Refresh
↓
Dependency Graph
↓
Execution Plan
Plan có thể mất vài phút.
Apply cũng tương tự.
Một thay đổi nhỏ phải kiểm tra cả hệ thống
Giả sử bạn chỉ muốn sửa:
EC2 Tag
Terraform vẫn phải:
- Refresh toàn bộ state
- Đọc toàn bộ dependency graph
- So sánh toàn bộ infrastructure
Mặc dù chỉ thay đổi:
+ tag = "production"
Đó là lý do Plan ngày càng chậm.
Blast Radius quá lớn
Đây mới là điều đáng sợ nhất.
Ví dụ.
Bạn sửa:
modules/network
Terraform có thể ảnh hưởng tới:
- VPC
- Route Tables
- NAT Gateway
- Subnets
- Security Groups
- ECS
- EKS
- Load Balancer
- Database
- Bastion
Một thay đổi nhỏ.
Nhưng phạm vi ảnh hưởng rất lớn.
Đây gọi là:
Blast Radius.
Blast Radius càng lớn.
Rủi ro càng cao.
Best Practice
Thay vì một root module khổng lồ.
Hãy chia infrastructure theo các thành phần độc lập.
Ví dụ:
live/
production/
network/
database/
application/
monitoring/
dns/
Mỗi thư mục:
- Có state riêng
- Có lifecycle riêng
- Có pipeline riêng
- Có quyền riêng
- Có blast radius nhỏ
Đây cũng chính là lý do rất nhiều tổ chức sử dụng Terragrunt hoặc kiến trúc multi-root Terraform khi hệ thống ngày càng phát triển.
Sai lầm #3 — Chỉnh sửa hạ tầng trực tiếp trên Cloud Console
Terraform được tạo ra với một triết lý rất rõ ràng:
Source of Truth phải là code.
Nhưng trong thực tế, rất nhiều người vẫn làm như sau.
Có một EC2 đang chạy production.
Đột nhiên cần tăng disk.
Thay vì sửa Terraform.
Họ mở AWS Console.
EC2
↓
Edit
↓
Volume
↓
100 GB → 200 GB
↓
Save
Mọi thứ hoạt động.
Server vẫn chạy.
Không có downtime.
Có vẻ như đây là cách nhanh nhất để xử lý.
Nhưng thực tế, bạn vừa tạo ra một vấn đề mà Terraform hoàn toàn không biết đến.
Sai lầm #4 — Để Infrastructure Drift xảy ra
Tiếp nối ví dụ ở phần trước.
Bạn đã thay đổi EC2 trực tiếp trên AWS Console.
Terraform
↓
EC2 (100 GB)
Sau đó.
Console:
100 GB
↓
200 GB
Terraform hoàn toàn không biết điều này.
Lúc này.
Thực tế hạ tầng đã trở thành:
Terraform Configuration
↓
100 GB
≠
Actual Infrastructure
↓
200 GB
Đây chính là:
Infrastructure Drift.
Drift nguy hiểm như thế nào?
Lúc đầu.
Có thể bạn nghĩ:
"Không sao, chỉ đổi một chút."
Nhưng sau vài tháng.
Rất nhiều thay đổi nhỏ xuất hiện.
Ví dụ:
- Security Group thêm rule
- IAM Policy chỉnh bằng tay
- S3 bật Versioning
- EC2 đổi Instance Type
- Load Balancer đổi Listener
- Database tăng Storage
Terraform không hề biết.
Đến một ngày.
Bạn chạy:
terraform apply
Terraform sẽ cố đưa infrastructure trở về đúng với code.
Ví dụ.
Code:
allocated_storage = 100Reality:
200 GB
Terraform:
Plan:
200
↓
100
Nếu provider cho phép.
Terraform sẽ cố rollback.
Điều này có thể gây downtime hoặc mất dữ liệu.
Làm sao để tránh Drift?
Nguyên tắc rất đơn giản.
Không thay đổi infrastructure bằng tay.
Nếu cần thay đổi.
Luôn sửa:
Terraform Code
Sau đó:
terraform plan
terraform apply
Trong trường hợp bắt buộc phải chỉnh trực tiếp (ví dụ sự cố production).
Hãy cập nhật lại Terraform ngay sau đó.
Đừng để code và infrastructure khác nhau quá lâu.
Sai lầm #5 — Lạm dụng depends_on
Khi mới học Terraform.
Rất nhiều người nghĩ:
"Cứ thêm depends_on cho chắc."
Ví dụ.
resource "aws_instance" "app" {
...
depends_on = [
aws_security_group.app,
aws_iam_role.app,
aws_subnet.private,
aws_route_table.private
]
}Sau một thời gian.
Resource nào cũng có:
depends_on
Cuối cùng.
Dependency graph trở thành:
A
↓
B
↓
C
↓
D
↓
E
↓
F
↓
G
Terraform gần như mất khả năng chạy song song.
Terraform vốn đã hiểu Dependency
Ví dụ.
resource "aws_security_group" "app" {}
resource "aws_instance" "app" {
security_groups = [
aws_security_group.app.id
]
}Terraform đã tự hiểu.
Security Group
↓
EC2
Không cần:
depends_onKhi nào mới nên dùng?
Chỉ dùng khi dependency không thể suy luận.
Ví dụ.
Một resource cần:
- API đã enable
- IAM propagation
- Một process ngoài Terraform
Hoặc các provider có dependency ẩn.
Đây mới là lúc nên dùng:
depends_onNếu mọi resource đều có depends_on.
Đó thường là dấu hiệu thiết kế chưa tốt.
Sai lầm #6 — Hardcode giá trị ở khắp nơi
Ví dụ.
resource "aws_instance" "web" {
ami = "ami-0abc123"
instance_type = "t3.medium"
tags = {
Environment = "production"
Team = "backend"
}
}Ban đầu.
Không có vấn đề.
Sau vài tháng.
Repository xuất hiện:
ami-0abc123
ami-0abc123
ami-0abc123
ami-0abc123
Một ngày.
AMI cần đổi.
Bạn phải sửa:
20 files
↓
40 files
↓
80 files
Đây chính là hậu quả của việc hardcode.
Hardcode còn nguy hiểm hơn bạn nghĩ
Không chỉ AMI.
Rất nhiều giá trị khác cũng thường bị hardcode.
Ví dụ.
Region
Availability Zone
CIDR
Subnet
Instance Type
Tags
Environment
Project Name
Bucket Name
Khi công ty mở thêm environment.
dev
staging
production
sandbox
Bạn sẽ phải copy rất nhiều code.
Repository bắt đầu đầy duplicate.
Best Practice
Những giá trị có khả năng thay đổi.
Nên được đưa thành:
- Variables
- Locals
- Data Sources
Ví dụ.
variable "instance_type" {
type = string
}Hoặc.
locals {
common_tags = {
Project = "Payment"
Team = "Platform"
}
}Terraform sẽ dễ tái sử dụng hơn rất nhiều.
Sai lầm #7 — Module làm quá nhiều việc
Module là một trong những tính năng mạnh nhất của Terraform.
Nhưng cũng là nơi dễ bị lạm dụng nhất.
Ví dụ.
Có người tạo một module tên:
production-stack
Bên trong.
VPC
↓
Subnets
↓
Security Groups
↓
ECS
↓
RDS
↓
Redis
↓
IAM
↓
CloudFront
↓
Route53
↓
CloudWatch
Một module.
Quản lý toàn bộ hệ thống.
Vấn đề ở đâu?
Giả sử.
Team Application chỉ muốn sửa:
ECS Task Definition
Họ vẫn phải:
- Download toàn bộ module
- Đọc toàn bộ input
- Hiểu toàn bộ infrastructure
Một thay đổi nhỏ.
Nhưng ảnh hưởng cả module.
Module dần trở thành:
God Module
Không ai dám sửa.
Module nên có một trách nhiệm
Ví dụ.
Thay vì:
production-stack
Hãy chia thành:
modules/
vpc/
ecs/
rds/
alb/
redis/
route53/
Mỗi module:
- Có mục tiêu rõ ràng
- Reusable
- Dễ test
- Dễ review
- Dễ version
Đây cũng là triết lý quen thuộc trong software engineering.
Một module nên chỉ giải quyết một bài toán.
Sai lầm #8 — Không Review Terraform Plan trước khi Apply
Đây là một sai lầm rất phổ biến, đặc biệt ở các dự án nhỏ hoặc khi làm việc một mình.
Workflow thường diễn ra như sau.
terraform applyTerraform hiển thị:
Plan: 12 to add, 4 to change, 8 to destroy.
Nhìn lướt qua.
yes
Enter.
Vài phút sau.
Production bắt đầu gặp sự cố.
Terraform luôn nói trước điều nó sắp làm
Một trong những điểm mạnh nhất của Terraform là Execution Plan.
Terraform sẽ luôn cho bạn biết:
- Resource nào sẽ được tạo
- Resource nào sẽ được cập nhật
- Resource nào sẽ bị xóa
- Thuộc tính nào sẽ thay đổi
Ví dụ.
- aws_db_instance.main
- allocated_storage = 200
+ allocated_storage = 100Nếu đọc kỹ.
Bạn sẽ phát hiện:
Terraform đang chuẩn bị giảm dung lượng database.
Hoặc.
-/+ aws_instance.webDấu:
-/+
Có nghĩa là:
Terraform sẽ destroy rồi recreate.
Đây là điều mà rất nhiều người bỏ qua.
Production nên Review Plan
Ở môi trường production.
Thông thường workflow sẽ là:
Pull Request
↓
terraform plan
↓
Review
↓
Approval
↓
terraform apply
Không phải:
Code
↓
terraform apply
Review Plan cũng giống như review code.
Đó là cơ hội cuối cùng để phát hiện những thay đổi ngoài ý muốn.
Sai lầm #9 — Không sử dụng Version Control cho Module và Provider
Một repository Terraform thường phụ thuộc vào rất nhiều thành phần.
Ví dụ.
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
}
}Hoặc.
module "network" {
source = "git::https://..."
}Nếu không pin version.
Mỗi lần chạy.
Terraform có thể tải về phiên bản mới nhất.
Điều gì có thể xảy ra?
Ví dụ.
Hôm nay.
Provider:
6.10
Ngày mai.
6.11
Một breaking change nhỏ.
Execution Plan thay đổi.
Pipeline bắt đầu fail.
Trong khi code của bạn không hề thay đổi.
Đây là một lỗi rất khó debug.
Best Practice
Luôn pin version.
Ví dụ.
terraform {
required_version = "~> 1.13"
}Provider.
version = "~> 6.0"Module.
ref = "v1.4.0"Điều này giúp mọi môi trường:
- Local
- CI/CD
- Production
Đều chạy cùng một phiên bản.
Sai lầm #10 — Không tách Environment
Một lỗi phổ biến khác.
Là sử dụng cùng một Terraform Root Module cho mọi môi trường.
Ví dụ.
terraform/
main.tf
Sau đó.
terraform apply
Lúc thì deploy dev.
Lúc thì production.
Chỉ khác:
terraform.tfvars
Hoặc.
-var-file
Cách làm này rất dễ gây nhầm lẫn.
Ví dụ.
Bạn định deploy:
dev
Nhưng lại dùng nhầm:
production.tfvars
Kết quả.
Production bị thay đổi ngoài ý muốn.
Best Practice
Tách rõ từng environment.
Ví dụ.
live/
dev/
staging/
production/
Hoặc.
Terragrunt
↓
live
↓
production
↓
network
↓
database
↓
application
Mỗi environment:
- Có state riêng
- Có pipeline riêng
- Có quyền riêng
- Có lifecycle riêng
Điều này giúp giảm đáng kể rủi ro khi triển khai.
Terraform không khó, nhưng rất dễ dùng sai
Điều thú vị là phần lớn những vấn đề trong bài viết này không phải do Terraform.
Terraform vẫn hoạt động đúng như thiết kế.
Vấn đề nằm ở cách chúng ta tổ chức project, quản lý state và xây dựng workflow.
Khi hệ thống còn nhỏ.
Bạn có thể chưa cảm nhận được sự khác biệt.
Nhưng khi số lượng resource tăng lên hàng trăm hoặc hàng nghìn.
Những quyết định nhỏ hôm nay sẽ ảnh hưởng trực tiếp đến khả năng mở rộng và vận hành của toàn bộ hạ tầng trong tương lai.
Nếu phải tóm tắt những lời khuyên quan trọng nhất, đó sẽ là:
- Không lưu Terraform State trong Git.
- Luôn sử dụng Remote Backend.
- Không chỉnh sửa hạ tầng trực tiếp trên Cloud Console.
- Đọc kỹ Execution Plan trước mỗi lần Apply.
- Chia nhỏ infrastructure để giảm blast radius.
- Thiết kế module đơn giản, có một trách nhiệm rõ ràng.
- Tận dụng Variables, Locals và Data Sources thay vì hardcode.
- Pin version cho Terraform, Provider và Module.
- Tách riêng từng environment và workflow triển khai.
Terraform là một công cụ rất mạnh.
Nhưng sức mạnh đó chỉ phát huy tối đa khi được sử dụng với những nguyên tắc phù hợp.
Tổng kết
Sau 11 bài đầu tiên của series, chúng ta đã đi từ những khái niệm nền tảng nhất như Infrastructure as Code, cách Terraform hoạt động, Terraform State cho đến những chủ đề thực tế hơn như thiết kế repository, import hạ tầng hiện có và Remote State Backend.
Đến đây, bạn đã có đủ nền tảng để xây dựng và vận hành các dự án Terraform ở quy mô nhỏ đến trung bình theo đúng các best practices phổ biến.
Tuy nhiên, khi hệ thống tiếp tục phát triển với nhiều môi trường, nhiều tài khoản cloud và nhiều team cùng làm việc, Terraform thuần túy sẽ bắt đầu bộc lộ những hạn chế trong việc quản lý cấu trúc dự án và tái sử dụng cấu hình.
Đó cũng là lý do Terragrunt ra đời.
Ở bài tiếp theo, chúng ta sẽ cùng tìm hiểu khi nào Terraform không còn đủ và vì sao ngày càng nhiều tổ chức sử dụng Terragrunt để quản lý hạ tầng production ở quy mô lớn.
Tiếp tục series
Bình luận
Đang tải bình luận…