การย้ายครั้งนี้ครอบคลุม 3 สภาพแวดล้อม ทรัพยากรมากกว่า 50 ประเภท และทรัพยากร Terraform มากกว่า 300 รายการ
ทีมที่ดูแลสภาพแวดล้อมนี้มีผู้รับจ้างหลักเพียงคนเดียว เป้าหมายด้านปฏิบัติการจึงเรียบง่าย: ใช้ฟีเจอร์ใหม่ของ provider สำหรับ Cloudflare โดยไม่ทำให้ production เสียหาย
Cloudflare อยู่ใกล้ edge เกินกว่าจะย้ายโดยไม่ระวัง ทั้ง DNS, WAF, rulesets, Zero Trust, tunnels และ redirects หากกระบวนการไม่เป็นระเบียบ วงจรรับผลย้อนกลับจะช้าลงอย่างรวดเร็ว
ผมจัดการงานนี้เป็นการย้าย edge ที่ควบคุมได้: ทยอยนำการเปลี่ยนแปลงไปใช้ ไม่ใช้ auto-apply ทดสอบก่อน production และซ่อม state ด้วยสคริปต์สำหรับทรัพยากรที่ provider อัปเกรดให้ไม่ได้
ก่อนแตะ provider v5 ผมจึงกำหนดรูปแบบการทำงานให้ชัดเจน:
- บล็อกคำสั่งอันตราย
- เริ่มด้วยสิทธิ์ Cloudflare แบบอ่านอย่างเดียว
- ทำงานกับสำเนา state ในเครื่องก่อน
- ทดลองรันเครื่องมือย้ายระบบแบบ dry-run
- แล้วจึงพิจารณา import การทำความสะอาด state และ apply
โพสต์นี้อธิบายวิธีเตรียมการย้ายระบบเพื่อให้วิศวกรทำงานโดยคาดเดาน้อยลง ไม่ใช่รายละเอียดการย้ายทรัพยากรทุกประเภท
เริ่มด้วยสิทธิ์อ่านอย่างเดียว บล็อก
terraform apply ดึง state มาทำงานในเครื่อง และเตรียมพร้อมสำหรับการแก้ไขด้วยตนเองเหตุผลที่เรื่องนี้สำคัญต่อธุรกิจ ลิงก์ไปยังหัวข้อ
นี่ไม่ใช่เพียงการอัปเกรด Terraform แต่เป็นปัญหาโครงสร้างพื้นฐานใน Day 2
ช่วงแรกทีมส่วนใหญ่พอใจเพียงมี IaC แต่เมื่อแรงกดดันจากผลิตภัณฑ์เพิ่มขึ้น มีการเปลี่ยนแปลงเร่งด่วน คนเริ่มคลิกแก้ระบบโดยตรงและบอกว่าจะกลับมาเก็บภายหลัง ความครอบคลุมของ Terraform ก็ลดจาก 100% เป็น 90%, 80% แล้วทุกการเปลี่ยนแปลงจะช้าลงและเชื่อถือได้น้อยลง
นี่คือความเสี่ยงจริง
หากการกำหนดค่า edge ไม่อยู่ในโค้ดอย่างถูกต้อง:
- การส่งมอบช้าลง
- การเปลี่ยน production ตรวจสอบยากขึ้น
- วิศวกรใหม่ต้องพึ่งความรู้ที่อยู่ในตัวบุคคลมากขึ้น
- บริษัทต้องพึ่งความทรงจำของผู้ดูแลระบบคนเดียว
การย้ายนี้จึงสำคัญ เพราะนำ edge layer กลับสู่สภาพที่ธุรกิจเดินหน้าต่อได้โดยไม่พึ่ง click ops
สิ่งที่เปิดทางให้ทำได้นอกเหนือจาก Terraform ลิงก์ไปยังหัวข้อ
Cloudflare กำลังลงทุนเกินกว่าการใช้งาน CDN และ DNS แบบเดิม แพลตฟอร์มนักพัฒนากลายเป็นผลิตภัณฑ์หลัก ไม่ใช่ฟีเจอร์เสริม
หาก provider ล้าสมัยหรือระบบครึ่งหนึ่งจัดการด้วยมือ การนำสิ่งที่ Cloudflare ลงทุนอยู่มาใช้จะยากขึ้น
การย้ายช่วยให้บริษัทพร้อมใช้:
- Workers AI สำหรับรัน inference บนเครือข่าย Cloudflare
- AI Gateway สำหรับ observability, caching, retries, rate limiting และ fallback ของแอป AI
- Vectorize สำหรับ vector search และ retrieval workloads
- Durable Objects สำหรับการประสานงานแบบ stateful และระบบ real-time
- Agents SDK สำหรับเอเจนต์แบบ stateful ที่มี scheduling, tools และ human-in-the-loop
- Hyperdrive สำหรับเชื่อม Workers กับฐานข้อมูลตามภูมิภาคที่มีอยู่และเพิ่มประสิทธิภาพทั่วโลก
บริษัทจึงยังสร้างฟีเจอร์แอปบนแพลตฟอร์มเดียวกับที่อยู่หน้า production traffic ได้ สำหรับบริษัทเล็ก นี่คือแรงทวีคูณ
คุณค่าไม่ใช่ “เราอัปเกรด Terraform” แต่คือ “เราพร้อมนำความสามารถใหม่ของแพลตฟอร์มมาใช้โดยไม่ต้องแก้หนี้โครงสร้างพื้นฐานเก่าก่อน”
เริ่มด้วย Cloudflare token แบบอ่านอย่างเดียว ลิงก์ไปยังหัวข้อ
ขั้นต่อไปคือ authentication
ผมให้ Cloudflare token แบบอ่านอย่างเดียวก่อน ไม่ใช่ admin หรือ “สิทธิ์เต็มชั่วคราว” แต่ให้เท่าที่จำเป็นสำหรับตรวจสอบสิ่งที่มีอยู่
เหตุผลคือ:
- ต้องการสำรวจก่อน
- provider refresh และ plan มักต้องอ่าน API
- ต้องการตรวจสอบสิ่งที่มีอยู่ก่อนอนุญาตเส้นทางเขียน
- หาก token รั่วไหล ขอบเขตผลกระทบจะเล็กกว่ามาก
ระยะนี้ต้องเห็นเฉพาะ objects ที่ Terraform จัดการอยู่ เช่น zones, DNS, rulesets, WAF objects และ Zero Trust resources ตาม stack
หากภายหลังต้องใช้สิทธิ์เขียน ผมจะสลับข้อมูลประจำตัวเมื่อมนุษย์ตรวจ diff แล้วเท่านั้น การสำรวจและ API lookups ใช้ token แบบอ่านอย่างเดียว ส่วน mutation อยู่ในอีกระยะที่ผ่านการตรวจสอบ
ทำ Authentication ให้เรียบง่าย ลิงก์ไปยังหัวข้อ
รูปแบบ authentication ควรเรียบง่ายและชัดเจน:
export CLOUDFLARE_API_TOKEN="..."
token ควรมาจากแหล่ง secrets ที่เหมาะสม:
- secret manager ในเครื่อง
- secret store ของ CI
- shell session อายุสั้น
ห้าม commit ลง repository หรือคัดลอกใส่ prompt
แผน Migration ที่ผมใช้ ลิงก์ไปยังหัวข้อ
ลำดับจากบันทึกของผมคือ:
- อัปเกรดจาก
4.52.0เป็น4.52.5ก่อน - รัน
tf-migrateแบบ dry-run - ใช้การ rewrite HCL และตรวจทุกไฟล์
.tfที่เปลี่ยน - แก้ resources ที่เปลี่ยนชื่อหรือถูกถอดออกด้วยตนเอง
- เปลี่ยน provider เป็น
~> 5 - ซ่อมปัญหา state
- apply ใน
testก่อน - จึงค่อยแตะ
prod
ผมแยกงานเป็นหลาย PR:
- PR1: อัปเกรด provider ชั่วคราวเป็น
4.52.5 - PR2: rewrite HCL ด้วย
tf-migrateและแก้ไขด้วยตนเอง - PR3: provider
~> 5, ทำความสะอาด state, import และ validation ขั้นสุดท้าย
วิธีนี้ทำให้ diff อ่านง่าย เข้าใจผล CI ง่าย และมี checkpoint ชัดเจนหลังแต่ละระยะ อีกทั้งลดความเสี่ยง เพราะเมื่อระยะใดผิดพลาด เรารู้ว่าควรหยุด ย้อนกลับ หรือ plan ใหม่ตรงไหน
ดึง State มาทำงานในเครื่องก่อน ลิงก์ไปยังหัวข้อ
ผมไม่เริ่มทดลองกับ backend ตามปกติ แต่ดึง state มาในเครื่องและเตรียมโหมดทำงานในเครื่องก่อน:
./tf.sh state pull > migration.tfstate
cp <environment>.tfvars <environment>.auto.tfvars
การคัดลอก tfvars เป็น *.auto.tfvars ทำให้ Terraform ในเครื่องโหลดค่าของสภาพแวดล้อมเดียวกันโดยไม่ต้องเพิ่ม flags ทุกคำสั่ง
จากนั้นเปลี่ยน backend ชั่วคราว:
terraform {
backend "local" {
path = "migration.tfstate"
}
}
เมื่อรู้ว่าอาจต้องทำความสะอาด state, import และอัปเกรด provider schema ผมต้องการสำเนาในเครื่องก่อน เพื่อมีพื้นที่ปลอดภัยสำหรับตรวจสอบ ทดสอบ และทำความเข้าใจความเสียหายก่อนแตะ backend ปกติ ทั้งยังให้ feedback loop เร็วขึ้น เพราะ migration นี้ประกอบด้วยการปรับทีละเล็กหลายครั้ง
คำเตือน: Terraform state ในเครื่องอาจมี secrets ต้องดูแลไฟล์นี้ให้เหมาะสม
ทดลองรันเครื่องมือย้ายระบบแบบ Dry-run ก่อน ลิงก์ไปยังหัวข้อ
ก่อนเปลี่ยนข้อจำกัด provider ผมรันเครื่องมือแบบ dry-run:
tf-migrate migrate --source-version v4 --target-version v5 --dry-run --config-dir .
ส่วนสำคัญคือ warnings ซึ่งระบุจุดที่ต้องทำงานด้วยตนเอง:
- Access policies ที่ผูกกับ application
- resources ที่ถูกถอดออกใน
v5 - resources ที่ต้องล้าง state และ import ใหม่
นี่เป็นผลลัพธ์ที่ดี เครื่องมือไม่ต้องย้ายทุกอย่างให้เสร็จ เพียงชี้ว่าควรใช้เวลาตรวจสอบตรงไหน
สิ่งที่ tf-migrate ทำไม่เสร็จ
ลิงก์ไปยังหัวข้อ
Access policies ที่ผูกกับ application ย้ายอัตโนมัติไม่ได้ ใน v5 policies เหล่านี้ต้องอยู่ inline ใน cloudflare_zero_trust_access_application
cloudflare_split_tunnel ถูกถอดออกและต้องย้ายไปกำหนดค่าใน device profile
cloudflare_zone_settings_override ก็ถูกถอดออก เครื่องมือสร้าง resources แยกตาม setting แต่ยังต้องลบ state เก่าและ import resources ใหม่ให้ถูกต้อง
ยังมีการเปลี่ยนระดับ field เช่น min_days_for_renewal ถูกถอดจาก origin CA certificate resources
ดังนั้นผมถือผลจากเครื่องมือ migration เป็นรอบแรก ไม่ใช่ผลลัพธ์สุดท้าย
เตรียมรับการทำความสะอาด State ด้วยตนเอง ลิงก์ไปยังหัวข้อ
บางครั้ง provider v5 decode payload ใน state เก่าไม่ได้ ทำให้ Terraform ล้มเหลวก่อนแสดง diff ที่ใช้ประโยชน์ได้ ผมพบกับ:
- Zero Trust gateway policies
- load balancer monitors
- zones และ objects ที่เกี่ยวกับ zone settings
ตัวอย่าง error:
rule_settings: expected object, got arrayheader: expected object, got arrayplan: expected object, got string
แนวทางคือ:
- สำรอง state
- ลบเฉพาะ addresses ที่ล้มเหลวออกจาก state
- import กลับด้วยรูปแบบ
v5 - รัน plan ใหม่
local backend ทำให้วิศวกรซ่อม state ได้อย่างตั้งใจแทนการเร่งทำกับระบบจริง
ทรัพยากร Cloudflare บางรายการยังต้องตรวจด้วยคน ลิงก์ไปยังหัวข้อ
รายการที่ควรตรวจเป็นอันดับแรก:
- Access policies ที่ผูกกับ applications
- split tunnel configuration
- zone settings overrides
- rulesets
- load balancer resources
Access policies ไม่ใช่เพียงปัญหาเปลี่ยนชื่อ ใน v5 บาง policy ต้องย้ายเป็น inline policy ใน application resource ผมไม่ไว้ใจให้ automatic rewrite ทำโดยไม่มีการตรวจสอบ
zone settings ก็เช่นกัน resources แบบ override เก่าอาจกลายเป็น resources แยกจำนวนมาก ซึ่งมักต้อง import และล้าง state อย่างชัดเจน ไม่ใช่เพียงแก้ HCL
ใช้ Shell Scripts ขนาดเล็กช่วย Migration ลิงก์ไปยังหัวข้อ
สคริปต์ขนาดเล็กแบบใช้แล้วทิ้งช่วยได้มากกว่าการจำทุก state rm และรูปแบบ import สคริปต์ควรเรียบง่าย ชัดเจน และตรวจสอบได้ง่าย
ผมใช้สคริปต์สามประเภท:
- ลบ state entries เก่า
- import resources ที่เปลี่ยนชื่อกลับเข้า state
- query Cloudflare API และจับคู่ objects อัตโนมัติ
IDs ในตัวอย่างต่อไปนี้เป็นค่าจำลองเพื่อแสดงรูปแบบเท่านั้น
ตัวอย่าง 1: สคริปต์ล้าง State หลายรายการ ลิงก์ไปยังหัวข้อ
#!/usr/bin/env bash
set -euo pipefail
terraform state rm \
'cloudflare_ruleset.example' \
'cloudflare_access_policy.example' \
'cloudflare_zone_settings_override.this' \
'cloudflare_worker_domain.this' \
'cloudflare_tunnel_virtual_network.default'
วิธีนี้ปลอดภัยกว่าพิมพ์รายการยาวด้วยมือเมื่อเหนื่อยและผ่าน plan มาหลายรอบแล้ว
ตัวอย่าง 2: สคริปต์ Import ที่รู้จัก Environment ลิงก์ไปยังหัวข้อ
สำหรับ resources ที่มีในทุก environment แต่ใช้ IDs ต่างกัน ผมใช้ wrapper ตรวจ AWS account แล้วเลือก import ID:
#!/usr/bin/env bash
set -euo pipefail
ACCOUNT_ID="$(aws sts get-caller-identity --query Account --output text)"
case "$ACCOUNT_ID" in
"123456789012")
ENV_NAME="test"
CLOUDFLARE_ACCOUNT_ID="7f3c5a0b1d4e6f8899aabbccddeeff00"
RESOURCE_ID="2c9d4a8f7b6e5d4c3b2a1908fedcba76"
;;
"210987654321")
ENV_NAME="prod"
CLOUDFLARE_ACCOUNT_ID="7f3c5a0b1d4e6f8899aabbccddeeff00"
RESOURCE_ID="8a7b6c5d4e3f2109fedcba9876543210"
;;
*)
echo "Unsupported account: $ACCOUNT_ID"
exit 1
;;
esac
echo "Detected environment: $ENV_NAME"
terraform import cloudflare_load_balancer_monitor.default "$CLOUDFLARE_ACCOUNT_ID/$RESOURCE_ID"
รูปแบบนี้ใช้ได้กับ load balancer monitors, pools, load balancers, worker domains และ resources อื่น
ตัวอย่าง 3: อ่าน Plan แล้ว Import Rulesets ที่มีอยู่ ลิงก์ไปยังหัวข้อ
บางครั้ง Terraform ต้องการสร้าง ruleset ที่มีอยู่แล้วใน Cloudflare หาก plan มี metadata เพียงพอ ผมไม่ต้องการ import ทีละรายการด้วยมือ
สคริปต์ช่วยทำงานดังนี้:
- อ่าน
plan.txt - หา
cloudflare_ruleset.* will be created - ดึง zone ID, name, phase และ description
- เรียก Cloudflare API
- หา ruleset ID ที่ตรงกัน
- รัน
terraform import
รูปแบบคร่าว ๆ:
#!/usr/bin/env bash
set -euo pipefail
PLAN_FILE="${1:-plan.txt}"
ZONE_ID="f1e2d3c4b5a697887766554433221100"
RULESET_ID="9b8a7c6d5e4f32100123456789abcdef"
# parse plan output here
# call Cloudflare API here
# match by zone_id + name + phase
# terraform import "cloudflare_ruleset.example" "zones/$ZONE_ID/$RULESET_ID"
นี่คือจุดที่ automation ประหยัดเวลาแทนการเพิ่มความเสี่ยง และลดการพึ่งพาคนคนเดียว หากไม่มีสคริปต์ ความรู้ migration จะอยู่ในความทรงจำของวิศวกรคนหนึ่ง แต่เมื่อมีสคริปต์ วิศวกรคนอื่นทำตาม เข้าใจรูปแบบงานซ่อม และทำซ้ำได้โดยไม่ต้อง reverse-engineer ทั้ง stack
วางเครื่องมือไว้หลัง Guardrails ลิงก์ไปยังหัวข้อ
ผมยังใช้ code assistants กับงานประเภทนี้ เพราะช่วยสแกนไฟล์ .tf จำนวนมาก หา resources ที่เปลี่ยนชื่อ สรุป warnings และเตรียมการแก้ซ้ำ ๆ
แต่ขอบเขตต้องชัดเจน:
- อ่าน repository
- อ่านเอกสาร provider
- เตรียมการเปลี่ยนโค้ด
- สรุป migration warnings
- ห้าม apply การเปลี่ยนแปลงโครงสร้างพื้นฐานเอง
หากใช้ Cursor, beforeShellExecution เป็นหนึ่งในมาตรการควบคุมที่เพิ่มได้ง่าย โดยใช้เป็นชั้นปฏิเสธก่อนรันคำสั่ง:
"beforeShellExecution": [
{
"command": "~/.cursor/hooks/block-apply.sh"
}
]
hook ขนาดเล็ก:
#!/usr/bin/env bash
set -euo pipefail
input="$(cat 2>/dev/null || echo '{}')"
cmd="$(echo "$input" | jq -r '.command // .cmd // .shell_command // empty')"
case "$cmd" in
*"terraform apply"*|*"terraform destroy"*|*"terraform import"*|*"terraform state rm"*|*"terraform state mv"*|*"auto-approve"*)
echo "Blocked by policy during migration window: $cmd" >&2
exit 2
;;
esac
ระยะแรกเพียงเท่านี้ก็พอ เครื่องมือยังสแกน modules เปรียบเทียบ resources ระหว่าง v4 และ v5 เตรียม refactors และสร้างบันทึกสำหรับ review ได้ แต่ข้ามไปทำ mutation โดยตรงไม่ได้
นโยบาย deny นี้ใช้สำหรับช่วงสำรวจและเตรียม diff ด้วย assistant เมื่อ review เสร็จ ผมจะรัน terraform import, terraform state rm และ terraform apply ที่อนุมัติแล้วเองใน shell session แยกซึ่งมีการกำกับดูแล hook มีไว้หยุด mutation ก่อนเวลา ไม่ได้ห้ามเวิร์กโฟลว์ migration ตลอดไป
สิ่งที่ช่วยมากที่สุด ลิงก์ไปยังหัวข้อ
- ใช้
testก่อนและให้prodอยู่หลังการ review - รัน
tf-migrateแบบ dry-run ก่อนเปลี่ยนเวอร์ชัน provider - เตรียมรับงาน
state rmและterraform import - เก็บ helper scripts สำหรับ import ที่ทำซ้ำ
- ให้ CI รัน plan ตลอดทุกระยะ
- ถอด
-auto-approveออกในช่วง migration
ทั้งหมดนี้ไม่ซับซ้อน แต่เมื่อรวมกันทำให้ migration ผ่านได้ง่ายและลดค่าบำรุงรักษาภายหลัง ตรรกะซ่อม state หรือ import ที่ทำซ้ำจะกลายเป็นเครื่องมือปฏิบัติการที่มีเอกสาร แทนพิธีกรรมครั้งเดียว
เวิร์กโฟลว์ระยะแรก ลิงก์ไปยังหัวข้อ
ก่อนขั้นตอนที่เขียนข้อมูลได้ วงจรทำงานควรเล็ก:
- อ่านโค้ด Terraform
- รัน migration แบบ dry-run
- เตรียมการเปลี่ยนโค้ด
- รันการตรวจสอบที่ปลอดภัย เช่น
terraform fmtและterraform validate - เตรียมคำสั่ง import และล้าง state สำหรับ review
- ตรวจ diff
เป้าหมายแรกไม่ใช่ “ทำ migration ให้เสร็จ” แต่คือกำจัดความไม่แน่นอน
ทดสอบก่อน Production และให้ CI ทำงานต่อ ลิงก์ไปยังหัวข้อ
กฎ rollout ของผมคือ:
- migrate และ apply ใน
test - ยืนยันว่า plan หลัง apply สะอาด
- ให้ CI plan ทั้ง
testและprod - จึงอนุญาตเส้นทาง
prod
ระหว่าง migration แผน prod อาจไม่เสถียรชั่วคราวจากขั้นตอนเปลี่ยนชื่อและ state ซึ่งยอมรับได้ในช่วงสั้น ๆ แต่การ apply prod โดยไม่ตรวจสอบยอมรับไม่ได้
ผมถอด -auto-approve ออกจากกระบวนการทั้งหมดในช่วงนี้
ผลลัพธ์ ลิงก์ไปยังหัวข้อ
เส้นทางอัปเกรดไม่ได้อัตโนมัติทั้งหมด แต่การใช้ dry-run, state ในเครื่อง, scripted imports และ staged rollout ทำให้คาดการณ์ได้มากพอที่จะดำเนินงานอย่างปลอดภัย
นั่นคือเป้าหมายจริง ไม่ใช่ทำให้ migration ดูสวยงาม แต่ทำให้ผ่านโดยไม่ทำลาย production และไม่ให้ความทรงจำของวิศวกรคนเดียวกลายเป็น runbook เพียงชุดเดียว
ผมสนุกกับส่วนนี้ของงานโครงสร้างพื้นฐาน ผมรับผิดชอบความเสี่ยงทางเทคนิคได้เมื่อกระบวนการชัดเจนและมี rollback path ที่ใช้ได้จริง
ผลลัพธ์ในหลายมุมคือ:
- พร้อมนำความสามารถใหม่ของแพลตฟอร์ม Cloudflare มาใช้
- พึ่ง click ops และความทรงจำของคนเดียวน้อยลง
- รูปแบบ rollout สำหรับ migration ที่ edge ของ production ปลอดภัยขึ้น
- ชัดเจนขึ้นว่าวิศวกรคนอื่นสามารถดูแลสภาพแวดล้อมนี้ต่อได้
กำหนดเส้นทาง Reset ก่อนต้องใช้ ลิงก์ไปยังหัวข้อ
กำหนดทางกลับก่อน apply ครั้งแรก หลังซ่อมในเครื่องควรมี reset path ที่ชัดเจน:
- คืน backend block ตามปกติ
- reinitialize Terraform
- migrate backend metadata หากจำเป็น
- ลบไฟล์ชั่วคราว เช่น tfvars ที่คัดลอก scratch plans และ local state artifacts
หากข้ามขั้นตอนนี้ migration จะยุ่งเหยิงอย่างรวดเร็ว ไฟล์ชั่วคราวสะสม คนลืมว่า plan ใดใหม่ที่สุด และตัดสินใจผิดได้ง่ายขึ้น
สรุป ลิงก์ไปยังหัวข้อ
ส่วนยากของ migration นี้ไม่ใช่การ rewrite HCL แต่คือการทำให้กระบวนการคาดการณ์ได้มากพอที่วิศวกรคนอื่นจะทำตามได้
กฎจึงเรียบง่าย:
- read-only ก่อน
- local state ก่อน
- dry-run ก่อน
- ใช้ helper scripts ขนาดเล็กแทนงานซ้ำด้วยมือ
- ให้มนุษย์ตรวจสอบก่อน mutation
นี่คือวิธีที่ผมเริ่ม migration Cloudflare provider จาก v4 ไป v5 และช่วยให้วิศวกรคนถัดไปทำงานได้เร็วขึ้น