การ Republish ที่ล้มเหลวจะไม่ลบ Backend ที่ยังทำงานอยู่
เมื่อวันที่ 25 กันยายน การ republish ที่ล้มเหลวได้ลบ backend ของแอปตัวหนึ่งที่ทำงานอยู่และให้บริการได้ปกติมาประมาณสองชั่วโมงครึ่ง โค้ดที่ใช้ยกเลิก publish ที่ล้มเหลวได้ลบทุกเซิร์ฟเวอร์แอปที่ publish นั้นแตะต้อง รวมถึงแอปที่กำลังให้บริการอยู่ก่อนที่ publish จะเริ่ม ตอนนี้จะลบเฉพาะแอปที่ publish นั้นสร้างขึ้นใหม่เท่านั้น ส่วนแอปที่มีอยู่แล้วจะถูกคืนกลับไปยังเวอร์ชันเดิมที่รันอยู่ก่อนหน้า การแก้ไขเพิ่มเติมอีกสองรายการในวันเดียวกันช่วยให้การยกเลิกนั้นทำงานแม้เครื่องมือ deploy ของแพลตฟอร์มโฮสติ้งจะล้มเหลวเอง และช่วยไม่ให้แอปที่ปกติดีถูกตัดสินว่าล้มเหลวเพียงเพราะกำลังหลับอยู่
Backend ถูก publish อย่างไร
เมื่อแอป Mythex ของคุณมี backend (คือ API หรือเซิร์ฟเวอร์ ไม่ใช่แค่ไฟล์สแตติก) การ publish จะ build จาก Dockerfile (สูตรสำหรับแพ็กแอปเป็น image) แล้วนำ image นั้นไปติดตั้งบนเครื่องของแพลตฟอร์มโฮสติ้ง จากนั้น health check จะรอจนเวอร์ชันใหม่ทำงานขึ้นมาและทำงานต่อเนื่องได้จริง ก่อนที่การ publish จะถูกทำเครื่องหมายว่าใช้งานได้
หากมีอะไรล้มเหลวระหว่างทาง rollback จะทำงานเพื่อย้อนสิ่งที่ทำไปแล้ว การทำ rollback ให้ถูกต้องสำคัญกว่าเกือบทุกอย่างในการ publish เพราะมันทำงานในช่วงที่มีบางอย่างผิดพลาดอยู่แล้วพอดี
ปัญหาที่ 1: rollback ลบสิ่งที่ควรจะกู้คืน
Rollback เก็บรายการเซิร์ฟเวอร์แอปที่ publish นั้นติดตั้งไปยัง และเมื่อเกิดความล้มเหลว ก็จะทำลายทุกรายการ สำหรับการ publish ครั้งแรกนั้นถูกต้อง เพราะแอปเพิ่งถูกสร้างขึ้นเมื่อสักครู่ ไม่มีอะไรพึ่งพาอยู่ และการลบจะทำให้ทุกอย่างกลับไปเหมือนเดิม
แต่สำหรับการ republish มันผิดอย่างสิ้นเชิง แอปมีอยู่แล้วและกำลังให้บริการผู้เข้าชมจริง เมื่อวันที่ 25 กันยายน API ของแอปตัวหนึ่งที่ทำงานมาประมาณสองชั่วโมงครึ่งถูกลบโดยการ republish ที่ล้มเหลว
มีความล้มเหลวรูปแบบที่สองซ่อนอยู่ในโค้ดส่วนเดียวกัน หากการลบไม่สำเร็จ image ใหม่ที่พังจะยังคงทำงานอยู่บนเครื่อง ในขณะที่เราแจ้งเจ้าของว่าเว็บไซต์ปัจจุบันยังใช้งานได้อยู่ เมื่อ health check ล้มเหลวได้ image ใหม่ก็ถูกติดตั้งไปยังทุกเครื่องแล้ว การไม่ทำอะไรเลยไม่เท่ากับการยกเลิก
วิธีแก้: ลบเฉพาะสิ่งที่สร้างขึ้น และกู้คืนส่วนที่เหลือ
ตอนนี้ทุกการ publish จะบันทึกข้อมูลของเซิร์ฟเวอร์แอปแต่ละตัวที่แตะต้อง ได้แก่:
- การ publish นี้ สร้างแอปนี้ขึ้นมาใหม่หรือไม่
- ถ้าไม่ได้สร้าง แอปนั้น กำลังรัน image ใด ก่อนหน้านี้
เมื่อเกิดความล้มเหลว แต่ละแอปจะได้รับการจัดการหนึ่งในสามแบบ:
- สร้างโดย publish นี้: ลบทิ้ง เหมือนเดิม
- มีอยู่แล้ว และรู้ image ก่อนหน้า: deploy image ก่อนหน้ากลับไปด้วยการตั้งค่าเดิม ไม่มีการ build มีเพียง image ที่ย้อนกลับไป
- มีอยู่แล้ว แต่ไม่รู้ image ก่อนหน้า: ปล่อยไว้ตามเดิม แอปที่มีอยู่จะไม่ถูกลบเลย แม้ว่าเราจะกู้คืนไม่ได้ก็ตาม
กฎเหล่านี้เป็นฟังก์ชันเล็กๆ ที่ไม่มีผลข้างเคียง และมี test ของตัวเอง ทำให้การตัดสินใจไม่กลับไปเป็น "ลบทุกอย่าง" โดยไม่มี test ล้มเหลวให้เห็น
ปัญหาที่ 2: เครื่องมือ deploy อาจล้มเหลวก่อนที่เราจะบันทึกวิธียกเลิก
ไม่นานหลังการแก้ไขนั้น เราพบช่องว่างในการแก้ไขนั้น เครื่องมือ deploy ของแพลตฟอร์มโฮสติ้งมี smoke check ของตัวเอง หากเครื่องใหม่ขัดข้อง มันจะจบด้วย error แต่มันทำเช่นนั้น หลังจาก image ใหม่ถูกติดตั้งบนเครื่องแล้ว
โค้ดของเราจะลงทะเบียนแอปไว้สำหรับ rollback ก็ต่อเมื่อเครื่องมือส่งค่ากลับมาว่าสำเร็จ เมื่อเครื่องมือเองทำให้ deploy ล้มเหลว แอปยังไม่อยู่ในรายการ rollback จึงไม่มีการกู้คืนอะไรเลย ในสภาพแวดล้อม staging ของเรา การ republish ที่พังทำให้ image ที่พังยังคงให้บริการอยู่
วิธีแก้: ลงทะเบียนวิธียกเลิกก่อนเริ่มทำงาน
ตอนนี้แอปจะถูกลงทะเบียนสำหรับ rollback ทันทีที่เตรียมพร้อม และก่อนที่จะติดตั้งอะไรลงไป โดยบันทึกว่าแอปถูกสร้างขึ้นหรือไม่และรัน image อะไรอยู่ ไม่ว่าอะไรจะล้มเหลวหลังจากนั้น rollback ก็รู้ว่าต้องกู้คืนอะไร เราตรวจสอบในเครื่องแล้ว: การ republish ที่พังล้มเหลวจาก smoke check ของเครื่องมือ deploy เอง และเวอร์ชันก่อนหน้ายังคงตอบสนองต่อไป
ปัญหาที่ 3: แอปที่กำลังหลับถูกตัดสินว่า "ไม่เคยเริ่มทำงาน"
แอปที่ publish บน Mythex จะหลับเมื่อไม่มีการใช้งานเพื่อประหยัดเครดิตของคุณ การ republish จะทิ้งเครื่องที่หลับอยู่ให้หยุดทำงาน ดังนั้น health check จึงต้องปลุกมันขึ้นมา ซึ่งมันขอให้แพลตฟอร์มเริ่มเครื่อง เพียงครั้งเดียว และไม่สนใจคำตอบที่ได้รับ
ในวันที่ 25 กันยายน คำขอเริ่มทำงานครั้งเดียวนั้นไม่ได้ผล API ที่ทำงานปกติถูกทิ้งให้หยุดอยู่ตลอดช่วง health check ห้านาทีเต็ม และถูกตัดสินว่าล้มเหลวด้วยข้อความ "1 machine never started (last state: stopped)"
แพลตฟอร์มบางครั้งก็ปฏิเสธการเริ่มทำงาน ระหว่างที่เรากำลังทำซ้ำปัญหานี้ แพลตฟอร์มตอบกลับว่ามี conflict "lease currently held" โดยที่เราไม่ได้กระตุ้นให้เกิดขึ้น และแพลตฟอร์มจะปฏิเสธการเริ่มทำงานระหว่างที่เครื่องกำลังถูกอัปเดต
วิธีแก้: ถามซ้ำ และเคาะประตู
ตอนนี้ health check จะ:
- ถามซ้ำให้เริ่มเครื่องที่หยุดอยู่ ทุก 20 วินาที ระหว่างที่รอ
- บันทึกว่าแพลตฟอร์มตอบอะไรกลับมาเมื่อมันปฏิเสธ
- ส่งคำขอปกติหนึ่งครั้งไปยังแอป เหมือนผู้เข้าชมทั่วไป พร็อกซีของแพลตฟอร์มจะปลุกเครื่องที่หลับเมื่อมีคำขอเข้ามา
เราทำซ้ำปัญหานี้บนแพลตฟอร์มจริงด้วยแอปทดลองที่การเริ่มทำงานครั้งแรกถูกปฏิเสธ ก่อนการแก้ไข การ publish ล้มเหลวหลังผ่านไป 91 วินาที หลังการแก้ไข มันเผยแพร่สำเร็จภายใน 20 วินาที แอปที่ขัดข้องจริงตอนบูตก็ยังคงล้มเหลวตามที่ควรเป็น
สิ่งที่เราได้เรียนรู้
- Rollback ต้องรู้ความต่างระหว่างของที่สร้างใหม่กับของที่มีอยู่แล้ว "ยกเลิก" สำหรับสิ่งที่คุณสร้างคือการลบ "ยกเลิก" สำหรับสิ่งที่มีอยู่แล้วคือการกู้คืน การปฏิบัติกับทั้งสองอย่างเหมือนกันคือสาเหตุที่กลไกความปลอดภัยไปลบแอปที่ทำงานปกติ
- เขียนวิธียกเลิกไว้ก่อนลงมือทำ หากการยกเลิกถูกบันทึกหลังจากขั้นตอนหนึ่งสำเร็จ ขั้นตอนที่ล้มเหลวกลางคันจะไม่มีอะไรให้ยกเลิก
- อย่าเพิกเฉยต่อคำตอบของคำขอที่คุณต้องพึ่งพา คำตอบ "ไม่" ที่ถูกมองข้ามไปเพียงครั้งเดียวทำให้แอปที่ปกติดีกลายเป็นการ publish ที่ล้มเหลว
การแก้ไขที่เกี่ยวข้องจากวันเดียวกัน: การจับแอปที่ขัดข้องทันทีหลัง publish สำหรับสิ่งที่เกิดขึ้นเมื่อการ publish ล้มเหลวฝั่งคุณ ดู การแก้ปัญหาการ publish ในเอกสาร