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

The State File

state ของ Terraform คือไฟล์ JSON ที่แมป resource แต่ละตัวใน configuration ของคุณเข้ากับ object จริงที่สร้างขึ้น และเป็นสิ่งเดียวที่ทำให้ Terraform รู้ว่าตัวเองจัดการอะไรอยู่

ตอนคุณรัน terraform apply Terraform สร้าง resource ผ่าน Azure Resource Manager API — virtual machine, storage account, resource group สักตัว Azure API ไม่มีแนวคิดเรื่อง “Terraform” เลย ถ้า subscription ของคุณมี virtual machine 500 ตัว Azure API ก็บอกไม่ได้ว่า VM ตัวไหนคือตัวที่ Terraform สร้างให้กับ resource "azurerm_linux_virtual_machine" "web" block นี้ ตัวไหนที่เพื่อนร่วมทีมสร้างมือใน Azure Portal และตัวไหนเป็นของ Terraform configuration อื่นไปเลย

Terraform แก้ปัญหานี้ด้วยการเก็บ record ของตัวเอง ทุกครั้งที่ apply Terraform จะเขียน mapping ระหว่าง resource address ใน configuration กับ ID จริงของ infrastructure ลงไฟล์ state ที่ชื่อ default คือ terraform.tfstate

resource "azurerm_linux_virtual_machine" "web" {
name = "web-vm"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
size = "Standard_B2s"
admin_username = "azureuser"
network_interface_ids = [
azurerm_network_interface.web.id,
]
}

หลัง apply ไฟล์ state จะบันทึกประมาณว่า azurerm_linux_virtual_machine.web -> /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/example-resources/providers/Microsoft.Compute/virtualMachines/web-vm path ยาว ๆ นั้นแหละคือหน้าตาจริงของ “real-world object” บน Azure — ทุก resource ถูกระบุด้วย resource ID แบบเต็มที่เข้ารหัส subscription, resource group, provider namespace และชื่อ resource ไว้ในสตริงเดียว นอกจาก ID แล้ว state ยัง cache ทุก attribute ที่ Azure ส่งกลับมาสำหรับ VM ตัวนั้นด้วย — private IP, OS disk ID, network interface ที่ผูกอยู่ และอื่น ๆ ข้อมูลที่ cache ไว้นี้ทำให้ Terraform ไม่ต้องเรียก Azure API แล้ว describe resource ทุกตัวใหม่แค่เพื่อคำนวณ plan เพราะเทียบ configuration กับ state ล่าสุดก่อนได้เลย นี่คือทั้งวิธีตรวจจับว่าอะไรเปลี่ยน และเป็นข้อดีด้าน performance จริง ๆ กับ configuration ขนาดใหญ่ state ยังเป็นที่เก็บ dependency metadata ด้วย ทำให้ Terraform รู้ลำดับที่ต้องสร้าง แก้ไข หรือลบ resource

state file ไม่ได้ผ่านการ sanitize attribute value ใด ๆ ที่ Terraform ได้รับจาก provider — หรือที่คุณตั้งตรงใน configuration — จะไปอยู่ใน terraform.tfstate แบบ plaintext รวมถึงค่าที่เป็น secret ด้วย ตัวอย่างที่เจอบ่อย

resource "azurerm_postgresql_flexible_server" "main" {
name = "app-db"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
version = "15"
sku_name = "GP_Standard_D2s_v3"
administrator_login = "appadmin"
administrator_password = "S3cretPassword!"
}

คุณไม่เคยรัน terraform output กับ password ตัวนั้น และไม่เคย print ใน log แต่ค่านั้นก็ยังไปอยู่ใน terraform.tfstate แบบ plaintext อยู่ดี เพราะ Terraform ต้องจำ attribute ทุกตัวที่ตั้งไว้ เพื่อตรวจ drift ตอน plan ครั้งถัดไป ใครก็ตามที่อ่านไฟล์นั้นได้ก็อ่าน password ได้ด้วย

นี่คือเหตุผลที่ terraform.tfstate ห้าม commit เข้า version control เด็ดขาด ใส่ลง .gitignore ตั้งแต่ commit แรกของโปรเจกต์ใหม่

.gitignore
terraform.tfstate
terraform.tfstate.backup
*.tfstate
*.tfstate.*

บทถัดไปจะพูดถึงการย้าย state ออกจาก laptop ไปไว้บน remote backend ที่ควบคุม access ได้จริง นั่นคือวิธีแก้ความเสี่ยงนี้ที่แท้จริง — .gitignore แค่กันอุบัติเหตุที่เห็นชัดที่สุดเท่านั้น

Terraform มีคำสั่งสามตัวสำหรับดูข้างใน state โดยไม่ต้องเปิดไฟล์ JSON เอง

Terminal window
# Human-readable dump of the entire current state
terraform show
# List every resource address currently tracked in state
terraform state list
# Show full attribute detail for one specific resource
terraform state show azurerm_linux_virtual_machine.web

terraform state list คือวิธีเร็วที่สุดในการตอบคำถามว่า “configuration นี้จัดการอะไรอยู่ตอนนี้” คำสั่งนี้ print address อย่าง azurerm_linux_virtual_machine.web หรือ module.network.azurerm_virtual_network.main โดยไม่มีรายละเอียดอื่น terraform state show <address> จะลงลึกไป address ใดตัวหนึ่ง แล้ว print attribute ทุกตัวที่ Terraform บันทึกไว้ ซึ่งบ่อยครั้งเร็วกว่าการไปเปิด Azure Portal เวลาต้องเช็คค่าที่ Terraform track อยู่

flowchart LR
  cfg["Terraform config (.tf files)"] --> apply["terraform apply"]
  apply --> azure["Real Azure resources"]
  azure -->|attributes written back| state["terraform.tfstate"]
  cfg -->|next run| plan["terraform plan"]
  state -->|read for comparison| plan
Config and real Azure resources both feed the state file that the next plan reads
ทำไม Terraform ถึงต้องมี state file แทนที่จะ query Azure Resource Manager API ทุกครั้ง
database password ถูกตั้งเป็น resource attribute แต่ไม่เคย print ด้วย terraform output ค่านั้นยังไปอยู่แบบ plaintext ที่ไหนได้ไหม
terraform state list กับ terraform state show ต่างกันยังไง
.gitignore ของโปรเจกต์ Terraform ใหม่ ควรมีอะไรตั้งแต่ commit แรก