DevOps 6 — Infrastructure as Code (IaC)

13 thg 7, 2026 3 lượt xem
#devops
#iac
#terraform
#ansible

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ó 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ốnCác bước thực hiện
Ví dụTerraform, CloudFormationScript bash, một phần Ansible
Chạy lạiAn toàn, chỉ làm phần thiếuCó 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ạncá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 apply cù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íTerraformAnsible
Mục đích chínhProvisioning (tạo hạ tầng)Configuration management (cấu hình)
Ngôn ngữHCLYAML
Mô hìnhKhai báo (declarative)Thiên mệnh lệnh, module idempotent
StateCó state file (cần quản lý)Không có state cố định
AgentKhông (gọi API)Không (qua SSH)
Theo dõi driftMạnh (so state với thực tế)Yếu hơn
Phù hợp nhấtCloud resource, mạng, DNSCà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êm terraform.tfstate**.tfvars và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 plan trước apply. Đọc kỹ diff. Trong CI, đính plan và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 apply nhầm vào production.
  • Ưu tiên idempotent: trong Ansible, dùng module chuyên dụng thay vì command/shell thô.
  • 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

  1. 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?
  2. State file trong Terraform dùng để làm gì, và vì sao tuyệt đối không được commit nó vào Git?
  3. Khác biệt giữa terraform planterraform apply là gì? Tại sao nên luôn chạy plan trước?
  4. Idempotency là gì? Vì sao một task Ansible dùng apt: state=present lại idempotent còn command: apt install thì không?
  5. Phân biệt provisioning và configuration management. Terraform và Ansible mạnh ở khâu nào?
  6. Immutable infrastructure giải quyết vấn đề configuration drift như thế nào?

Đọc tiếp

DevOps 7 — Observability

Bài viết liên quan

Ba trụ cột logging–metrics–tracing, Prometheus/Grafana, alerting, SLI/SLO/SLA và golden signals.

13 thg 7, 2026 3

Continuous Integration/Delivery/Deployment, cấu trúc pipeline, ví dụ GitHub Actions và chiến lược release.

13 thg 7, 2026 3

Container vs máy ảo, image/layer, Dockerfile, volume, network, Docker Compose và best practices.

13 thg 7, 2026 3

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.

13 thg 7, 2026 3