Software Architecture

Infrastructure as Code (IaC): Quản trị hạ tầng như quản trị source code (Terraform, Pulumi)

7/19/2026 · 12p đọc


title: "Infrastructure as Code (IaC): Quản trị hạ tầng như quản trị source code (Terraform, Pulumi)"
series: "Software Architecture Masterclass"
part: "Phần 3 — Hệ thống Phân tán & Hạ tầng"
order: 27
audience: "Solutions Architect & Technical Lead"
reading_time: "13 phút"
tags:

  • infrastructure-as-code
  • terraform
  • pulumi
  • devops
  • state-management
  • configuration-drift
  • platform-engineering

Infrastructure as Code (IaC): Quản trị hạ tầng như quản trị source code (Terraform, Pulumi)

3 giờ sáng, production down. Root cause sau 40 phút điều tra: một security group bị sửa tay trên console AWS hai tuần trước, để "test nhanh" một tính năng, rồi quên revert. Không ai nhớ ai sửa, sửa gì, sửa khi nào. Không commit, không PR, không log thay đổi nào ngoài CloudTrail — thứ chẳng ai tra cứu cho tới khi có sự cố.

Đây không phải câu chuyện hiếm. Đây là hệ quả tất yếu của một mô hình vận hành hạ tầng đã hết hạn sử dụng từ lâu: click chuột trên console, ghi nhớ trong đầu vài kỹ sư senior, và hy vọng rằng "staging giống hệt production" là đúng. Vấn đề không nằm ở năng lực cá nhân — nó nằm ở việc hạ tầng được coi là một hoạt động vận hành (operational activity), trong khi nó cần được coi là một sản phẩm phần mềm (software artifact).

Bài này nói về Infrastructure as Code (IaC) — không phải như một buzzword DevOps, mà như một quyết định kiến trúc có đánh đổi thật, với một rủi ro vận hành mới mà nhiều đội ngũ không lường trước: quản lý state.

The Problem

Hãy hình dung một công ty tăng trưởng từ 5 lên 50 kỹ sư trong 18 tháng. Hạ tầng ban đầu — vài EC2 instance, một RDS, một load balancer — được một kỹ sư dựng bằng tay qua AWS Console trong một buổi chiều. Nó hoạt động. Không ai đụng vào nó nữa cho tới khi cần scale.

Ba vấn đề bắt đầu xuất hiện đồng thời:

Configuration drift giữa các môi trường. Staging được dựng lại sau production ba tháng, bởi một kỹ sư khác, theo trí nhớ về "hình như production cấu hình thế này". Một security group rule bị thiếu. Một biến môi trường có giá trị khác. Kết quả: "chạy được trên staging của tôi" nhưng lỗi trên production, hoặc ngược lại — bug chỉ tái hiện được ở một môi trường, khiến việc debug trở thành trò đoán mò.

Không có audit trail đáng tin cậy. Khi có sự cố, câu hỏi đầu tiên luôn là "cái gì đã thay đổi gần đây?". Với console thủ công, câu trả lời nằm rải rác trong CloudTrail logs (nếu có bật, nếu có ai buồn tra), trong trí nhớ của người sửa (nếu họ còn nhớ, nếu họ còn ở công ty), hoặc không tồn tại. Không có diff, không có lý do (rationale), không có người review.

"Works on my account" — hạ tầng không tái tạo được. Khi cần dựng một môi trường mới (region mới, disaster recovery site, môi trường demo cho sales), không có cách nào tái tạo chính xác những gì đang chạy production ngoài việc... đi qua từng màn hình console và ghi chú lại theo trí nhớ. Đây là rủi ro sinh tồn (existential risk) thực sự với một số công ty: nếu region chính sập và không ai biết chính xác cấu hình cần dựng lại, RTO (Recovery Time Objective) không còn là con số kỹ thuật — nó là số giờ công ty mất doanh thu và uy tín.

