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

[1] Developer tạo PR: "Thêm rule mở cổng 8443 cho service X"
│
▼
[2] CI Pipeline chạy 1. terraform fmt (format check) 2. terraform validate 3. terraform plan → post output lên PR
│
▼
[3] Reviewer đọc plan output
│
▼
[4] Merge → CI apply
│
▼
[5] Hạ tầng khớp trạng thái đã merge (theo dõi drift; quay lại bước 1 nếu cần chỉnh)

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 + create, downtime, đổ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.