ข้ามไปยังเนื้อหา

Remote state & locking

block remote_state ใน root.hcl ของแต่ละ cloud — ชิ้นที่ย้าย state ออกจาก laptop ของคุณเข้าไปที่ S3, GCS, และ Azure Blob Storage แต่ละตัวมี locking เพื่อไม่ให้ apply สองครั้งชนกัน เราจะใช้ path_relative_to_include() เพื่อให้ state ของทุก unit ลงจอดที่ path ที่สะท้อน filesystem แล้ว generate provider ที่ถูกต้องต่อ cloud

นี่ปิดฐานของ Terragrunt หลังจากนี้ ทุก module ในคอร์สสืบทอด remote, locked, encrypted state ฟรี ๆ — ชั้น network, cluster, data, และ iam ทำงานได้เลย

local state สอบตกทั้งสองข้อจาก Module 2: แชร์ไม่ได้และไม่ encrypt remote state แก้ทั้งคู่ state อยู่ใน cloud bucket, encrypt at rest; locking ทำให้แน่ใจว่าขณะที่ apply หนึ่งถือ lock อยู่ อีกตัวจะรอแทนที่จะทำไฟล์พัง นั่นคือสิ่งที่ให้เพื่อนร่วมทีม — หรือ CI job — apply ได้อย่างปลอดภัยโดยไม่ทับคุณ

แต่ละ cloud เก็บ backend ของตัวเอง: AWS state ใน S3, GCP state ใน GCS, Azure state ใน Azure Storage นี่ไม่ใช่แค่ความเป็นระเบียบ — แต่คือการแยก blast-radius และ data residency ปัญหากับ backend ของ cloud หนึ่งไม่ทำให้อีกสองตัวล่มได้ และ state ของแต่ละ cloud อยู่ใน cloud นั้น

path_relative_to_include() คือฟังก์ชันเล็ก ๆ ที่ทำให้สิ่งนี้ scale ได้ โดย return path ของ unit เทียบกับ root.hcl ที่ include อยู่ ดังนั้น live/aws/network เขียน state ไปที่ network/terraform.tfstate และ live/aws/cluster ไปที่ cluster/terraform.tfstate — อัตโนมัติ โดยไม่ต้องมี backend config ต่อ unit

remote state พร้อม locking เทียบกับ local state:

  • Pros: แชร์ได้, encrypt, และปลอดภัยสำหรับ apply พร้อมกัน state รอดจาก laptop ที่หาย และ CI apply ต่อ source of truth เดียวกับคุณได้
  • Cons: คุณต้อง bootstrap backend ก่อน (ปัญหาไก่กับไข่ด้านล่าง) และ lock ที่ค้างบางครั้งต้องใช้ terragrunt force-unlock ต้นทุน operational เล็ก ๆ แลกกับความปลอดภัยก้อนใหญ่

แต่ละ cloud เก็บ backend ของตัวเอง เทียบกับ backend กลางตัวเดียวสำหรับทั้งสาม:

  • Pros: การแยก — outage หรือ misconfiguration ของ backend หนึ่งกระทบตัวอื่นไม่ได้ — บวก data residency และ IAM ที่อยู่ภายในแต่ละ cloud
  • Cons: สาม backend ต้องสร้างและ secure แทนที่จะเป็นตัวเดียว เรายอมรับ: การรวม state ของสาม cloud ไว้ที่ provider เดียวเท่ากับยื่นกุญแจของทั้ง platform ให้ provider นั้น ซึ่งขัดกับจุดประสงค์ของ multi-cloud

backend ต้องมีอยู่ก่อน Terraform จึงจะเก็บ state ลงไปได้ — คุณจัดการ state bucket ด้วย state ที่อยู่ในนั้นไม่ได้ สร้างแต่ละตัวครั้งเดียวด้วยมือ:

Terminal window
# AWS — S3 bucket + DynamoDB lock table
aws s3api create-bucket --bucket clouddeploy-tfstate-aws --region us-east-1
aws s3api put-bucket-versioning --bucket clouddeploy-tfstate-aws \
--versioning-configuration Status=Enabled
aws dynamodb create-table --table-name clouddeploy-locks \
--attribute-definitions AttributeName=LockID,AttributeType=S \
--key-schema AttributeName=LockID,KeyType=HASH \
--billing-mode PAY_PER_REQUEST --region us-east-1
# GCP — GCS bucket (locking is built in, no lock table needed)
gcloud storage buckets create gs://clouddeploy-tfstate-gcp \
--location=us-central1 --uniform-bucket-level-access
# Azure — storage account + container (blob leasing provides locking)
az group create --name clouddeploy-tfstate --location eastus
az storage account create --name clouddeploytfstate \
--resource-group clouddeploy-tfstate --location eastus --sku Standard_LRS
az storage container create --name tfstate --account-name clouddeploytfstate
live/aws/root.hcl
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 = <<EOF
provider "aws" {
region = "us-east-1"
}
EOF
}