Cả ba vấn đề đều có chung một gốc rễ: hạ tầng đang được coi là trạng thái hiện tại (current state) thay vì được coi là định nghĩa có thể tái tạo (reproducible definition). Đó chính xác là vấn đề mà source code từng giải quyết cho ứng dụng — và Infrastructure as Code áp dụng cùng một triết lý cho hạ tầng.

The Theory

Infrastructure as Code là việc định nghĩa hạ tầng (network, compute, storage, IAM, DNS...) bằng file khai báo (declarative) hoặc mệnh lệnh (imperative), được lưu trong version control, và áp dụng (apply) thông qua một pipeline nhất quán — thay vì thao tác trực tiếp trên console hay SSH vào server để chỉnh tay.

Có hai trường phái chính:

Declarative (khai báo) — Terraform, CloudFormation, Pulumi (ở chế độ declarative), Kubernetes YAML. Bạn mô tả trạng thái mong muốn (desired state): "tôi muốn có một RDS instance loại db.t3.medium, engine MySQL 8, trong subnet X". Công cụ tự tính toán diff giữa trạng thái hiện tại và trạng thái mong muốn, rồi thực thi các bước cần thiết để đạt được nó. Bạn không viết "làm sao để tạo" — bạn viết "kết quả cuối cùng là gì".

Imperative (mệnh lệnh) — Ansible (chủ yếu), scripts bash tự viết, AWS CDK (compile ra CloudFormation nhưng code là imperative). Bạn mô tả các bước cần thực hiện theo thứ tự: "chạy lệnh A, rồi lệnh B, rồi kiểm tra điều kiện C". Linh hoạt hơn cho logic phức tạp, nhưng khó đảm bảo tính idempotent (chạy lại nhiều lần cho cùng một kết quả) nếu không tự viết guard clause cẩn thận.

Terraform và Pulumi (ở core) đại diện cho trường phái declarative với một vòng đời ba bước cố định:

  1. Write — định nghĩa resource trong code (HCL với Terraform, TypeScript/Python/Go với Pulumi).
  2. Plan — công cụ so sánh code với state (bản ghi trạng thái hạ tầng hiện tại đã biết) và trạng thái thật trên cloud provider, sinh ra một execution plan: resource nào sẽ được tạo mới, sửa tại chỗ (in-place), hay buộc phải hủy-tạo-lại (destroy and recreate).
  3. Apply — thực thi plan đã được duyệt.

Điểm mấu chốt về triết lý — không phải chi tiết công cụ cụ thể — là khái niệm state: một bản ghi trung gian ánh xạ từ resource trong code sang resource ID thật trên cloud. State là thứ cho phép công cụ biết "resource này tôi đã tạo trước đó, giờ cần sửa gì" thay vì tạo trùng lặp mỗi lần chạy. Đây cũng chính là nguồn gốc của loại rủi ro vận hành mới mà phần Trade-off sẽ nói kỹ.

Nguyên tắc nền tảng thứ hai là idempotency: chạy apply hai lần liên tiếp với cùng một code, lần thứ hai phải là no-op (không có gì thay đổi). Đây là thuộc tính bắt buộc để pipeline CI/CD có thể chạy IaC một cách an toàn, lặp lại, không cần con người phán đoán "lần này có nên chạy không".

The Trade-off

Đây là phần bắt buộc phải trung thực, vì phong trào "IaC everything" thường bị trình bày như một chiến thắng miễn phí. Nó không phải.

Được gì:

  • Loại bỏ configuration drift. Trạng thái hạ tầng của mọi môi trường được suy ra từ cùng một nguồn sự thật (source of truth) — code trong git. Staging và production khác nhau ở giá trị biến (instance size, replica count), không khác nhau ở cấu trúc.
  • Review và audit trail có thật. Mọi thay đổi hạ tầng đi qua pull request: ai đề xuất, ai duyệt, lý do gì, diff chính xác là gì. Đây là bằng chứng compliance (SOC2, ISO 27001) mà console thủ công không bao giờ cung cấp được đầy đủ.
  • Rollback như rollback code. git revert một commit hạ tầng, chạy lại planapply, hạ tầng quay về trạng thái trước đó — về nguyên tắc. (Có ngoại lệ quan trọng ở phần "mất gì".)
  • Tái tạo được (reproducibility). Dựng lại toàn bộ một môi trường ở region khác, hoặc dựng môi trường disaster recovery, trở thành một lệnh apply với biến region khác — không phải một tuần lễ click chuột theo trí nhớ.
  • Documentation tự động cập nhật. Code IaC chính là tài liệu mô tả hạ tầng chính xác nhất tại mọi thời điểm — vì nó chính là thứ tạo ra hạ tầng, không phải một diagram Confluence lạc hậu từ 6 tháng trước.

