Sau bài này bạn làm được: viết Terraform module cơ bản cho Security Group; review PR thay đổi mạng và biết câu hỏi nào cần hỏi; hiểu tại sao terraform plan phải chạy trong CI trước khi merge.


Click-ops và hậu quả

“Click-ops” là cấu hình hạ tầng bằng console cloud (tay chuột):

Problem:
  Dev A click thêm SG rule "mở 0.0.0.0/0:5432" vì debug tối qua
  → quên không đóng lại
  → tồn tại 3 tháng
  → không ai biết rule đó từ đâu ra
  → không audit trail

IaC (Infrastructure as Code) giải quyết: mọi thay đổi là code, có review, có version history, có thể rollback.


Terraform vs OpenTofu (2025): sau khi HashiCorp đổi license Terraform sang BSL (8/2023), cộng đồng fork thành OpenTofu (CNCF sandbox, Linux Foundation). Cú pháp HCL tương thích gần như 100%, state file cùng format — nhiều tổ chức đã migrate bằng cách đổi binary terraformtofu. Ví dụ trong bài dùng terraform làm lệnh nhưng hoàn toàn áp dụng được với tofu plan / tofu apply.


Terraform cơ bản cho mạng

VPC và subnet

resource "aws_vpc" "main" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_support   = true
  enable_dns_hostnames = true

  tags = {
    Name        = "prod-vpc"
    Environment = "production"
  }
}

resource "aws_subnet" "public" {
  for_each          = { "a" = "10.0.1.0/24", "b" = "10.0.101.0/24" }
  vpc_id            = aws_vpc.main.id
  cidr_block        = each.value
  availability_zone = "us-east-1${each.key}"

  tags = { Name = "public-${each.key}" }
}

resource "aws_subnet" "private" {
  for_each          = { "a" = "10.0.2.0/24", "b" = "10.0.102.0/24" }
  vpc_id            = aws_vpc.main.id
  cidr_block        = each.value
  availability_zone = "us-east-1${each.key}"

  tags = { Name = "private-${each.key}" }
}

Security Group

resource "aws_security_group" "app" {
  name        = "app-server"
  description = "App server SG"
  vpc_id      = aws_vpc.main.id

  ingress {
    description = "HTTPS from internet"
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    description     = "SSH from bastion only"
    from_port       = 22
    to_port         = 22
    protocol        = "tcp"
    security_groups = [aws_security_group.bastion.id]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = { Name = "app-server" }
}

terraform plan — đọc output trước khi apply

terraform plan -out=tfplan

# Output mẫu:
# + aws_security_group_rule.allow_5432  ← sẽ THÊM rule mới
# ~ aws_lb_listener.https               ← sẽ THAY ĐỔI (in-place)
# - aws_subnet.old_private              ← sẽ XÓA
# -/+ aws_instance.app                 ← sẽ DESTROY và TẠO LẠI (!)

Dấu hiệu cần cẩn thận trong plan:

  • -/+ destroy và recreate → downtime nếu không có replica.
  • - delete subnet/SG → có thể ảnh hưởng resource khác tham chiếu.
  • ~ thay đổi SG rule → có thể cắt traffic đang chạy.

GitOps workflow cho thay đổi mạng

[Developer] ──PR: "Thêm rule mở cổng 8443 cho service X"──▶ [GitHub/GitLab]
                                                        [CI Pipeline chạy:]
                                                        1. terraform fmt (format check)
                                                        2. terraform validate
                                                        3. terraform plan → post output lên PR
                                                        [Reviewer đọc plan output]
                                                        [Approve hoặc request changes]
                                                        [Merge → CI apply]

Checklist review PR thay đổi mạng:

□ Rule mở cổng nào? Từ source nào? Đã minimize privilege chưa?
□ Có rule nào bị XÓA không? Ai đang dùng rule đó?
□ Có subnet/SG nào bị thay đổi gây destroy resource không?
□ Thay đổi có ảnh hưởng production ngay lập tức không?
□ Nếu rollback, làm thế nào?
□ Có thay đổi nào tương tự ở staging/test chưa?

Quản lý Kubernetes manifest bằng GitOps

Kubernetes resource (Ingress, NetworkPolicy, Service) cũng là YAML — áp dụng GitOps:

[Git repo]
  └── k8s/
       ├── network-policy.yaml
       ├── ingress.yaml
       └── service.yaml

[ArgoCD / Flux]
  └── watch repo → sync vào cluster khi có thay đổi

Review flow:
  PR thay đổi NetworkPolicy → CI validate YAML
                             → dry-run apply (--dry-run=server)
                             → reviewer approve
                             → merge → ArgoCD sync tự động

Kiểm tra manifest trước khi apply:

# Dry run trên cluster (validate với API server)
kubectl apply --dry-run=server -f network-policy.yaml

# Diff so với trạng thái hiện tại
kubectl diff -f network-policy.yaml

Giữ IaC và reality đồng bộ

Nguy cơ lớn nhất của IaC: drift — ai đó vào console sửa tay, IaC không biết.

Phát hiện drift:

# Terraform tự detect drift khi plan
terraform plan
# "Note: Objects have changed outside of Terraform"

Prevent drift:

  • Khóa console access bằng IAM policy (chỉ cho read, không cho write resource mạng).
  • Chạy terraform plan theo schedule (ví dụ mỗi ngày) và alert nếu có diff.
  • Dùng Sentinel / OPA để enforce policy “resource phải có tag Environment”.

Tóm tắt

  • IaC = audit trail + reproducibility + review — click-ops không có điều này.
  • terraform plan output phải được review trước khi apply vào production.
  • GitOps: PR → CI plan → review → merge → auto-apply.
  • Drift là rủi ro thường trực — chạy plan thường xuyên và alert khi có drift.

Câu hỏi hay gặp

plan hiện -/+ EC2 khi đổi ami — nguy hiểm gì nếu không có replica?

Trả lời: -/+destroy + createdowntime, đổi IP private (nếu không giữ), mất ephemeral disk. Cần blue/green, ASG rolling, hoặc replace one-by-one.

Apply tay trên máy + sửa console — drift nhiều resource: vì sao, hệ quả?

Trả lời: State Terraform không khớp thực tế; ai đó đã đổi ngoài code. Hệ quả: plan/apply có thể xóa nhầm hoặc không ai tin được infra.

Bắt buộc tag Owner trên mọi SG — dùng gì trong Terraform ecosystem?

Trả lời: Default tags (provider), validation trong module, hoặc policy-as-code (Sentinel, OPA/Terraform Cloud) để chặn apply nếu thiếu tag.


Bài tiếp theo (Giai đoạn IV): Release và incident mạng — thực hành deploy không gây 502, và checklist khi incident xảy ra.