Terraform hoạt động như thế nào? Hiểu cách Terraform "suy nghĩ"
Terraform không chạy tuần tự như một Bash script. Bài viết này giải thích execution model của Terraform từ Desired State, State, Dependency Graph đến Plan, Apply, Refresh và Drift để bạn hiểu Terraform thực sự hoạt động như thế nào.

Terraform hoạt động như thế nào? Hiểu cách Terraform "suy nghĩ"
Đây có lẽ là bài viết quan trọng nhất trong toàn bộ series.
Rất nhiều người học Terraform bằng cách ghi nhớ syntax.
resource ...
variable ...
output ...
data ...Sau đó họ bắt đầu tạo EC2, S3, VPC hoặc Docker Container.
Mọi thứ có vẻ ổn...
Cho đến một ngày Terraform báo:
Plan: 0 to add, 1 to change, 2 to destroy.
hoặc
Cycle detected.
hoặc
Resource already exists.
Lúc này, đa số chỉ biết thử lại, sửa đại vài dòng code rồi chạy terraform apply thêm vài lần.
Vấn đề không nằm ở syntax.
Vấn đề là chúng ta chưa hiểu Terraform đang nghĩ gì.
Sau nhiều năm sử dụng Terraform trong các dự án thực tế, mình nhận ra rằng chỉ cần hiểu execution model của Terraform, bạn sẽ hiểu được khoảng 80% các hành vi của nó.
Đó cũng là mục tiêu của bài viết này.
Terraform không chạy như Bash Script
Đây là hiểu lầm lớn nhất của hầu hết mọi người khi mới bắt đầu.
Nếu mình có một Bash script như sau:
create-network
create-volume
create-containerShell sẽ chạy từ trên xuống dưới.
Step 1
↓
Step 2
↓
Step 3
Đơn giản.
Rất trực quan.
Nhiều người mặc định Terraform cũng làm điều tương tự.
Ví dụ:
resource "docker_network" "network" {}
resource "docker_volume" "volume" {}
resource "docker_container" "app" {}Họ nghĩ Terraform sẽ chạy theo đúng thứ tự xuất hiện trong file.
Thực tế thì không hề.
Terraform gần như không quan tâm resource nào được viết trước hay sau.
Điều nó quan tâm là:
Resource nào phụ thuộc vào resource nào?
Đó là lý do sau này bạn có thể tách hàng trăm file .tf, đặt resource ở bất kỳ đâu mà Terraform vẫn hoạt động chính xác.
Bởi vì Terraform không đọc file theo thứ tự.
Terraform đọc toàn bộ configuration, sau đó tự xây dựng một mô hình hoàn chỉnh trước khi quyết định sẽ làm gì.
Đó chính là lý do Terraform được gọi là một declarative tool.
Bạn chỉ mô tả đích đến.
Terraform sẽ tự tìm đường đi.
Terraform chỉ quan tâm tới ba thứ
Nếu phải tóm tắt Terraform bằng một câu duy nhất thì mình sẽ nói:
Terraform chỉ đang cố gắng làm cho Infrastructure ngoài đời giống với thứ bạn mô tả trong code.
Nghe có vẻ đơn giản.
Nhưng để làm được điều đó, Terraform phải so sánh ba trạng thái khác nhau.
Desired State
Current State
Actual Infrastructure
Nếu bạn hiểu ba khái niệm này, bạn sẽ hiểu gần như toàn bộ Terraform.
Desired State
Desired State là trạng thái mà bạn mong muốn hệ thống đạt được.
Nó chính là những file Terraform bạn đang viết.
Ví dụ:
resource "docker_container" "nginx" {
image = "nginx:latest"
name = "web"
}Bạn không nói:
Tạo container.
Bạn cũng không nói:
Nếu chưa có thì tạo.
Hay:
Nếu có rồi thì update.
Bạn chỉ nói một điều:
Tôi muốn tồn tại một container tên web sử dụng image nginx:latest.
Đó chính là Desired State.
Terraform không quan tâm bạn đi đến trạng thái đó bằng cách nào.
Nó chỉ quan tâm trạng thái cuối cùng.
Đây cũng là sự khác biệt lớn nhất giữa lập trình mệnh lệnh (Imperative Programming) và Infrastructure as Code.
Current State
Terraform cần biết lần chạy trước nó đã tạo những gì.
Nếu không có trí nhớ, mỗi lần chạy Terraform sẽ phải đoán.
Đó là lý do tồn tại của State.
State giống như "bộ nhớ" của Terraform.
Ví dụ:
terraform.tfstate
Trong đó Terraform lưu:
- Resource ID
- Provider metadata
- Attribute
- Dependency
- Mapping giữa code và resource ngoài đời
Nói cách khác:
Desired State là điều bạn muốn.
Current State là điều Terraform nhớ.
Đây là lý do bài tiếp theo của series sẽ dành hoàn toàn để nói về Terraform State.
Actual Infrastructure
Còn một thứ nữa Terraform phải biết.
Đó là hạ tầng đang tồn tại ngoài đời.
Ví dụ:
Docker daemon.
AWS.
Azure.
Google Cloud.
Vercel.
Supabase.
Firebase.
Cloudflare.
...
Đây mới là "sự thật".
Terraform không thể chỉ tin vào file .tf.
Terraform cũng không thể chỉ tin vào State.
Nó phải hỏi provider:
"Hiện tại ngoài đời đang có những gì?"
Ví dụ với Docker Provider, Terraform sẽ gọi Docker API.
Với AWS Provider, Terraform sẽ gọi AWS API.
Với Vercel Provider, Terraform sẽ gọi Vercel API.
Terraform luôn cố gắng biết được trạng thái thực tế của hạ tầng trước khi đưa ra bất kỳ quyết định nào.
Terraform giống như một người đang giải bài toán
Mình rất thích hình dung Terraform giống như một kỹ sư.
Người kỹ sư đó có ba tài liệu trên bàn.
Desired State
(Terraform Code)
▲
│
│
Compare │
│
▼
Current State ---------------- Actual Infrastructure
(terraform.tfstate) (Cloud Provider)
Anh ta sẽ tự hỏi:
Tôi muốn hệ thống như thế nào?
↓
Desired State
Tôi nhớ lần trước mình đã làm gì?
↓
Current State
Hệ thống hiện tại thực sự đang như thế nào?
↓
Actual Infrastructure
Sau khi có đủ ba thông tin này, Terraform mới bắt đầu tính toán.
Không tạo resource.
Không sửa resource.
Không xoá resource.
Mà tính toán.
Đó là bước quan trọng nhất của Terraform.
Trong phần tiếp theo, chúng ta sẽ thấy Terraform xây dựng Dependency Graph, tạo Execution Plan và quyết định chính xác thứ tự thực thi của toàn bộ infrastructure như thế nào.
Terraform không thực thi ngay, nó xây dựng một đồ thị
Đây là điểm khác biệt lớn nhất giữa Terraform và hầu hết các công cụ automation khác.
Sau khi đã biết:
- Desired State
- Current State
- Actual Infrastructure
Terraform vẫn chưa tạo bất kỳ resource nào.
Thay vào đó, nó sẽ phân tích toàn bộ configuration để trả lời một câu hỏi:
Resource nào phụ thuộc vào resource nào?
Kết quả của quá trình này là một Dependency Graph, hay chính xác hơn là một Directed Acyclic Graph (DAG).
Đây chính là "bản đồ" mà Terraform sử dụng để quyết định thứ tự thực thi.
Dependency Graph (DAG)
Giả sử bạn có một ứng dụng Docker đơn giản.
resource "docker_network" "app" {
name = "app-network"
}
resource "docker_volume" "data" {
name = "app-data"
}
resource "docker_container" "nginx" {
image = "nginx:latest"
networks_advanced {
name = docker_network.app.name
}
volumes {
volume_name = docker_volume.data.name
container_path = "/usr/share/nginx/html"
}
}Nhìn vào đoạn code trên, Terraform hiểu ngay rằng:
- Container cần Network.
- Container cần Volume.
- Network và Volume không phụ thuộc vào nhau.
Terraform sẽ xây dựng đồ thị như sau:
docker_network
│
│
▼
docker_container
▲
│
│
docker_volumeĐiều quan trọng là:
Terraform không tạo Network trước vì nó nằm ở trên file.
Terraform tạo Network trước vì Container phụ thuộc vào Network.
Đó là hai khái niệm hoàn toàn khác nhau.
Thứ tự file gần như không có ý nghĩa
Ví dụ bạn đảo ngược toàn bộ file.
resource "docker_container" "nginx" {
...
}
resource "docker_volume" "data" {
...
}
resource "docker_network" "app" {
...
}Terraform vẫn tạo theo đúng thứ tự:
Network
↓
Volume
↓
ContainerThậm chí bạn có thể chia project thành:
network.tf
container.tf
volume.tf
variables.tf
outputs.tf
Hay:
modules/
live/
providers.tf
versions.tf
main.tf
Terraform vẫn chỉ nhìn vào dependency graph.
Đó là lý do các Terraform project lớn có thể chứa hàng trăm file mà vẫn hoạt động ổn định.
Terraform còn có thể chạy song song
Một lợi ích rất lớn khi sử dụng DAG là Terraform biết resource nào không phụ thuộc lẫn nhau.
Ví dụ:
VPC
/ \
/ \
Subnet A Subnet B
\ /
\ /
EC2 InstanceSau khi tạo VPC, Terraform nhận ra:
- Subnet A độc lập.
- Subnet B độc lập.
Hai resource này có thể được tạo cùng lúc.
Create VPC
↓
Create Subnet A
Create Subnet B
↓
Create EC2Terraform sẽ thực hiện song song để rút ngắn thời gian triển khai.
Nếu project có hàng trăm resource độc lập, lợi ích này sẽ rất rõ ràng.
Đây cũng là lý do Terraform thường nhanh hơn nhiều người tưởng.
Terraform không "đoán"
Có một câu hỏi mình từng gặp khá nhiều:
"Làm sao Terraform biết Container cần Network?"
Câu trả lời là:
Terraform không đoán.
Nó phân tích các biểu thức tham chiếu (references).
Ví dụ:
docker_network.app.nameTerraform thấy:
Container
↓
tham chiếu
↓
Network
Vậy thì Network phải được tạo trước.
Đây được gọi là implicit dependency.
Bạn không cần viết thêm bất kỳ cấu hình nào.
Terraform tự hiểu.
Còn depends_on để làm gì?
Nếu Terraform đã tự xây được graph thì tại sao lại có depends_on?
Bởi vì không phải dependency nào cũng thể hiện qua việc tham chiếu dữ liệu.
Ví dụ:
Bạn có một resource tạo Firewall Rule.
Một resource khác deploy ứng dụng.
Hai resource không dùng dữ liệu của nhau.
Nhưng bạn vẫn muốn Firewall hoàn thành trước.
Lúc này Terraform sẽ không tự biết.
Bạn cần nói rõ:
resource "..." "application" {
...
depends_on = [
firewall_rule.main
]
}Đây được gọi là explicit dependency.
Trong thực tế, mình rất hiếm khi dùng depends_on.
Nếu phải dùng quá nhiều, thường đó là dấu hiệu module hoặc kiến trúc đang có vấn đề.
Sau khi xây xong graph, Terraform bắt đầu tính toán
Đến thời điểm này Terraform đã có:
- Desired State
- Current State
- Actual Infrastructure
- Dependency Graph
Lúc này nó mới bắt đầu so sánh.
Terraform sẽ đi qua từng node trong graph và trả lời những câu hỏi như:
- Resource này đã tồn tại chưa?
- Có thuộc tính nào thay đổi không?
- Có cần tạo mới không?
- Có cần cập nhật không?
- Có cần xoá không?
- Có resource nào phải được thay thế hoàn toàn không?
Kết quả của toàn bộ quá trình này chính là thứ chúng ta gọi là...
Execution Plan.
Terraform Plan là gì?
Nhiều người nghĩ terraform plan chỉ là một lệnh preview.
Thực ra nó quan trọng hơn rất nhiều.
Plan chính là kết quả của quá trình tính toán.
Terraform sẽ tạo ra một "bản diff" giữa:
Desired State
↓
Current State
↓
Actual InfrastructureNếu mọi thứ giống nhau.
Terraform sẽ nói:
No changes.
Infrastructure matches the configuration.Nếu có khác biệt.
Terraform sẽ liệt kê toàn bộ thay đổi.
Ví dụ:
Plan:
+ create docker_container.web
~ update docker_network.app
- destroy docker_volume.oldBạn có thể xem Plan giống như git diff.
Nó không thay đổi hệ thống.
Nó chỉ cho bạn biết:
Nếu Apply ngay bây giờ, điều gì sẽ xảy ra?
Đây là lý do trong môi trường production, rất nhiều đội ngũ yêu cầu review Plan trước khi cho phép Apply.
Họ không review file .tf.
Họ review ảnh hưởng thực tế lên hạ tầng.
Đó mới là thứ quan trọng.
Terraform Apply thực sự làm gì?
Đây là một hiểu lầm rất phổ biến.
Nhiều người nghĩ:
terraform apply
↓
Đọc file .tf
↓
Tạo resourceThực tế, Terraform không làm như vậy.
Sau khi hoàn thành toàn bộ các bước trước đó, Terraform đã có một Execution Plan.
terraform apply đơn giản là thực thi bản kế hoạch đó.
Hay nói cách khác:
Configuration
│
▼
Execution Plan
│
▼
Apply
Terraform sẽ đi theo đúng Dependency Graph đã xây dựng trước đó.
Ví dụ:
Create Network
↓
Create Volume
↓
Create ContainerNếu Network tạo thất bại.
Terraform sẽ không tiếp tục tạo Container.
Nếu Container phụ thuộc vào nhiều resource khác, Terraform sẽ chờ tất cả hoàn thành trước khi tiếp tục.
Đây là lý do Apply luôn tuân thủ Dependency Graph.
Terraform không "chạy lại từ đầu"
Một điểm rất hay của Terraform là nó chỉ thực hiện những gì cần thiết.
Giả sử lần đầu bạn có:
resource "docker_container" "web" {
image = "nginx:1.27"
}Terraform tạo thành công.
Sau đó bạn đổi thành:
image = "nginx:1.28"Terraform sẽ không:
- Xoá toàn bộ project
- Tạo lại mọi resource
Nó chỉ nhận ra:
Image changed.
Sau đó quyết định:
- Update?
- Hay Replace?
Tùy thuộc vào cách Provider định nghĩa resource đó.
Đây cũng là lý do tại sao hiểu Provider rất quan trọng.
Không phải mọi thay đổi đều có thể update tại chỗ.
Có những thay đổi bắt buộc phải destroy rồi create lại.
Một số thay đổi sẽ dẫn đến Replace
Ví dụ đơn giản.
Bạn đổi tên Docker Network.
name = "network-a"↓
name = "network-b"Docker không hỗ trợ đổi tên Network.
Terraform sẽ tính toán:
Destroy Network A
↓
Create Network BChứ không phải:
Rename NetworkTerraform chỉ có thể làm những gì Provider hỗ trợ.
Nếu Provider nói resource này là immutable, Terraform sẽ chọn Replace.
Đây là lý do đôi khi bạn chỉ sửa một dòng code nhưng Plan lại hiển thị:
-/+ resource ...Đó không phải bug.
Đó là quyết định của Provider.
Destroy cũng chỉ là một Execution Plan
Nhiều người nghĩ terraform destroy là một chế độ đặc biệt.
Thực ra không phải.
Terraform chỉ tạo ra một Execution Plan khác.
Thay vì hướng tới trạng thái:
Infrastructure tồn tại
Desired State lúc này là:
Không còn resource nào.
Terraform sẽ tính toán:
Current State
↓
Desired State = Empty
↓
Destroy Everything
Và tiếp tục sử dụng Dependency Graph.
Ví dụ:
Container
↓
Volume
↓
NetworkTerraform sẽ destroy theo thứ tự ngược lại.
Destroy Container
↓
Destroy Volume
↓
Destroy NetworkNếu làm ngược lại, Container sẽ mất kết nối hoặc phát sinh lỗi.
Đây là một lợi ích khác của DAG.
Terraform luôn biết thứ tự an toàn để tạo và xoá resource.
Refresh là gì?
Đến đây có thể bạn sẽ thắc mắc:
Nếu ai đó thay đổi hạ tầng trực tiếp trên Cloud thì sao?
Ví dụ:
- Vào AWS Console đổi tên EC2.
- Vào Vercel Dashboard sửa Environment Variable.
- Vào Firebase Console tạo thêm App.
- Vào Docker Desktop xoá Container.
Terraform đâu biết những thay đổi này?
Đó là nhiệm vụ của Refresh.
Refresh là quá trình Terraform hỏi Provider:
"Hãy cho tôi biết trạng thái thực tế của hạ tầng."
Ví dụ:
Terraform
↓
AWS API
↓
EC2 Name = backend-prodSau đó Terraform so sánh với State và Configuration.
Refresh không tạo resource.
Refresh không sửa resource.
Nó chỉ đồng bộ thông tin để Terraform có dữ liệu mới nhất trước khi tính toán.
Trong các phiên bản Terraform hiện nay, bước refresh thường được thực hiện tự động trước khi lập kế hoạch (plan) hoặc áp dụng (apply), nên bạn hiếm khi phải chạy riêng lệnh refresh.
Drift là gì?
Drift là một khái niệm mà bất kỳ team nào làm IaC cũng sẽ gặp.
Drift xảy ra khi:
Infrastructure ngoài đời không còn giống với Infrastructure được mô tả trong code.
Ví dụ.
Bạn deploy một Docker Container.
Terraform
↓
Container webMột tuần sau.
Một người khác SSH vào server.
Sau đó chạy:
docker stop webHoặc đổi image.
Hoặc đổi network.
Hoặc sửa trực tiếp trên AWS Console.
Lúc này:
Terraform Code
≠
Actual InfrastructureĐó chính là Drift.
Terraform xử lý Drift như thế nào?
Khi chạy terraform plan, Terraform sẽ:
- Đọc Configuration.
- Đọc State.
- Refresh từ Provider.
- So sánh tất cả.
Nếu phát hiện Drift, Plan sẽ hiển thị những thay đổi cần thiết để đưa hệ thống quay trở lại Desired State.
Ví dụ:
~ update docker_container.web
image:
nginx:1.28
↓
nginx:1.27Điều này có nghĩa:
Terraform không quan tâm ai là người thay đổi.
Terraform chỉ quan tâm:
Infrastructure hiện tại không còn giống với code.
Nếu mục tiêu của bạn là giữ thay đổi đó, hãy cập nhật code.
Nếu không, lần Apply tiếp theo sẽ đưa hạ tầng trở lại đúng như Configuration.
Đó chính là sức mạnh của Infrastructure as Code.
Code luôn là nguồn chân lý (source of truth).
Toàn bộ cách Terraform "suy nghĩ"
Đến đây, chúng ta có thể ghép tất cả các mảnh lại với nhau.
Mỗi khi bạn chạy:
terraform applyTerraform sẽ thực hiện quy trình sau:
terraform apply
│
▼
Đọc toàn bộ Terraform Configuration
│
▼
Load các Provider
│
▼
Đọc Current State (.tfstate)
│
▼
Query Actual Infrastructure qua Provider
│
▼
Xây dựng Dependency Graph (DAG)
│
▼
So sánh Desired, Current và Actual State
│
▼
Tạo Execution Plan (Diff)
│
▼
Thực thi Plan theo Dependency Graph
│
▼
Cập nhật Terraform StateĐây chính là execution model của Terraform.
Nếu bạn hiểu sơ đồ này, rất nhiều hành vi tưởng chừng "khó hiểu" của Terraform sẽ trở nên hợp lý.
Bạn sẽ biết vì sao Terraform cần State, vì sao nó phải đọc Provider trước khi Apply, vì sao Drift xảy ra, vì sao có những resource bị Replace thay vì Update, và vì sao thứ tự các file .tf gần như không quan trọng.
Kết luận
Mình luôn cho rằng Terraform không phải là một công cụ để "chạy script tạo hạ tầng".
Terraform là một state reconciliation engine.
Nó liên tục trả lời một câu hỏi duy nhất:
"Làm thế nào để đưa hạ tầng hiện tại về đúng trạng thái mà người dùng mong muốn?"
Đó là lý do Terraform cần:
- Desired State
- Current State
- Actual Infrastructure
- Dependency Graph
- Execution Plan
Hiểu được cách Terraform suy nghĩ sẽ giúp bạn giải thích được hầu hết mọi hành vi của nó, thay vì chỉ ghi nhớ các lệnh hay syntax.
Đây cũng là nền tảng để bước sang chủ đề tiếp theo của series.
Những điều cần nhớ
- Terraform là công cụ declarative, không phải công cụ chạy tuần tự như Bash script.
- Terraform chỉ quan tâm đến việc đồng bộ Desired State với Actual Infrastructure.
- Terraform sử dụng Current State như "bộ nhớ" để theo dõi những gì đã quản lý.
- Trước khi thực thi, Terraform luôn xây dựng Dependency Graph (DAG) và tạo Execution Plan.
terraform applychỉ thực thi kế hoạch đã được tính toán.- Drift xảy ra khi hạ tầng thực tế không còn khớp với code.
- Trong Infrastructure as Code, code luôn là source of truth.
Bài tiếp theo
Ở bài viết này, chúng ta đã nhắc đến Terraform State rất nhiều lần nhưng chưa đi sâu vào chi tiết.
Vậy State thực sự là gì?
Tại sao Terraform không thể hoạt động nếu thiếu State?
State Locking là gì?
Remote State khác gì Local State?
Và tại sao nhiều team từng "phá" production chỉ vì chỉnh sửa một file .tfstate?
Đó sẽ là nội dung của bài viết tiếp theo:
Terraform State — Bộ não của Terraform
Tiếp tục series
Bình luận
Đang tải bình luận…