Infrastructure as Codeterraformterragrunt

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.

22 thg 7, 202614 min readCập nhật 22 thg 7, 2026
Terraform hoạt động như thế nào? Hiểu cách Terraform "suy nghĩ"

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.

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

code
create-network
 
create-volume
 
create-container

Shell 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ụ:

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

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

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.

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

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

code
resource "docker_container" "nginx" {
  ...
}
 
resource "docker_volume" "data" {
  ...
}
 
resource "docker_network" "app" {
  ...
}

Terraform vẫn tạo theo đúng thứ tự:

code
Network
 

 
Volume
 

 
Container

Thậ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ụ:

code
        VPC
       /   \
      /     \
Subnet A   Subnet B
      \     /
       \   /
       EC2 Instance

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

code
Create VPC
 

 
Create Subnet A
Create Subnet B
 

 
Create EC2

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

code
docker_network.app.name

Terraform 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õ:

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

code
Desired State
 

 
Current State
 

 
Actual Infrastructure

Nếu mọi thứ giống nhau.

Terraform sẽ nói:

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

code
Plan:
 
+ create docker_container.web
 
~ update docker_network.app
 
- destroy docker_volume.old

Bạ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ĩ:

code
terraform apply
 

 
Đọc file .tf
 

 
Tạo resource

Thự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ụ:

code
Create Network
 

 
Create Volume
 

 
Create Container

Nế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ó:

code
resource "docker_container" "web" {
  image = "nginx:1.27"
}

Terraform tạo thành công.

Sau đó bạn đổi thành:

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

code
name = "network-a"

code
name = "network-b"

Docker không hỗ trợ đổi tên Network.

Terraform sẽ tính toán:

code
Destroy Network A
 

 
Create Network B

Chứ không phải:

code
Rename Network

Terraform 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ị:

code
-/+ 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ụ:

code
Container
 

 
Volume
 

 
Network

Terraform sẽ destroy theo thứ tự ngược lại.

code
Destroy Container
 

 
Destroy Volume
 

 
Destroy Network

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

code
Terraform
 

 
AWS API
 

 
EC2 Name = backend-prod

Sau đó 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.

code
Terraform
 

 
Container web

Một tuần sau.

Một người khác SSH vào server.

Sau đó chạy:

code
docker stop web

Hoặc đổi image.

Hoặc đổi network.

Hoặc sửa trực tiếp trên AWS Console.

Lúc này:

code
Terraform Code
 

 
Actual Infrastructure

Đó chính là Drift.


Terraform xử lý Drift như thế nào?

Khi chạy terraform plan, Terraform sẽ:

  1. Đọc Configuration.
  2. Đọc State.
  3. Refresh từ Provider.
  4. 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ụ:

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

code
terraform apply

Terraform sẽ thực hiện quy trình sau:

code
                 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 apply chỉ 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

Bình luận

0/2000

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