Capstone: Hạ Tầng Web Đa Tầng Hoàn Chỉnh
You've reached the end
Mười chín bài qua đã dựng từng mảnh: provider, state, biến, module, đa môi trường, lifecycle, CI/CD, kiểm thử. Bài cuối ráp chúng thành một thứ hoàn chỉnh — một hạ tầng web đa tầng mà gần như dự án thật nào cũng có hình hài tương tự: cân bằng tải đứng trước một nhóm máy chủ co giãn, một database phía sau, và một kho lưu trữ object. Ta tổ chức nó thành ba module nối với nhau, apply thật, truy cập ứng dụng, rồi dọn sạch.
Mục tiêu
Dựng một hạ tầng web hoàn chỉnh bằng cách kết hợp module, thấy mọi kỹ thuật của series cùng làm việc, kiểm chứng nó chạy thật, và teardown gọn.
💰 Chi phí
Stack này có ALB và RDS — hai thứ tính tiền theo giờ (ALB khoảng 0,025 USD/giờ, RDS db.t3.micro khoảng 0,017 USD/giờ), cộng EC2 t3.micro. Dựng lên rồi destroy trong vòng mười lăm phút thì tổng chi phí chỉ vài cent. Điểm mấu chốt: destroy ngay sau khi xem xong, đừng để qua đêm.
Kiến trúc
Internet
│ HTTP :80
┌─────────▼──────────┐
│ Application LB │ public subnet a + b
│ (SG: nhận từ Net) │
└─────────┬──────────┘
│ target group
┌─────────▼──────────┐
│ Auto Scaling Group │ nginx x2 (t3.micro)
│ (SG: CHỈ nhận từ ALB)
└─────────┬──────────┘
│ :5432
┌─────────────▼───┐ ┌───────────────┐
│ RDS PostgreSQL │ │ S3 (assets) │
│ (SG: chỉ từ app)│ │ chặn public │
└─────────────────┘ └───────────────┘
─── tất cả trong VPC 10.0.0.0/16 (module network) ───
Lưu lượng đi vào ALB, ALB phân tới các instance trong ASG, instance nói chuyện với RDS. Mỗi tầng có security group riêng, và chúng tham chiếu nhau để thắt chặt: instance chỉ nhận HTTP từ ALB (không mở thẳng ra Internet), RDS chỉ nhận 5432 từ security group của instance. Đây là mô hình phân lớp bảo mật cơ bản, dựng hoàn toàn bằng tham chiếu giữa các SG.
Ba module
Cấu trúc gồm ba module, mỗi cái gói một tầng:
20-capstone/
modules/
network/ # VPC, subnet, IGW, route table (tái dùng từ bài 14)
web/ # ALB, target group, launch template, ASG, 2 SG
data/ # RDS PostgreSQL, S3, DB subnet group, SG
main.tf # root: nối ba module
Module network dùng lại nguyên từ bài 14, đúng lợi ích của module: viết một lần dùng nhiều nơi. Module web gói ALB và ASG: launch template cài nginx qua user_data (cách thay provisioner ở bài 17), ASG gắn vào target group của ALB. Module data gói RDS và S3, trong đó mật khẩu DB dùng password_wo — write-only argument của bài 8 để mật khẩu không lọt vào state.
Root nối ba module bằng đúng kỹ thuật composition ở bài 13 — output của module này thành input của module kia:
module "network" {
source = "./modules/network"
name = var.name
public_subnets = {
(data.aws_availability_zones.available.names[0]) = cidrsubnet("10.0.0.0/16", 8, 0)
(data.aws_availability_zones.available.names[1]) = cidrsubnet("10.0.0.0/16", 8, 1)
}
}
module "web" {
source = "./modules/web"
vpc_id = module.network.vpc_id # network -> web
subnet_ids = module.network.public_subnet_ids
ami_id = data.aws_ami.al2023.id
}
module "data" {
source = "./modules/data"
vpc_id = module.network.vpc_id
subnet_ids = module.network.public_subnet_ids
app_security_group_id = module.web.instance_security_group_id # web -> data
db_password_wo = var.db_password
}
Để ý chuỗi phụ thuộc: network cung cấp VPC và subnet cho cả web lẫn data; web cung cấp security group của instance cho data để RDS biết cho phép ai kết nối. Terraform đọc các tham chiếu này, dựng đồ thị (bài 5), và thực hiện đúng thứ tự. Gần như mọi khái niệm của series hội tụ ở đây: for_each và cidrsubnet trong network, data source lấy AMI và AZ, composition giữa các module, password_wo cho secret, SG tham chiếu nhau.
Apply
$ terraform plan
Plan: 19 to add, 0 to change, 0 to destroy.
$ terraform apply -auto-approve
module.data.aws_db_instance.this: Still creating... [04m30s elapsed]
Apply complete! Resources: 19 added, 0 changed, 0 destroyed.
Mười chín resource từ ba module, trong một lần apply. RDS là thứ lâu nhất (khoảng bốn-năm phút), Terraform dựng song song những nhánh độc lập trong lúc chờ. Output cho ta điểm vào:
$ terraform output
alb_dns_name = "web-2026...elb.amazonaws.com"
db_endpoint = "<db>.ap-southeast-1.rds.amazonaws.com:5432"
assets_bucket = "tf-capstone-assets-2026..."
Kiểm chứng end-to-end
Hạ tầng dựng xong chưa đủ — phải chắc nó chạy. Truy cập ALB:
$ curl http://web-2026...elb.amazonaws.com/
<h1>tf-capstone - ip-10-0-0-129.ap-southeast-1.compute.internal</h1>
Trang nginx trả về, kèm hostname nội bộ của instance phục vụ request. Toàn bộ chuỗi hoạt động: ALB nhận HTTP, chuyển tới một instance trong ASG, instance chạy nginx (cài qua user_data lúc boot) và trả nội dung. Lưu lượng đi đúng qua các security group đã thắt.
Kiểm thêm rằng mật khẩu DB không lọt vào state, đúng như password_wo hứa ở bài 8:
$ grep -c 'Capstone-Demo-9182' terraform.tfstate
0
Không lần nào — mật khẩu tới được RDS lúc apply nhưng không được lưu trong state.
🧹 Teardown
$ terraform destroy -auto-approve
Destroy complete! Resources: 19 destroyed.
Terraform dỡ theo thứ tự ngược đồ thị: ASG và RDS trước, rồi subnet và security group, cuối cùng VPC. Một lệnh xóa sạch cả mười chín resource — đây là điểm mạnh của hạ tầng khai báo: dựng và phá đều bằng chính cấu hình, không sót tài nguyên trả tiền âm thầm. Kiểm lại trên console để chắc không còn gì sót (ALB và RDS là thứ dễ quên nhất).
Tổng kết series
Bạn đã đi từ "Terraform là gì" tới một hạ tầng web đa tầng dựng bằng module qua một quy trình lặp lại được. Những thứ đáng mang theo: hạ tầng là code khai báo, plan trước apply; state là nguồn sự thật phải giữ an toàn (S3 + use_lockfile, không lộ secret nhờ password_wo); module là đơn vị tái dùng, nối với nhau qua output/input; nhiều môi trường tách bằng bố cục thư mục; và mọi thay đổi nên đi qua CI/CD với cổng chất lượng (fmt/validate/tflint/Trivy/Checkov) cùng kiểm thử (terraform test). Xuyên suốt, ta luôn pin phiên bản và luôn dọn sạch sau khi thử.
Đi tiếp từ đâu
Series dừng ở nền vững, còn nhiều hướng để đào sâu. Về tổ chức quy mô lớn: HCP Terraform và Stacks cho điều phối nhiều state, hoặc Terragrunt nếu theo hướng mã nguồn mở. Về chính sách: policy-as-code với Sentinel hoặc OPA để chặn theo quy tắc công ty ngay trong pipeline. Về phạm vi: viết module riêng cho tổ chức và xuất bản lên registry nội bộ, dùng provider ngoài AWS (Cloudflare, GitHub, Kubernetes) trong cùng một cấu hình. Mỗi hướng đều xây trên đúng nền tảng mười chín bài này đã đặt — và giờ bạn có đủ gốc để tự đọc tài liệu mà đi tiếp.
You've reached the end