Mất gì — và đây là phần hay bị bỏ qua:

  • Đường cong học tập thật, không nhỏ. HCL (ngôn ngữ của Terraform) có tư duy khai báo khác hẳn lập trình mệnh lệnh quen thuộc — kỹ sư quen viết imperative code cần thời gian để nghĩ theo kiểu "mô tả kết quả" thay vì "mô tả các bước". Debug một lỗi "resource đã tồn tại nhưng không nằm trong state" hay hiểu vì sao Terraform muốn destroy-and-recreate một resource tưởng chừng chỉ cần update tại chỗ, đòi hỏi kinh nghiệm tích lũy qua va vấp thực tế, không có trong tài liệu chính thức.
  • Đầu tư công cụ và quy trình ban đầu không hề rẻ. Thiết lập remote backend, state locking, module structure hợp lý, IAM permission tối thiểu cho pipeline CI/CD chạy apply, review process cho hạ tầng — tất cả tốn thời gian thực trước khi có ROI. Với một team 3 người và một EC2 instance, khoản đầu tư này có thể chưa hoàn vốn.
  • Bản thân state file trở thành một single point of failure vận hành mới. Đây là cái giá bị đánh giá thấp nhất. State file bị corrupt, bị mất, hoặc bị hai người apply đồng thời gây conflict — hậu quả có thể tệ hơn cả configuration drift mà IaC được kỳ vọng giải quyết: Terraform có thể quyết định "resource này không có trong state, vậy tôi sẽ tạo mới" trong khi resource thật vẫn đang chạy production, dẫn tới duplicate resource, hoặc tệ hơn — quyết định destroy một resource đang phục vụ traffic vì state nói rằng nó "không nên tồn tại".
  • "Plan trông vô hại" là một cạm bẫy tâm lý. Một thay đổi tưởng chừng chỉ sửa một tag lại có thể buộc provider phải destroy-and-recreate toàn bộ resource (ví dụ đổi một thuộc tính immutable của RDS instance) — nếu không đọc kỹ output plan dòng “-/+” (destroy and recreate) thay vì chỉ dòng “~” (update in-place), một apply vô tình có thể xóa production database.
  • Drift vẫn có thể xảy ra — chỉ là loại khác. Nếu ai đó vẫn sửa tay qua console (thường vì "gấp quá, sửa ngay đã"), state sẽ không khớp với thực tế, và lần plan tiếp theo sẽ đề xuất "sửa lại" những gì người kia vừa chỉnh — một dạng xung đột mới giữa con người và công cụ mà đội ngũ phải có kỷ luật vận hành để tránh, chứ IaC không tự động ngăn được.

Nói ngắn gọn: IaC không loại bỏ rủi ro vận hành hạ tầng — nó chuyển dịch loại rủi ro, từ "con người quên mất mình đã sửa gì" sang "hệ thống quản lý state có một điểm hỏng duy nhất". Đây là đánh đổi đáng giá ở quy mô đủ lớn, nhưng phải được quản trị chủ động (remote state, locking, backup), không phải mặc định an toàn.

The Architecture Diagram

