Terraform
← Tổng quan series

Deploy website tĩnh lên S3 bằng Terraform

Tự động tạo bucket, upload cả thư mục static bằng fileset và kiểm soát cache cho từng loại file.

Instructor walkthrough

Bài giảng: hiểu luồng trước khi chạy lệnh

Bài toán: Tự động tạo bucket, upload cả thư mục static bằng fileset và kiểm soát cache cho từng loại file.

01Chuẩn bịkiểm tra account, region, provider và chi phí
02Đọc planđối chiếu resource address, diff và dependency
03Thực hànhapply mutation nhỏ trong sandbox, lưu evidence
04Khôi phụcre-run, failure drill, destroy hoặc rollback
Command reference · baseline
terraform fmt
terraform init
terraform validate
terraform plan
terraform apply
terraform plan -destroy
terraform destroy

Khi nào S3 là lựa chọn phù hợp?

Landing page, tài liệu tĩnh hoặc SPA build sẵn có thể lưu trên S3. Trong production, thường đặt CloudFront phía trước và dùng Origin Access Control thay vì mở bucket công khai. Ví dụ dưới đây ưu tiên hiểu cách Terraform upload file; hãy khóa quyền public theo yêu cầu bảo mật của hệ thống.

Giả sử thư mục có dạng:

site/
├── index.html
└── assets/app.js

Tạo bucket và upload file

terraform {
  required_providers {
    aws = { source = "hashicorp/aws", version = "~> 6.0" }
  }
}

provider "aws" { region = "ap-southeast-1" }

resource "aws_s3_bucket" "site" {
  bucket = "demo-static-site-12345"
}

locals {
  site_root = "${path.module}/site"
  files     = fileset(local.site_root, "**/*")
  mime_types = {
    html = "text/html"
    css  = "text/css"
    js   = "application/javascript"
    svg  = "image/svg+xml"
  }
}

resource "aws_s3_object" "site_files" {
  for_each = { for file in local.files : file => file }

  bucket       = aws_s3_bucket.site.id
  key          = each.key
  source       = "${local.site_root}/${each.key}"
  content_type = lookup(local.mime_types, split(".", each.key)[length(split(".", each.key)) - 1], "application/octet-stream")
  etag         = filemd5("${local.site_root}/${each.key}")
}

fileset trả về các path tương đối; for_each biến mỗi file thành một object riêng. filemd5 khiến Terraform nhận ra file đã đổi và upload lại. Không dùng file() cho binary: file() dành cho nội dung text, còn upload object dùng source.

terraform init
terraform fmt
terraform plan
terraform apply

Cache và deploy lặp lại

File HTML thường cache ngắn, còn asset có hash tên file có thể cache lâu hơn:

cache_control = endswith(each.key, ".html") ? "no-cache" : "public,max-age=31536000,immutable"

Khi đổi website, nên build vào thư mục mới rồi chạy plan/apply. Khi xóa bucket, S3 bucket phải rỗng; trong lab có thể dùng force_destroy = true, nhưng production không nên bật tùy tiện vì nó cho phép Terraform xóa toàn bộ object.

Kiểm tra một lần deploy

Sau apply, kiểm tra object và metadata thay vì chỉ nhìn số resource:

aws s3 ls s3://demo-static-site-12345/ --recursive
aws s3api head-object \
  --bucket demo-static-site-12345 \
  --key index.html \
  --query '{Type:ContentType,Cache:CacheControl,ETag:ETag}'

Nếu thay nội dung mà plan không upload lại, kiểm tra source có trỏ đúng path và etag = filemd5(...) có nằm trong resource. Nếu dùng pipeline, hãy build website trước, lưu artifact và để Terraform upload đúng thư mục artifact đó; không build khác nhau giữa bước plan và apply.

Với SPA, còn phải xử lý fallback 403/404 về index.html, HTTPS, OAC và invalidation CloudFront. Upload được file lên S3 mới chỉ là nửa đầu của một website production.

Expected state, security gate và deploy drill

Dùng bucket tên có hậu tố lab, tag Training = terraform-04, thư mục site/ cố định và một AWS account/region sandbox. Expected state là mỗi file trong fileset có một aws_s3_object, content type/cache đúng extension, HTML cache ngắn, asset hash cache dài, và plan lần hai no-op khi artifact không đổi. Bucket lab phải block public access mặc định; nếu bài minh họa website endpoint cần public thì phải tách branch/lab, ghi rõ rủi ro và không dùng bucket production.

Thực hiện các drill:

  1. Thêm/sửa/xóa một file trong site/, chạy plan và xác nhận chỉ object tương ứng create/update/delete; kiểm tra head-object content type, cache-control và ETag.
  2. Đổi tên một asset, xem Terraform có xóa key cũ và tạo key mới; kiểm tra reference trong HTML để tránh deploy artifact không đồng bộ.
  3. Đổi site_root hoặc chạy plan ở thư mục sai, đọc fileset/source error rồi khôi phục; không trỏ source vào thư mục workspace chứa secret hoặc .env.
  4. Chạy terraform plan -destroy và kiểm tra bucket/object scope; không bật force_destroy trên production. Với bucket versioning/OAC/CloudFront, ghi migration/retention/invalidation plan riêng.

Quality gate

Pass khi người học chứng minh được artifact deterministic (build một lần, plan/apply cùng output), content/cache metadata đúng, public access không mở ngoài chủ ý, drift/object deletion được review, website smoke test/CloudFront path có evidence và cleanup không để bucket/object/request phát sinh chi phí. Terraform upload file không tự giải quyết HTTPS, SPA fallback, OAC hay cache invalidation.