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

Drift Detection and Import

configuration drift เกิดขึ้นเมื่อความเป็นจริงเปลี่ยนไปนอกการควบคุมของ Terraform และ import คือวิธีเอา resource จริงที่มีอยู่แล้วเข้ามาอยู่ใน state ของ Terraform โดยไม่ต้องสร้างใหม่

state เป็นแค่ snapshot ที่บันทึกโลกจริง ณ ตอน apply ครั้งล่าสุดเท่านั้น ไม่มีอะไรห้ามใครสักคนเปิด Azure Portal แล้วแก้ resource ตรง ๆ — แก้ tag ของ virtual machine, เปลี่ยน tier ของ storage account หรือเปิด rule ของ network security group ด้วยมือ Terraform ไม่มีทางรู้ว่าเกิดอะไรขึ้นจนกว่าจะเช็คอีกครั้ง

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,
]
tags = {
environment = "production"
}
}

สมมติเพื่อนร่วมทีมแก้ tag environment ของ VM ตัวนั้นใน Azure Portal เป็น "staging" นอกเหนือจากการรัน Terraform ใด ๆ ครั้งถัดไปที่ใครสักคนรัน terraform plan Terraform จะ refresh มุมมองของ resource จริง สังเกตว่า tag ไม่ตรงกับ configuration แล้ว แล้วแสดง diff ออกมา — โดยเสนอให้เปลี่ยน tag กลับเป็น "production" เพราะสำหรับ Terraform แล้ว configuration คือ source of truth และการแก้ใน portal คือ drift ที่ต้องแก้กลับ

Terminal window
terraform plan
# ~ resource "azurerm_linux_virtual_machine" "web" {
# ~ tags = {
# ~ "environment" = "staging" -> "production"
# }
# }

นี่คือวิธีปกติที่ plan แสดง drift ออกมา — เป็น proposed change ที่จะเขียนทับความเป็นจริงด้วย configuration ในครั้ง apply ถัดไป บางทีคุณก็อยากได้ทางกลับกัน คืออยากให้ Terraform แค่ update record ของตัวเองให้ตรงกับความเป็นจริง โดยไม่แตะ infrastructure จริงเลย นั่นคือหน้าที่ของ -refresh-only

Terminal window
terraform apply -refresh-only

โหมดนี้ refresh state จาก Azure resource จริง แล้วแสดงว่าอะไรเปลี่ยนไปบ้าง จากนั้นถามให้คุณ confirm การเขียนการเปลี่ยนแปลงนั้นลง terraform.tfstate — แต่ไม่แก้ Azure จริงเลย นี่คือเครื่องมือที่ถูกต้องเวลา drift นั้นตั้งใจให้เกิด แล้วคุณตัดสินใจแล้วว่าให้การเปลี่ยนแปลงใน portal ชนะ หรือตอนที่คุณต้อง update mental model ว่า state ตอนนี้เก็บอะไรอยู่ ก่อนตัดสินใจว่าจะแก้ configuration หรือยอมรับความเป็นจริง

เอา resource ที่มีอยู่แล้วเข้ามาอยู่ใต้การจัดการด้วย import

หัวข้อที่มีชื่อว่า “เอา resource ที่มีอยู่แล้วเข้ามาอยู่ใต้การจัดการด้วย import”

drift สมมติว่า Terraform จัดการ resource นั้นอยู่แล้ว แต่มีอีกปัญหาหนึ่งที่เกี่ยวข้องกัน คือ resource ที่มีอยู่จริงใน Azure แต่ไม่เคยถูกสร้างโดย Terraform เลย — อาจมีมาก่อนที่ทีมจะเริ่มใช้ Terraform หรือมีใครสักคนคลิกสร้างขึ้นมาใน portal ระหว่างเหตุการณ์ฉุกเฉิน terraform import คือคำสั่ง CLI ที่ผูก resource จริงตัวนั้นเข้ากับ resource address ใน configuration ของคุณ

Terminal window
terraform import azurerm_linux_virtual_machine.web /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/example-resources/providers/Microsoft.Compute/virtualMachines/web-vm

รันคำสั่งนั้นแล้วจะเขียนแค่ state entry เท่านั้น — โดยแมป azurerm_linux_virtual_machine.web เข้ากับ resource ID จริง แล้วดึง attribute ปัจจุบันทั้งหมดของ VM ตัวนั้นเข้ามา คำสั่งนี้ ไม่ เขียน resource "azurerm_linux_virtual_machine" "web" { ... } block ที่ตรงกันให้คุณ คุณยังต้อง hand-write HCL ที่ตรงกับ attribute ของ resource จริงมากพอ ให้ plan ครั้งถัดไปออกมาสะอาด ถ้า configuration ของคุณไม่ตรงกับความเป็นจริงหลัง import แล้ว plan จะแสดง diff แบบเดียวกับที่ drift สร้างขึ้น และคุณต้องปรับ HCL — ไม่ใช่ resource จริง — จนกว่าจะตรงกัน

