IaCInfrastructure as Codeterraformterraform mistakes

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.

22 thg 7, 202611 min readCập nhật 22 thg 7, 2026
Những sai lầm phổ biến khi dùng Terraform (và cách tránh chúng)

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:

code
allocated_storage = 100

Reality:

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ụ.

code
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ụ.

code
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:

code
depends_on

Khi 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:

code
depends_on

Nế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ụ.

code
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ụ.

code
variable "instance_type" {
 
  type = string
 
}

Hoặc.

code
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.

code
terraform apply

Terraform 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ụ.

code
- aws_db_instance.main
 
- allocated_storage = 200
 
+ allocated_storage = 100

Nếu đọc kỹ.

Bạn sẽ phát hiện:

Terraform đang chuẩn bị giảm dung lượng database.

Hoặc.

code
-/+ aws_instance.web

Dấ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ụ.

code
terraform {
 
  required_providers {
 
    aws = {
 
      source  = "hashicorp/aws"
 
      version = "~> 6.0"
 
    }
 
  }
 
}

Hoặc.

code
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ụ.

code
terraform {
 
  required_version = "~> 1.13"
 
}

Provider.

code
version = "~> 6.0"

Module.

code
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.

Bình luận

0/2000

Đang tải bình luận…