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

Drift Detection and Import

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

state เป็นแค่ snapshot ที่บันทึกไว้ ณ ตอน apply ครั้งล่าสุดเท่านั้น ไม่มีอะไรกันไม่ให้ใครเปิด AWS Console แล้วแก้ resource ตรง ๆ เช่นแก้ tag ของ EC2 instance เปลี่ยนขนาด RDS instance หรือเปิด port บน security group ด้วยมือ Terraform ไม่มีทางรู้เรื่องนี้จนกว่าจะไปดูใหม่อีกครั้ง

resource "aws_instance" "web" {
ami = "ami-0abcdef1234567890"
instance_type = "t3.micro"
tags = {
Name = "web-server"
}
}

สมมติเพื่อนร่วมทีมแก้ tag Name ของ instance นั้นใน console เป็น "web-server-old" โดยไม่ผ่าน Terraform run ใด ๆ เลย ครั้งถัดไปที่ใครก็ตามรัน terraform plan Terraform จะ refresh มุมมองของ resource จริง สังเกตว่า tag ไม่ตรงกับ configuration แล้ว แล้วแสดง diff ออกมา — โดยเสนอให้เปลี่ยน tag กลับเป็น "web-server" เพราะสำหรับ Terraform แล้ว configuration คือ source of truth และการแก้ผ่าน console คือ drift ที่ต้องแก้ไขให้ตรงกัน

Terminal window
terraform plan
# ~ resource "aws_instance" "web" {
# ~ tags = {
# ~ "Name" = "web-server-old" -> "web-server"
# }
# }

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

Terminal window
terraform apply -refresh-only

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

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

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

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

Terminal window
terraform import aws_instance.web i-0123456789abcdef0

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

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

import {
to = aws_instance.web
id = "i-0123456789abcdef0"
}

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

Terminal window
terraform plan
# Terraform will perform the following actions:
#
# # aws_instance.web will be imported
# resource "aws_instance" "web" {
# id = "i-0123456789abcdef0"
# instance_type = "t3.micro"
# ...
# }

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

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

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

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

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