Recent Terraform can lock S3 state natively with use_lockfile = true, retiring the separate DynamoDB table. This course keeps dynamodb_table because it’s still the most widely deployed setup you’ll meet — but on a greenfield backend, the native lockfile is the simpler choice.

GCS lock state ในตัว จึงไม่มี lock table ให้จัดการ:

live/gcp/root.hcl
remote_state {
backend = "gcs"
generate = {
path = "backend.tf"
if_exists = "overwrite_terragrunt"
}
config = {
bucket = "clouddeploy-tfstate-gcp"
prefix = "${path_relative_to_include()}"
}
}
generate "provider" {
path = "provider.tf"
if_exists = "overwrite_terragrunt"
contents = <<EOF
provider "google" {
project = "YOUR_GCP_PROJECT_ID"
region = "us-central1"
}
EOF
}

Azure lock state ผ่าน blob leasing ซึ่งมีในตัวเช่นกัน:

live/azure/root.hcl
remote_state {
backend = "azurerm"
generate = {
path = "backend.tf"
if_exists = "overwrite_terragrunt"
}
config = {
resource_group_name = "clouddeploy-tfstate"
storage_account_name = "clouddeploytfstate"
container_name = "tfstate"
key = "${path_relative_to_include()}/terraform.tfstate"
}
}
generate "provider" {
path = "provider.tf"
if_exists = "overwrite_terragrunt"
contents = <<EOF
provider "azurerm" {
features {}
}
EOF
}

รูปทรงเดียวกันบนทุก cloud: block remote_state ที่ key มาจาก path_relative_to_include() และ block generate "provider" ต่างแค่ชนิด backend และ provider

init AWS network unit ใหม่; ตอนนี้ Terragrunt ตั้งค่า S3 backend และถ้าคุณเคยมี local state Terragrunt จะเสนอ migrate ให้:

Terminal window
cd live/aws/network
terragrunt init
Initializing the backend...
Successfully configured the backend "s3"! Terraform will automatically
use this backend unless the backend configuration changes.

apply แล้วยืนยันว่า state object ลงจอดใน bucket จริง — สะท้อน path ของ unit:

Terminal window
terragrunt apply
aws s3 ls s3://clouddeploy-tfstate-aws/network/
# → terraform.tfstate

พิสูจน์ว่า locking ทำงาน: เริ่ม apply แล้วขณะที่กำลังรันกลางคัน เปิดตัวที่สองใน terminal อื่น ตัวที่สองปฏิเสธที่จะไปต่อ:

Error acquiring the state lock
Lock Info:
ID: 3f2a... Operation: OperationTypeApply
Who: you@laptop ...

สุดท้าย plan ทั้ง cloud ตอนนี้ที่ state เป็น remote และ locked แล้ว:

Terminal window
cd live/aws
terragrunt run --all plan # No changes across the tree — state matches reality.

คุณเสร็จเมื่อ init รายงานว่า backend ตั้งค่าแล้ว, state object โผล่ใน bucket ที่ path ของ unit, และ apply ตัวที่สองที่พร้อมกันถูก lock บล็อก

ตรวจสอบความเข้าใจ:

  1. ทำไมคุณต้องสร้าง state bucket และ lock table ก่อน Terraform จะใช้ได้ — และคุณจะจัดการอย่างไรโดยไม่ติดปัญหาไก่กับไข่?
  2. state locking ป้องกันอะไร และ backend S3, GCS, และ azurerm แต่ละตัวให้ locking มาอย่างไร?
  3. path_relative_to_include() คำนวณอะไร และทำไมการใช้ใน state key ถึง scale ไปทั้ง tree ของ unit ได้?
  4. ให้เหตุผลสองข้อว่าทำไมแต่ละ cloud ถึงเก็บ backend ของตัวเองแทนที่จะรวม state ของทั้งสาม cloud ไว้ที่เดียว

ตอนนี้ state อยู่ใน backend ของแต่ละ cloud, encrypt และ locked, พร้อม key ที่วางให้สะท้อน tree live/ — เขียนครั้งเดียวใน root.hcl แล้วทุก unit สืบทอด นั่นทำให้ฐานของ IaC ครบ: module ที่ reusable, Terragrunt unit แบบ DRY, และ remote locked state บนทั้งสาม cloud ทุก module จากนี้สร้างบนฐานนี้ ต่อไปเราจะเขียน infrastructure module จริงตัวแรก — network — แล้วยืนขึ้นมาสามแบบหลัง interface เดียว

Next: Networking →