The repo layout
สิ่งที่จะสร้าง
หัวข้อที่มีชื่อว่า “สิ่งที่จะสร้าง”โครงของทั้งโปรเจกต์: tree modules/ ของ Terraform แบบ reusable ที่เจาะจงต่อ cloud, tree live/ ที่มีหนึ่ง directory ต่อ cloud คอยขับ module พวกนั้นผ่าน Terragrunt, บวก charts/ สำหรับ Helm และ .github/workflows/ สำหรับ CI ยังไม่มี resource — แค่รูปทรงที่ module บทถัด ๆ ไปจะเติมเข้ามา
layout ไม่ใช่แค่เรื่องหน้าตา แต่เข้ารหัสการตัดสินใจที่สำคัญที่สุดของโปรเจกต์: module อธิบายว่า infrastructure ชิ้นหนึ่ง คืออะไร; tree live/ อธิบายว่า module นั้นรัน ที่ไหน และ ด้วย input อะไร แบ่งตรงนี้ให้ถูกแล้วสาม cloud ยังจัดการไหว แบ่งผิดแล้วคุณจะได้ทุกอย่างสามชุด
Terraform module ตอบคำถาม “network คืออะไร?” ครั้งเดียว โดยเก็บรายละเอียดเจาะจงต่อ cloud ไว้ข้างใน — VPC บน AWS, VNet บน Azure live/ directory ตอบว่า “cloud ไหน, region ไหน, CIDR ไหน?” การแยกแบบนั้นคือสิ่งที่ทำให้ live/aws/network, live/gcp/network, และ live/azure/network เรียก module รูปทรงเดียวกัน ด้วย input ต่างกันได้ แทนที่จะ duplicate โค้ด resource สามครั้ง
Terragrunt คือกาวใน live/ root.hcl ตัวเดียวต่อ cloud เก็บ setting ที่ทุก unit ใช้ร่วมกัน — provider configuration และ remote-state backend — แล้วแต่ละ unit ก็ include เข้ามา นั่นคือชัยชนะ DRY ที่ Module 3 จะแกะให้ดู ตรงนี้เราแค่เจียดพื้นที่ไว้ให้ก่อน
ข้อดีข้อเสีย
หัวข้อที่มีชื่อว่า “ข้อดีข้อเสีย”การแบ่ง modules/ + live/ เทียบกับ Terraform tree แบน ๆ ตัวเดียวต่อ cloud:
- Pros: module เขียนและ test ครั้งเดียว แล้วนำกลับมาใช้ tree
live/ผอมอยู่ — ส่วนใหญ่เป็น input กับการต่อสาย dependency — diff ของ “เพิ่ม region ที่สอง” หรือ “ดัน node count” จึงเล็กและอ่านง่าย - Cons: อ้อมมากขึ้น จะเข้าใจว่า
live/aws/clusterสร้างอะไรจริง ๆ คุณต้องตามsourceเข้าไปที่modules/aws/clusterการกระโดดนั้นคือราคาของการ reuse และคุ้มทันทีที่คุณมีผู้เรียกมากกว่าหนึ่งราย
module directory แยกต่อ cloud (modules/aws, modules/gcp, modules/azure) เทียบกับ module เดียวที่ branch ด้วย count/for_each ต่อ cloud:
- Pros: implementation ของแต่ละ cloud วิวัฒน์อิสระและอ่านสะอาด — ไฟล์ EKS ไม่พันกับ conditional ของ GKE provider, ชื่อ resource, และพฤติกรรมเฉพาะตัวแยกกันชัด
- Cons: มี duplication จริงบ้างข้ามทั้งสาม (ชื่อ variable, รูปทรง output) เรายอมรับโดยตั้งใจ: interface เหมือนกัน (ตั้งแต่ Module 4 เป็นต้นไป) ต่างแค่ไส้ใน และไฟล์ที่ชัดเจนดีกว่าไฟล์ที่ฉลาดเกินตัว
ติดตั้ง
หัวข้อที่มีชื่อว่า “ติดตั้ง”1. modules/ — reusable, cloud-specific implementations
หัวข้อที่มีชื่อว่า “1. modules/ — reusable, cloud-specific implementations”subtree ละหนึ่งต่อ cloud แต่ละอันมี module สี่ตัวเหมือนกัน ทุก module ชนิดเดียวกัน expose input และ output เดียวกัน บนทั้งสาม cloud — ต่างแค่ implementation
mkdir -p modules/{aws,gcp,azure}/{network,cluster,data,iam}interface convention (สร้างเต็มใน module บทหลัง) คือ:
- network — in:
name,cidr. out:network_id,private_subnet_ids,public_subnet_ids. - cluster — in:
name,network_id,subnet_ids,node_count,node_size. out:cluster_name,cluster_endpoint,cluster_ca. - data — in:
name,network_id,subnet_ids,db_name. out:db_host,db_port,db_name,db_user,db_password(sensitive). - iam — in:
cluster_name,namespace,service_account. out: the workload-identity binding id.
2. live/{aws,gcp,azure}/ — the per-cloud config tree
หัวข้อที่มีชื่อว่า “2. live/{aws,gcp,azure}/ — the per-cloud config tree”แต่ละ cloud ได้ root.hcl หนึ่งตัวและหนึ่ง directory ต่อ unit unit คือ state ที่ deploy ได้หนึ่งชิ้น — มี terragrunt.hcl ของตัวเองที่ชี้ไปที่ module และป้อน input
mkdir -p live/{aws,gcp,azure}/{network,cluster,data,platform,shopmicro}network,cluster,data,iam→ ชั้นที่เจาะจงต่อ cloud แต่ละตัวเรียก module ของตัวเองในmodules/<cloud>/platform→ ชั้น Helm ที่ เป็นกลาง ต่อ cloud (Datadog, Keycloak, GrowthBook)shopmicro→ workload คือhelm_releaseของ ShopMicro chart
3. live/<cloud>/root.hcl — the shared root
หัวข้อที่มีชื่อว่า “3. live/<cloud>/root.hcl — the shared root”root คือที่อยู่ของ setting เจาะจงต่อ cloud ที่ทุก unit ต้องใช้ นี่คือโครง AWS — block remote_state และ generate "provider" มีรายละเอียดใน Module 3 ดังนั้นถือว่าอันนี้เป็นรูปทรง ไม่ใช่ของสำเร็จ:
remote_state { backend = "s3" generate = { path = "backend.tf", if_exists = "overwrite_terragrunt" } config = { bucket = "clouddeploy-tfstate-aws" key = "${path_relative_to_include()}/terraform.tfstate" region = "us-east-1" encrypt = true dynamodb_table = "clouddeploy-locks" }}
generate "provider" { path = "provider.tf" if_exists = "overwrite_terragrunt" contents = <<EOFprovider "aws" { region = "us-east-1"}EOF}live/gcp/root.hcl และ live/azure/root.hcl เป็นแบบเดียวกัน แค่สลับ backend (gcs / azurerm) และ provider ที่ generate ออกมา region ทั้งสามถูกกำหนดตายตัวสำหรับคอร์ส: AWS us-east-1, GCP us-central1, Azure eastus
4. charts/ and .github/workflows/
หัวข้อที่มีชื่อว่า “4. charts/ and .github/workflows/”mkdir -p charts .github/workflowscharts/ เก็บ Helm values และ ShopMicro chart; .github/workflows/ เก็บ CI/CD จาก Module 12 ตอนนี้ว่างเปล่า — เป็น placeholder เพื่อให้โครงสร้างครบ
ตรวจสอบผล
หัวข้อที่มีชื่อว่า “ตรวจสอบผล”ยืนยันว่า tree มีอยู่และ root parse ได้:
find modules live -type d | sortlive/awslive/aws/clusterlive/aws/datalive/aws/networklive/aws/platformlive/aws/shopmicrolive/gcp...modules/aws/clustermodules/aws/datamodules/aws/iammodules/aws/network...จากนั้นเช็คว่า Terragrunt เห็น HCL root ที่ valid:
cd live/awsterragrunt --version # terragrunt version vX.Y.Zterragrunt hcl fmt --check # exits 0 if root.hcl is well-formattedยังไม่มี module ต่อสายไว้ จึงยังไม่มีอะไรให้ plan — นั่นคือ Module 2 ที่คุณจะยืน resource จริงตัวแรกขึ้นมา คุณเสร็จตรงนี้เมื่อ tree ของ directory ตรงกับ layout ข้างบนและ root.hcl format ได้สะอาด
ตรวจสอบความเข้าใจ:
- อธิบายอย่างละหนึ่งประโยค:
modules/directory เป็นเจ้าของอะไร และlive/directory เป็นเจ้าของอะไร? - ทำไมการเก็บ interface ของ module ให้เหมือนกันข้าม cloud ถึงสำคัญกว่าการเก็บ implementation ให้เหมือนกัน?
- “unit” ใน tree
live/คืออะไร และทำไมแต่ละตัวถึงได้terragrunt.hclของตัวเอง? root.hclรวมศูนย์ setting สองตัวไหนให้ทุก unit ใน cloud และถ้าไม่มี คุณต้อง copy-paste อะไร?
ตอนนี้ repository มีที่อยู่ให้ทุก module, live configuration ของทุก cloud, Helm charts, และ CI การแบ่ง modules/ เทียบกับ live/ — อะไร เทียบกับ ที่ไหน — คือกระดูกสันหลังที่ทุกอย่างเกาะ ต่อไปเราจะทิ้งโครงไว้ข้างหลังแล้วให้ Terraform ทำอะไรสักอย่างจริง ๆ: provider, resource ตัวแรกบนแต่ละ cloud, และ loop init/plan/apply
Next: Terraform Foundations →