DevOps 6 — Infrastructure as Code (IaC)
Infrastructure as Code (IaC) là gì?
Hãy tưởng tượng bạn cần dựng một môi trường production: vài máy chủ ảo, một load balancer, một database, vài security group (tường lửa), một bucket lưu trữ. Cách "cổ điển" là đăng nhập vào console của nhà cung cấp đám mây (AWS, GCP, Azure), bấm chuột qua hàng chục màn hình, gõ tên, chọn vùng, gán quyền... Sau một buổi chiều mệt mỏi, môi trường chạy được. Nhưng ba tháng sau, khi cần dựng lại y hệt cho staging, không ai nhớ chính xác đã bấm những gì.
Infrastructure as Code (IaC) là cách tiếp cận biến toàn bộ hạ tầng đó thành code — những file văn bản mô tả "tôi muốn có gì". Thay vì bấm chuột, bạn viết một file, chạy một lệnh, và công cụ tự dựng toàn bộ. File đó được lưu trong Git, review qua Pull Request, và có thể dùng lại bất cứ lúc nào.
Vì sao IaC quan trọng?
- Lặp lại được (reproducible): Cùng một file code → cùng một hạ tầng. Dựng dev, staging, production từ chung một khuôn mẫu, không lệch nhau.
- Version control: Hạ tầng nằm trong Git như mọi code khác. Bạn thấy được ai đổi gì, khi nào, và quay lui (rollback) khi cần.
- Code review: Mọi thay đổi hạ tầng đi qua Pull Request. Đồng đội nhìn thấy "bạn sắp mở port 22 ra toàn Internet" trước khi nó xảy ra, chứ không phải sau khi bị tấn công.
- Tránh configuration drift: "Drift" là hiện tượng hạ tầng thực tế trôi dạt khỏi thiết kế ban đầu vì ai đó "sửa tay nhanh một chút" trong console. Khi hạ tầng là code, mọi thay đổi tay đều bị phát hiện và có thể đưa về đúng trạng thái mong muốn.
- Tài liệu sống: File IaC chính là tài liệu chính xác nhất về hạ tầng — vì nó là hạ tầng, không phải mô tả về hạ tầng.
Một câu nói kinh điển: "Hãy đối xử với server như gia súc (cattle), không phải thú cưng (pets)." Thú cưng có tên, được chăm sóc riêng, ốm thì lo lắng chữa. Gia súc thì đánh số, hỏng thì thay con khác. IaC giúp bạn coi server là gia súc — dựng lại trong vài phút, không phải vuốt ve sửa chữa thủ công.
Khai báo (Declarative) vs Mệnh lệnh (Imperative)
Đây là khái niệm cốt lõi, phân biệt hai trường phái công cụ IaC.
Mệnh lệnh (imperative) mô tả các bước cần làm, theo thứ tự:
"Tạo một máy chủ. Cài Docker. Mở port 80. Khởi động service."
Bạn ra lệnh từng bước, như viết một kịch bản nấu ăn. Vấn đề: nếu chạy lại lần nữa, kịch bản có thể tạo thêm một máy chủ nữa (vì bạn bảo "tạo"), hoặc lỗi vì Docker đã cài rồi.
Khai báo (declarative) mô tả trạng thái mong muốn cuối cùng, không quan tâm các bước:
"Tôi muốn tồn tại đúng 1 máy chủ, có Docker, port 80 mở."
Công cụ tự so sánh trạng thái hiện tại với trạng thái mong muốn, rồi tính ra phần chênh lệch (diff) và chỉ làm những gì cần. Chạy lại lần nữa mà không có gì đổi → công cụ không làm gì cả. Đây chính là tính idempotency (sẽ nói kỹ ở dưới).
| Khai báo (Declarative) | Mệnh lệnh (Imperative) | |
|---|---|---|
| Mô tả | Trạng thái mong muốn | Các bước thực hiện |
| Ví dụ | Terraform, CloudFormation | Script bash, một phần Ansible |
| Chạy lại | An toàn, chỉ làm phần thiếu | Có thể lặp/lỗi nếu không cẩn thận |
| Tư duy | "Tôi muốn cái gì" | "Tôi làm như thế nào" |
Trên thực tế ranh giới không tuyệt đối: Ansible thiên về mệnh lệnh nhưng các module được thiết kế để idempotent; Terraform thuần khai báo.
Terraform — Provisioning hạ tầng theo kiểu khai báo
Terraform (của HashiCorp) là công cụ IaC phổ biến nhất để provisioning — tức là tạo ra hạ tầng: máy chủ, mạng, database, DNS... Nó dùng ngôn ngữ HCL (HashiCorp Configuration Language), thuần khai báo.
Provider
Provider là plugin giúp Terraform "nói chuyện" với một nền tảng cụ thể — AWS, GCP, Azure, Cloudflare, thậm chí GitHub hay Kubernetes. Mỗi provider cung cấp một bộ các loại resource mà nó quản lý.
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "ap-southeast-1" # Singapore
}
Resource
Resource là đơn vị hạ tầng cơ bản: một máy ảo, một bucket, một security group. Bạn khai báo loại resource, đặt tên cục bộ, và liệt kê thuộc tính mong muốn.
resource "aws_instance" "web" {
ami = "ami-0abcdef1234567890"
instance_type = "t3.micro"
tags = {
Name = "web-server"
Env = "production"
}
}
Terraform tự hiểu phụ thuộc giữa các resource. Nếu một security group được gán vào instance, Terraform biết phải tạo security group trước.
State file — trái tim của Terraform
Đây là khái niệm quan trọng nhất và cũng dễ gây tai nạn nhất.
Terraform cần biết "tôi đã tạo những gì rồi" để lần sau biết phần nào còn thiếu,
phần nào phải sửa, phần nào phải xóa. Nó ghi lại điều này vào một file gọi là
state file (mặc định là terraform.tfstate). State là cầu nối ánh xạ giữa
code của bạn và các tài nguyên thật ngoài đời.
Vì sao state quan trọng:
- Không có state, Terraform không biết resource nào đã thuộc về nó → có thể tạo trùng lặp.
- Khi bạn đổi code và chạy
plan, Terraform so code mới với state để tính diff.
Rủi ro của state:
- Chứa secret: state có thể lưu mật khẩu database, private key ở dạng văn bản thường. Tuyệt đối không commit state vào Git.
- Xung đột khi làm việc nhóm: nếu hai người chạy
applycùng lúc với hai bản state local khác nhau, hạ tầng sẽ loạn. Cần state locking để khóa. - Mất state = thảm họa: mất file state, Terraform "quên" mọi thứ nó đã tạo.
Giải pháp: remote backend (xem phần dưới).
plan → apply → destroy
Ba lệnh chính tạo nên vòng đời Terraform:
terraform plan: xem trước — Terraform so sánh code, state và thực tế, in ra "tôi sẽ tạo X, sửa Y, xóa Z". Không thay đổi gì cả. Đây là bước review an toàn.terraform apply: thực thi kế hoạch — thật sự gọi API để tạo/sửa/xóa hạ tầng, rồi cập nhật state.terraform destroy: dọn dẹp — xóa toàn bộ hạ tầng do code này quản lý.
Quy trình chuẩn: luôn plan trước, đọc kỹ diff, rồi mới apply. Trong CI/CD,
plan thường chạy tự động và đính kèm vào Pull Request để đồng đội duyệt.
Biến (variables) và output
Biến giúp code tái sử dụng được — cùng một module dựng được nhiều môi trường chỉ bằng cách đổi giá trị biến.
variable "instance_type" {
type = string
default = "t3.micro"
description = "Loại máy ảo"
}
variable "instance_count" {
type = number
default = 2
}
Output xuất ra giá trị sau khi tạo xong — ví dụ IP công khai của server, để người khác hoặc module khác dùng.
output "web_public_ip" {
value = aws_instance.web.public_ip
}
Module — đóng gói để tái sử dụng
Module là một nhóm resource được đóng gói lại, có biến đầu vào và output đầu ra, dùng được nhiều lần. Giống như "hàm" trong lập trình. Ví dụ một module "network" tạo VPC + subnet + gateway, bạn gọi nó cho dev và prod chỉ với tham số khác nhau.
module "vpc" {
source = "./modules/network"
cidr_block = "10.0.0.0/16"
env = "production"
}
Remote backend — lưu state an toàn
Remote backend lưu state ở nơi tập trung (S3, GCS, Terraform Cloud) thay vì trên máy cá nhân. Điều này giải quyết cả ba rủi ro: chia sẻ được cho cả nhóm, có locking chống chạy đồng thời, và được mã hóa/sao lưu.
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "prod/terraform.tfstate"
region = "ap-southeast-1"
dynamodb_table = "terraform-locks" # state locking
encrypt = true
}
}
Ansible — Configuration management theo kiểu task
Nếu Terraform giỏi tạo ra máy chủ, thì Ansible giỏi cấu hình bên trong máy chủ: cài package, copy file, khởi động service, tạo user. Đây là configuration management.
Agentless qua SSH
Điểm đặc biệt: Ansible không cần agent. Nó không yêu cầu cài phần mềm đặc biệt nào trên máy đích. Nó chỉ cần SSH (và Python có sẵn). Từ máy điều khiển, Ansible kết nối SSH tới các máy đích, đẩy module lên chạy, rồi dọn đi. Điều này làm Ansible cực kỳ nhẹ và dễ bắt đầu.
Inventory
Inventory là danh sách các máy đích, có thể nhóm lại theo vai trò.
# inventory.yml
all:
children:
webservers:
hosts:
web1.example.com:
web2.example.com:
dbservers:
hosts:
db1.example.com:
Playbook và task
Playbook là file YAML mô tả các task (việc cần làm) áp lên các nhóm host.
Mỗi task gọi một module (như apt, copy, service).
# playbook.yml
- name: Cấu hình web server
hosts: webservers
become: true # chạy với quyền sudo
tasks:
- name: Cài Nginx
apt:
name: nginx
state: present
update_cache: true
- name: Copy file cấu hình
copy:
src: files/nginx.conf
dest: /etc/nginx/nginx.conf
notify: Restart nginx
- name: Đảm bảo Nginx đang chạy
service:
name: nginx
state: started
enabled: true
handlers:
- name: Restart nginx
service:
name: nginx
state: restarted
Idempotency — chạy bao nhiêu lần cũng cho cùng kết quả
Idempotency nghĩa là: chạy playbook lần đầu sẽ tạo thay đổi; chạy lại lần thứ hai, thứ ba... khi không có gì đổi thì Ansible không làm gì và báo "ok" thay vì "changed".
Hãy nhìn task apt: name=nginx state=present. Câu này không nói "hãy cài nginx"
(mệnh lệnh) mà nói "nginx phải tồn tại" (mô tả trạng thái). Lần đầu chưa có →
Ansible cài. Lần sau đã có → Ansible bỏ qua. Đó là lý do bạn chạy playbook lặp
lại an toàn, một tính chất cốt lõi của mọi công cụ IaC tốt.
Ngược lại, nếu bạn dùng module command hay shell để gọi lệnh thô (ví dụ
apt install nginx), nó không idempotent trừ khi bạn tự kiểm tra điều kiện.
Đây là lý do nên ưu tiên module chuyên dụng hơn là gõ lệnh shell.
Provisioning vs Configuration Management
Hai khái niệm hay bị nhầm:
- Provisioning: tạo ra hạ tầng từ con số không — máy ảo, mạng, ổ đĩa, database. Terraform là vua ở đây.
- Configuration management: cấu hình hạ tầng đã có — cài phần mềm, chỉnh file, quản lý service. Ansible (và Chef, Puppet, SaltStack) mạnh ở đây.
Trong thực tế, nhiều nhóm dùng cả hai: Terraform dựng máy, rồi Ansible cấu hình bên trong. Nhưng xu hướng hiện đại đang nghiêng về cách khác — immutable infrastructure.
| Tiêu chí | Terraform | Ansible |
|---|---|---|
| Mục đích chính | Provisioning (tạo hạ tầng) | Configuration management (cấu hình) |
| Ngôn ngữ | HCL | YAML |
| Mô hình | Khai báo (declarative) | Thiên mệnh lệnh, module idempotent |
| State | Có state file (cần quản lý) | Không có state cố định |
| Agent | Không (gọi API) | Không (qua SSH) |
| Theo dõi drift | Mạnh (so state với thực tế) | Yếu hơn |
| Phù hợp nhất | Cloud resource, mạng, DNS | Cài đặt phần mềm trên server |
Immutable Infrastructure
Cách làm truyền thống là mutable (có thể thay đổi): bạn nuôi một server, rồi thỉnh thoảng SSH vào cập nhật, vá lỗi, đổi cấu hình. Theo thời gian, mỗi server trở thành một "bông tuyết" độc nhất, không server nào giống nhau hoàn toàn — và drift tích lũy.
Immutable infrastructure (hạ tầng bất biến) đảo ngược triết lý: không bao giờ sửa server đang chạy. Muốn thay đổi gì → đóng gói một image máy mới (ví dụ bằng Packer hoặc Docker image), dựng server mới từ image đó, chuyển traffic sang, rồi xóa server cũ.
Lợi ích:
- Không còn drift: server mới luôn dựng từ image đã biết rõ.
- Rollback dễ: lỗi thì quay về image cũ.
- Nhất quán tuyệt đối: mọi server từ cùng image là giống hệt nhau.
Đây cũng là lý do container và Kubernetes hợp với immutable infrastructure: bạn build image một lần, deploy ở mọi nơi, và không bao giờ "sửa tay" container đang chạy.
Best Practices
- Không bao giờ commit secret vào code hay state. Dùng secret manager (AWS
Secrets Manager, HashiCorp Vault), biến môi trường, hoặc
.tfvarsđược gitignore. Thêmterraform.tfstate*và*.tfvarsvào.gitignore. - State an toàn: luôn dùng remote backend có mã hóa và locking cho team. Đừng để state trên máy cá nhân khi làm việc nhóm.
- Luôn
plantrướcapply. Đọc kỹ diff. Trong CI, đínhplanvào PR để review. - Chia nhỏ thành module để tái sử dụng và giảm "blast radius" (phạm vi ảnh hưởng khi sai).
- Tách môi trường: dev/staging/prod dùng state riêng (workspace hoặc thư mục
riêng), tránh
applynhầm vào production. - Ưu tiên idempotent: trong Ansible, dùng module chuyên dụng thay vì
command/shellthô. - Pin version của provider và module để build lặp lại được.
- Review mọi thay đổi hạ tầng qua Pull Request, đúng như review code thường.
Tóm tắt
Infrastructure as Code biến hạ tầng thành code có thể version, review và tái sử
dụng — chấm dứt việc bấm chuột thủ công và drift. Có hai trường phái: khai báo
(mô tả trạng thái mong muốn, như Terraform) và mệnh lệnh (mô tả các bước, một
phần của Ansible). Terraform lo provisioning hạ tầng bằng HCL, với vòng đời
plan → apply → destroy; state file là cầu nối giữa code và thực tế, cực kỳ
quan trọng nhưng chứa secret nên cần remote backend an toàn. Ansible lo
configuration management bằng YAML playbook, chạy agentless qua SSH, và dựa trên
idempotency để chạy lại an toàn. Module giúp tái sử dụng, biến và output giúp
tham số hóa. Xu hướng immutable infrastructure loại bỏ drift bằng cách dựng
mới thay vì sửa tại chỗ. Quy tắc vàng: không commit secret, giữ state an toàn,
luôn xem diff trước khi thay đổi.
Tự kiểm tra
- Phân biệt cách tiếp cận khai báo và mệnh lệnh. Terraform thuộc loại nào và vì sao điều đó giúp việc chạy lại an toàn?
- State file trong Terraform dùng để làm gì, và vì sao tuyệt đối không được commit nó vào Git?
- Khác biệt giữa
terraform planvàterraform applylà gì? Tại sao nên luôn chạyplantrước? - Idempotency là gì? Vì sao một task Ansible dùng
apt: state=presentlại idempotent còncommand: apt installthì không? - Phân biệt provisioning và configuration management. Terraform và Ansible mạnh ở khâu nào?
- Immutable infrastructure giải quyết vấn đề configuration drift như thế nào?
Đọc tiếp
Bài viết liên quan
Ba trụ cột logging–metrics–tracing, Prometheus/Grafana, alerting, SLI/SLO/SLA và golden signals.
Continuous Integration/Delivery/Deployment, cấu trúc pipeline, ví dụ GitHub Actions và chiến lược release.
Container vs máy ảo, image/layer, Dockerfile, volume, network, Docker Compose và best practices.
API là bề mặt tấn công chính của hệ thống dữ liệu hiện đại. Bài đi qua toàn bộ OWASP API Security Top 10 (2023) từ BOLA/IDOR tới quản lý inventory, biện pháp phòng thủ cốt lõi (authz mọi tầng, rate limit, schema, API gateway), khác biệt REST/GraphQL/gRPC, và bối cảnh Open Banking ngân hàng.