Terraform 1.5 เพิ่มวิธี import แบบที่สอง เขียนตรงลงในไฟล์ .tf เป็น block ระดับ first-class แทนที่จะเรียก CLI แบบครั้งเดียวจบ

import {
to = azurerm_linux_virtual_machine.web
id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/example-resources/providers/Microsoft.Compute/virtualMachines/web-vm"
}

ข้างในทำงานแบบเดียวกับ terraform import แต่ต่างกันตรง workflow ที่สำคัญ คำสั่ง CLI ทำงานทันทีและแก้ state ณ ตอนที่คุณเรียกเลย — ไม่มี preview และไม่มีอะไรให้ review ก่อนเกิดขึ้น ส่วน import block เป็นแค่ configuration ที่จะโผล่มาเป็น planned action ในครั้งถัดไปที่คุณรัน terraform plan พร้อมกับการเปลี่ยนแปลงอื่น ๆ ก่อนที่อะไรจะเกิดขึ้นจริง

Terminal window
terraform plan
# Terraform will perform the following actions:
#
# # azurerm_linux_virtual_machine.web will be imported
# resource "azurerm_linux_virtual_machine" "web" {
# id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/example-resources/providers/Microsoft.Compute/virtualMachines/web-vm"
# size = "Standard_B2s"
# ...
# }

นั่นทำให้ import block เป็นวิธีที่ควรเลือกใช้เมื่อทำได้ — เพราะ plannable, review ได้ใน pull request เหมือน configuration change อื่น ๆ และรันซ้ำข้าม environment ได้ แทนที่จะเป็น side effect ที่เกิดทันทีซึ่งต้องมีใครสักคนจำไว้ว่าต้องรันมือบนทุกเครื่องที่ต้องใช้ คุณยังต้องเขียน resource block ที่ตรงกันเองอยู่ดีไม่ว่าจะใช้วิธีไหน — block นี้แค่บอก Terraform ว่า object จริงตัวไหนต้องผูกกับ address ตัวไหน

มีอีกสองคำสั่งที่เติมเต็มชุดเครื่องมือแก้ state ทั้งคู่แก้แค่ bookkeeping ของ Terraform เอง ไม่แตะ Azure เลย

Terminal window
# Move a resource to a new address in state, e.g. after refactoring into a module
terraform state mv azurerm_linux_virtual_machine.web module.compute.azurerm_linux_virtual_machine.web
# Stop tracking a resource in state, without destroying the real object
terraform state rm azurerm_linux_virtual_machine.web

terraform state mv คือสิ่งที่คุณใช้เวลาปรับโครงสร้าง configuration ใหม่ — เช่นย้าย resource เข้าไปใน module — แล้วไม่อยากให้ Terraform มองว่าเป็น “ลบตัวเก่า สร้างตัวใหม่” แค่ update address ใน state ให้ตรงกับตำแหน่งใหม่ใน HCL ของคุณ และ resource จริงบน Azure ไม่ถูกแตะเลย terraform state rm คือทิศทางตรงข้ามกับ import โดยลบ entry ของ resource ออกจาก state ทำให้ Terraform ลืมไปเลย แต่ object จริงบน Azure ก็ยังทำงานต่อไปเหมือนเดิม มีประโยชน์เวลาคุณอยากส่งต่อ resource ให้ Terraform configuration อื่นจัดการ หรือตั้งใจหยุดจัดการบางอย่างโดยไม่ลบทิ้ง

flowchart LR
  portal["Manual change in Azure Portal"] --> drift["Resource now disagrees with config"]
  drift -->|terraform plan| diff["Diff shown: drift detected"]
  existing["Existing untracked Azure resource"] -->|terraform import or import block| state["Resource added to state"]
  state -->|next plan| clean["plan shows no diff, if config matches"]
A manual portal edit is caught as drift by plan, while an existing untracked resource is brought in via import
เพื่อนร่วมทีมแก้ tag ของ virtual machine ตรง ๆ ใน Azure Portal จะเกิดอะไรขึ้นครั้งถัดไปที่มีคนรัน terraform plan
terraform apply -refresh-only ทำอะไร
ทำไม import block แบบ declarative มักถูกเลือกใช้มากกว่าการรันคำสั่ง CLI terraform import
terraform state rm ทำอะไรกับ Azure resource จริงที่เคย track อยู่