flowchart TD
    A[Kỹ sư sửa code hạ tầng<br/>.tf / Pulumi program] --> B[Commit + Push<br/>lên nhánh feature]
    B --> C[Pull Request]
    C --> D[CI Pipeline: terraform plan<br/>chạy tự động, output đính kèm PR]
    D --> E{Review bởi<br/>đồng nghiệp / tech lead}
    E -- Yêu cầu sửa --> A
    E -- Duyệt --> F[Merge vào main]
    F --> G[CI Pipeline: terraform apply<br/>chạy với credential hạn chế]
    G --> H[(Remote State Backend<br/>+ Lock: S3+DynamoDB /<br/>Terraform Cloud / Pulumi Service)]
    H --> I[Cloud Provider<br/>AWS / GCP / Azure]
    G -.-> J[Audit log: ai apply,<br/>khi nào, diff gì]

    style H fill:#4a5568,stroke:#2d3748,color:#fff
    style E fill:#805ad5,stroke:#553c9a,color:#fff
    style G fill:#2b6cb0,stroke:#1a4971,color:#fff

Điểm mấu chốt trong sơ đồ này không phải là mũi tên từ code tới cloud — mà là hai nút thắt bắt buộc: review (E) trước khi merge, và remote state có lock (H) trước khi apply. Bỏ qua một trong hai, IaC quay lại y hệt vấn đề mà nó được sinh ra để giải quyết, chỉ là mặc một bộ áo "tự động hóa" trông đáng tin hơn.

Implementation Guardrails

  1. State file bắt buộc phải remote và có locking, không bao giờ local. State chứa thông tin nhạy cảm (đôi khi cả secret ở dạng plaintext) và là nguồn sự thật duy nhất về ánh xạ resource. Lưu local nghĩa là một laptop hỏng ổ cứng = mất khả năng quản lý toàn bộ hạ tầng. Locking (qua DynamoDB, Terraform Cloud, hay cơ chế tương đương) là bắt buộc để hai kỹ sư không apply đè lên nhau cùng lúc, gây corrupt state.

  2. Không bao giờ sửa state thủ công (terraform state edit trực tiếp chỉ dùng như biện pháp cứu hộ cuối cùng, có kiểm soát). Nếu resource drift khỏi state, dùng import/refresh có kiểm soát qua pipeline, không tay chỉnh JSON của state file. Một dòng sửa sai trong state có thể khiến lần apply kế tiếp xóa nhầm resource đang chạy production.

  3. Luôn plan trước khi apply, và output của plan phải được người khác đọc trước khi duyệt — đặc biệt chú ý các dòng đánh dấu destroy-and-recreate. Không có ngoại lệ "gấp quá nên apply thẳng", kể cả với thay đổi tưởng chừng nhỏ. Đây chính là quy ước đã áp dụng thực tế: mọi thay đổi hạ tầng phải qua terraform plan → review → apply, và với resource production cần một -target plan riêng, review riêng.

  4. Tách quyền: pipeline CI/CD chạy apply với credential giới hạn theo môi trường, con người không có quyền apply trực tiếp vào production từ máy cá nhân. Staging luôn được apply trước production để phát hiện lỗi ở môi trường ít rủi ro hơn. Đặt tên resource và tag theo quy ước cố định (project, environment, managed-by) để không lẫn giữa resource do IaC quản lý và resource tạo tay ngoài luồng.

Architect's Verdict

IaC không còn là lựa chọn tùy nghi một khi hạ tầng của bạn vượt quá "một VPS chạy một ứng dụng monolith cho vài chục người dùng nội bộ" — quá ngưỡng đó, mỗi lần sửa console thủ công là một quả bom hẹn giờ cho configuration drift, chỉ chưa biết khi nào nổ. Nhưng đừng bán nó như một giải pháp miễn phí rủi ro: bạn đang đánh đổi rủi ro "con người quên" lấy rủi ro "state file hỏng" — và rủi ro thứ hai, nếu không quản trị (remote backend, locking, backup state định kỳ), có thể gây thiệt hại nhanh và sâu hơn nhiều so với rủi ro thứ nhất. Đầu tư vào kỷ luật state trước khi đầu tư vào việc viết thêm module.

🔗 Bài viết liên quan


Bài trước: High Availability (HA) · Bài tiếp theo: Observability

Infrastructure as Code (IaC): Quản trị hạ tầng như quản trị source code (Terraform, Pulumi)