เคสนี้ผมเจอกับตัวเองตอนทดสอบ flow re-signup ของ Newton ครับ — ลูกค้าที่เคย cancel ไป แล้วกลับมาสมัครใหม่ ระบบ auto-provision ของผมสร้าง VPS ใหม่ให้เรียบร้อย ใส่ DNS เข้า Cloudflare เรียบร้อย ส่งเมล "พร้อมใช้งาน" เรียบร้อย — ลูกค้าคลิก URL กลับเจอ ERR_SSL_PROTOCOL_ERROR 555
ผมงงครับ ของใหม่ทั้งดุ้น ทำไมพัง?
ทิม (AI Agent ของผม) นั่งไล่ Cloudflare API กับ DNS resolver ครึ่งชั่วโมง สรุปสั้นๆ คือ — DNS A record เก่าของลูกค้าคนนี้ ยังค้างอยู่ใน Cloudflare ตั้งแต่รอบสมัครก่อน ชี้ไปที่ IP ของ VPS ที่ผมลบทิ้งไปนานแล้ว และเพราะ Cloudflare ดันยอมให้ A record ชื่อเดียวกันมี 2 records พร้อมกัน DNS resolver มันก็สลับ round-robin 50% ลูกค้าเข้า server ใหม่ได้ปกติ อีก 50% เด้งไป IP ตายตั้งแต่ DNS
วันนั้นทิมเลย rebuild flow ของระบบ provision ใหม่ ใส่กัน 3 ชั้น เพื่อให้ failure mode นี้กลับมาไม่ได้อีก ลองเล่าให้ฟังครับ
โฟลว์เดิม — ที่ดูเรียบร้อย แต่จริงๆ ไม่
เพื่อให้เข้าใจ ขอเล่า flow ของระบบ auto-provision ของ Newton สั้นๆ ก่อน — ตอนลูกค้ากดจ่ายเงิน Stripe เด้ง webhook มา ทิมรัน:
- สร้าง VPS ใหม่ผ่าน Hetzner API — ได้ IP กลับมา
- ยิง POST ไป
/zones/{zone}/dns_recordsของ Cloudflare สร้าง A recordnewton-customer-N.in.thชี้ไป IP ใหม่ - SSH เข้า server รัน provision script ลง Tim Chat + Claude Code + tools ทั้งหมด
- เซฟ row ในตาราง
serversสถานะactive - ส่งเมล "พร้อมใช้งาน" หาลูกค้า + Telegram แจ้งผม
ดูเรียบครับ — แต่ขั้นตอนที่ 2 มันมีรูโหว่ Cloudflare ตามดีฟอลต์ ยอมให้ A record ชื่อเดียวกันมีหลายตัว POST ใหม่ไม่ overwrite ของเก่า มัน append เข้าไปอีก record
กับลูกค้าใหม่ที่ subdomain ยังไม่เคยใช้ ไม่มีปัญหา — แต่ลูกค้าที่ re-signup ด้วย email เดิม ระบบ assign subdomain เดิมให้ ของเก่ายังอยู่ ของใหม่ append เข้าไป กลายเป็น 2 records ชี้ไปคนละ IP
ทำไม HTTPS เด้งทั้งที่ server ใหม่ก็ปกติ
คำถามถัดมาคือ ทำไม HTTPS เด้ง? ก็เพราะ Caddy บน server ใหม่ต้องคุยกับ Let's Encrypt เพื่อขอใบ certificate ผ่าน HTTP-01 challenge — แต่ตอน Let's Encrypt resolve domain มันก็เจอ 2 IPs มัน round-robin ไปทาง IP เก่าที่ตายแล้ว challenge ก็พังทันที ใบ cert ไม่ออก HTTPS ลูกค้าก็ขาด
แต่ทั้งหมดนี้เกิดขึ้นใน background — webhook handler ของผมไม่รู้เลยว่ามี issue มันส่งเมล "พร้อมใช้งาน" ไปแล้ว ลูกค้าคลิกแล้วเจอ error 555
นี่แหละ failure mode คลาสสิคของระบบ automation — script รันเสร็จ exit 0 ไม่ใช่หลักฐานว่า outcome เกิดขึ้นจริง
ทิมเข้าไปแก้แบบ 3 ชั้น
ชั้นที่ 1 — Pre-delete ก่อน POST
ทิมแก้ provision_server() ให้ก่อนสร้าง A record ใหม่ ให้ GET รายการ records ที่ชื่อเดียวกันก่อน ถ้ามี ลบทิ้งทุกตัว แล้วค่อย POST ของใหม่ Idempotent เต็มตัว รันซ้ำกี่รอบก็เหลือ record เดียวที่ชี้ IP ปัจจุบัน
(เรื่องนี้ไม่ใช่ Bug ของ Cloudflare นะครับ — มัน intentional ระบบ DNS ยอมให้มี multi-A record อยู่แล้วเพื่อทำ load balance แต่ในเคสของผม มัน "อนุญาตให้เกิด" ปัญหาตามคาด)
ชั้นที่ 2 — HTTPS verify ก่อน mark active
ทิมเพิ่มขั้นตอน "verify ก่อนบอกว่าเสร็จ" เข้าไปใน auto_provision_server() — หลัง provision script จบ มันยิง requests.get("https://newton-customer-N.in.th", timeout=...) ถ้าได้ 200 OK ค่อย mark สถานะเป็น active + ส่งเมล "พร้อมใช้งาน"
ถ้าพัง (เช่น ใบ cert ยังไม่ออก หรือ DNS ยังไม่ propagate) → mark ssl_pending + Telegram เด้งหาผม + ไม่ส่งเมลลูกค้า ผมเข้าไปดูเอง
เคสนี้สำคัญสุดในทั้ง 3 ชั้น เพราะมันคือการ "หยุดส่ง notification ของ outcome ที่ดู ready แต่จริงๆ ยังไม่ ready"
ชั้นที่ 3 — ห้าม default zone อีกตลอดไป
ระหว่าง audit ทิมเจอ bug ลึกอีกตัว — Newton ของผมรันทั้ง TH (incomeinclick.in.th) และ EN (incomeinclick.com) จาก codebase เดียว แต่ Cloudflare zones คนละตัวกัน TH ใช้ zone นึง EN ใช้ zone นึง
helper function cloudflare_api() ของระบบเดิม default zone_id ไป COM zone เสมอ — ถ้า caller ลืมส่ง zone_id ในเคส TH คำสั่ง DNS ก็ยิงเข้าผิด zone เงียบสนิท Cloudflare ส่ง 200 OK กลับมาด้วยซ้ำ (เพราะ authentication ผ่าน) แต่ record ไปอยู่ใน zone ผิด TH instance ก็เลย "เหมือนสร้าง record แล้ว" แต่ไม่มีอะไรเกิดขึ้นจริงในโดเมน .in.th
ทิม refactor บังคับให้ทุก call ที่เกี่ยวข้องกับ customer subdomain ต้องส่ง zone_id=CF_ZONE_SUBDOMAIN เข้ามาตรงๆ — default ที่เคยมี ลบทิ้ง ใครลืมส่งจะ raise TypeError ตั้งแต่ลอด unit test ไม่ทันได้ deploy
เครื่องมือ sanity audit สำหรับครั้งหน้า
หลัง fix เสร็จ ทิมเขียน script เก็บไว้ — รันทีไหร่ก็ได้ มันจะ:
- เปิดตาราง
serversทั้ง TH + EN database - ดึง A record ทั้งหมดของ
newton-*ผ่าน Cloudflare API ทั้ง 2 zones - เทียบ — record ตัวไหนใน Cloudflare ที่ database ไม่มี server active → flag ว่า "stale"
- record ตัวไหนชี้ไป IP ที่ไม่ตรงกับ
servers.ipใน DB → flag "mismatch"
ผมรันเดือนละครั้งก็พอ — เป็น net safety สุดท้ายเผื่อมี edge case ที่ยังคิดไม่ถึง
ของแถม — ทิมจะไม่ลบ record jarvis-* หรือ newton-* ที่ไม่อยู่ใน DB แบบ blind ครับ บางตัวเป็น customer เก่าสมัย rebrand จาก Jarvis → Newton ที่ผมยัง active server อยู่ใต้ชื่อ jarvis-* ทิมจึงเช็ค field servers.domain ก่อน จะลบเฉพาะตัวที่ลูกค้าไม่มี active row จริงๆ
บทเรียน — "verify before notify"
เรื่องนี้ผมรู้สึกขนลุกตอนคุยกับทิมจบครับ — มันคือ pattern เดียวกันที่ Documentor พังเงียบ 3 วัน เพียงแต่อยู่คนละชั้นของระบบ
Pattern เดียวกัน: "ทำงานเสร็จไหม? ดูเหมือนเสร็จ ส่ง notification ออกได้เลย" → ของจริงคือยังพังอยู่ แต่เพราะไม่มีใคร verify ก็ไม่มีใครรู้
ทิมเลยใส่ verification step เข้า pipeline ของ Newton ทุกที่ที่มี side-effect ออกไปหา customer — provision, payment grace, trial expiry, password reset — เด็จขาดทุกตัวต้อง "verify before notify" ห้าม "fire-and-forget" เด็ดขาด แล้วเขียน memory ของตัวเองไว้ว่ารอบหน้าถ้าผมขอ feature ที่มี customer-facing email ออก ต้องเตือนผมก่อนเสมอว่า "verify step อยู่ไหน"
(อีก failure mode สายเงียบที่ผมเพิ่งเจอเมื่อสัปดาห์ก่อน — ความทรงจำของทิมเองที่โดน sandbox block 3 วันแบบไม่มี error เด้ง คนละ subsystem เดียวกัน เลย failure mode)
คุณก็ต้องการ AI Agent ที่ verify ของตัวเองได้
เรื่องนี้ฟังดูเหมือนเรื่องเฉพาะของ DevOps แต่ปัญหาแบบนี้มันเป็น failure mode ทั่วๆ ไปของระบบ automation ทุกแห่ง — script รันเสร็จ exit 0 ไม่ใช่หลักฐานว่า outcome เกิดขึ้นจริง ทุกระบบที่ส่ง email หาลูกค้าหลัง automation จบ มีโอกาสเจอเรื่องแบบนี้
การจะทำให้ระบบของคุณ self-verify ได้ ต้องการ AI Agent ที่ เข้าถึงระบบของคุณได้จริง — ไม่ใช่แค่ ChatGPT ที่ตอบเรื่อง infrastructure ทั่วๆ ไป มันต้องอยู่ใน server ของคุณ มี SSH key ของคุณ มี API token ของคุณ และมี ความทรงจำเรื่องที่ผ่านมาในระบบของคุณ
คำถามที่พบบ่อย
DNS collision คืออะไร และเกิดขึ้นได้ยังไง?
DNS collision คือเมื่อมี A record ชื่อเดียวกันหลายตัวใน DNS zone ครับ Cloudflare ยอมให้มีหลาย A record ชื่อเดียวกันได้เพื่อทำ load balance แต่ถ้าตั้งใจมีแค่ตัวเดียว record เก่าที่ค้างอยู่จะทำให้ DNS resolver round-robin ระหว่าง IP เก่าและใหม่ ทำให้เข้าเว็บได้บ้างไม่ได้บ้าง
ทำไม HTTPS ถึงพังเมื่อมี DNS record ซ้อนกัน?
เพราะ SSL certificate ต้องผ่าน HTTP-01 challenge ครับ Let's Encrypt จะส่ง request มาที่ IP ของ domain นั้น แต่ถ้า DNS round-robin ไปถูก IP เก่าที่ตายแล้ว challenge พัง ใบ cert ไม่ออก เลยเจอ SSL error แม้ว่า server ใหม่จะปกติดี
pattern "verify before notify" คืออะไร และทำไมถึงสำคัญ?
หมายถึงการตรวจสอบว่า outcome เกิดขึ้นจริงก่อนส่ง notification ออกไปครับ script รัน exit 0 ไม่ใช่หลักฐานว่าของพร้อมใช้แล้ว ต้องทดสอบจริงว่า HTTPS ขึ้นไหม DNS ชี้ถูกไหม แล้วค่อยส่ง email แจ้งลูกค้า ป้องกันปัญหา "บอกว่าพร้อมแต่จริงๆ ยังพัง"
วิธีป้องกัน DNS record ซ้อนกันตอน re-signup ลูกค้าทำยังไง?
ก่อน POST A record ใหม่ ให้ GET รายการ record ที่ชื่อเดียวกันก่อน ถ้ามีอยู่แล้วให้ DELETE ทิ้งก่อนทุกตัว แล้วค่อย POST ใหม่ครับ วิธีนี้เรียกว่า idempotent รันซ้ำกี่รอบก็เหลือ record เดียวที่ชี้ IP ล่าสุดเสมอ
ผมสร้าง Newton ขึ้นมาเพื่อให้คุณมีสิ่งนี้ — AI Agent ส่วนตัวบน server ของคุณคนเดียว เจอ failure mode ก็แก้ให้ และจำว่าครั้งหน้าต้องใส่ "verify before notify" ตรงไหนบ้าง ระบบ provision อัตโนมัติของผม จะ setup Newton ให้คุณภายใน 10 นาที — ทดลองได้ฟรี 7 วัน ไม่ต้องเขียนโค้ดเองสักบรรทัด
