Nhiều Môi Trường: Workspace và Bố Cục Thư Mục

K
Kai··6 min read

Suốt series, mọi cấu hình chạy trong một state duy nhất cho một môi trường. Thực tế gần như luôn cần nhiều hơn: dev để thử, staging để kiểm, prod để chạy thật, mỗi cái state riêng để không đụng nhau. Part V xử lý chuyện đó. Bài này so hai cách tổ chức nhiều môi trường — workspace và bố cục thư mục — và quan trọng là biết khi nào dùng cái nào, vì chọn sai có thể khiến một apply nhắm vào dev lại xóa nhầm prod.

Mục tiêu

Hiểu và dùng được workspace, hiểu bố cục thư mục, và biết rõ giới hạn của workspace để không dùng nó cho việc nó không hợp.

Vấn đề: một state không đủ

Nếu dev và prod dùng chung một state, chúng dùng chung một danh sách resource — không thể có hai bucket "app" tách biệt. Mỗi môi trường cần state riêng. Hai cách giải khác nhau ở chỗ state riêng đó nằm đâu.

Cách 1: workspace

Workspace cho phép nhiều state có tên trong cùng một cấu hình và backend. Mỗi workspace giữ một state riêng; khi bạn ở workspace dev, Terraform chỉ thấy resource của dev, resource của prod vẫn còn nguyên nhưng vô hình cho tới khi bạn chuyển sang.

Cấu hình tham chiếu workspace hiện tại qua terraform.workspace để đổi hành vi:

locals {
  instance_type = terraform.workspace == "prod" ? "t3.small" : "t3.micro"
}

resource "aws_s3_bucket" "app" {
  bucket_prefix = "tf-series-bai15-${terraform.workspace}-"
  force_destroy = true
  tags = {
    Workspace = terraform.workspace
    Size      = local.instance_type
  }
}

Các lệnh quản lý workspace:

$ terraform workspace list
* default

$ terraform workspace new dev
$ terraform workspace new prod
$ terraform workspace list
  default
  dev
* prod

Apply trong từng workspace cho kết quả khác nhau theo terraform.workspace:

$ terraform workspace select dev && terraform apply -auto-approve
$ terraform output
bucket        = "tf-series-bai15-dev-2026..."
instance_type = "t3.micro"
workspace     = "dev"

$ terraform workspace select prod && terraform apply -auto-approve
$ terraform output
bucket        = "tf-series-bai15-prod-2026..."
instance_type = "t3.small"
workspace     = "prod"

Hai bucket riêng, prod dùng t3.small còn dev t3.micro, từ cùng một file. Với backend local, state mỗi workspace nằm ở thư mục riêng:

$ find terraform.tfstate.d -name '*.tfstate'
terraform.tfstate.d/dev/terraform.tfstate
terraform.tfstate.d/prod/terraform.tfstate

(Với backend S3, mỗi workspace thành một key riêng trong cùng bucket state.)

Giới hạn quan trọng của workspace

Workspace tiện, nhưng có một cảnh báo trong tài liệu mà nhiều người bỏ qua, dẫn tới sự cố nghiêm trọng. Tài liệu nói thẳng: workspace "không phù hợp để phân rã hệ thống hay cho các triển khai cần credential và kiểm soát truy cập riêng".

Lý do nằm ở chữ cùng backend. Mọi workspace chia sẻ một backend, một bộ credential, một cấu hình. Hệ quả thực tế:

Thứ nhất, không phân tách quyền được. Ai apply được dev thì cũng apply được prod, vì chúng dùng chung credential và chung backend — không thể cho một người chỉ quyền dev.

Thứ hai, dễ thao tác nhầm. Chuyển workspace chỉ là một lệnh select, không có gì ngăn bạn quên mình đang ở prod rồi destroy. Workspace hiện tại là trạng thái ẩn trong terminal, không hiện trong file.

Thứ ba, prod nên ở tài khoản AWS riêng để cách ly thật sự (blast radius), mà workspace thì buộc chung một provider/credential.

Workspace hợp cho biến thể nhẹ của cùng một thứ: nhiều bản review tạm thời, nhiều region của cùng một stack. Không hợp cho ranh giới prod–dev cần cách ly mạnh.

Cách 2: bố cục thư mục

Cách được khuyến nghị cho phân tách mạnh là tách mỗi môi trường ra một thư mục riêng, mỗi thư mục có backend riêng (key riêng, thậm chí bucket/tài khoản riêng), và cùng gọi một module dùng chung:

layout-demo/
  modules/app/              # logic dùng chung, viết một lần
  environments/
    dev/  main.tf           # backend key = dev/app.tfstate
    prod/ main.tf           # backend key = prod/app.tfstate

Mỗi thư mục môi trường mỏng, chỉ khai backend riêng và gọi module với tham số của môi trường đó:

# environments/prod/main.tf
terraform {
  # backend "s3" { bucket = "...", key = "prod/app.tfstate", region = "ap-southeast-1" }
}
provider "aws" { region = "ap-southeast-1" }

module "app" {
  source        = "../../modules/app"
  environment   = "prod"
  instance_type = "t3.small"
}
   workspace                          bố cục thư mục
   ─────────                          ──────────────
   1 thư mục, 1 backend               environments/dev/  -> backend key dev
     ├─ state "dev"                   environments/prod/ -> backend key prod
     ├─ state "prod"                  (credential/account có thể riêng)
     └─ chung credential                       │
        chọn bằng `workspace select`           └─ cùng gọi modules/app

Bạn cd environments/prod rồi mới chạy lệnh, nên môi trường hiện tại hiện rõ trong đường dẫn, không ẩn như workspace. Backend riêng cho phép prod dùng bucket state và credential riêng. CI có thể giới hạn ai chạy được thư mục nào. Đổi lại, có lặp một chút khung terraform{}/provider giữa các thư mục — giá phải trả cho sự rõ ràng và an toàn.

Chọn cái nào

Quy tắc gọn: dùng workspace cho biến thể nhẹ, tạm, không cần phân quyền (review apps, nhiều region của cùng stack). Dùng bố cục thư mục cho ranh giới môi trường thật — dev/staging/prod — nơi bạn muốn state riêng, credential riêng, và giảm rủi ro thao tác nhầm. Với hầu hết dự án có prod nghiêm túc, bố cục thư mục là lựa chọn mặc định nên chọn.

🧹 Dọn dẹp

$ terraform workspace select prod && terraform destroy -auto-approve
$ terraform workspace select dev  && terraform destroy -auto-approve
$ terraform workspace select default
$ terraform workspace delete dev && terraform workspace delete prod

Tổng kết

Workspace giữ nhiều state trong cùng một backend, chọn qua terraform workspace select, đổi hành vi qua terraform.workspace — gọn cho biến thể nhẹ, nhưng không hợp cho phân tách mạnh vì chung backend và credential, và môi trường hiện tại bị ẩn dễ gây nhầm. Bố cục thư mục tách mỗi môi trường ra một thư mục với backend riêng, gọi chung module; rõ ràng hơn, phân quyền được, prod cách ly thật, đổi lại lặp ít khung cấu hình. Cho dev/staging/prod nghiêm túc, chọn bố cục thư mục.

Tách thư mục đặt ra một câu hỏi mới: làm sao thư mục này đọc được output của thư mục kia (ví dụ compute cần id VPC mà network tạo ở state khác)? Bài 16 trả lời bằng terraform_remote_state, và đồng thời xử lý hai tình huống refactor thường gặp khi tổ chức lại code: đổi tên/di chuyển resource bằng moved block, và gỡ resource khỏi state bằng removed block — thay cho lệnh tay ở bài 7.