เราทำให้ deploy เหลือข้อผิดพลาด 502 เป็นศูนย์ได้อย่างไร
ทุกครั้งที่เรา deploy แบ็กเอนด์ของ Mythex จะมีคำขอไม่กี่รายการล้มเหลวด้วย 502 Bad Gateway ไม่มากนัก แต่ 502 ระหว่าง deploy หมายความว่าการคลิกของใครบางคนไม่ได้ผล หรือ build ที่เขาดูอยู่หยุดไปชั่วครู่ เราสืบจนพบสองปัญหาแยกกัน: watchdog ที่ซ่อมตัวเองซึ่งต่อสู้กับการ deploy และช่องว่างระหว่างเวลาที่เซิร์ฟเวอร์หยุดรับฟังกับเวลาที่ load balancer หยุดส่งทราฟฟิกไปให้ หลังการแก้ไข จำนวนติดต่อกันในสามการ deploy production ลดจาก 10 เป็น 6 และเป็น 0
การตั้งค่า
API และ orchestrator ของเรารันเป็น container บนกลุ่มเซิร์ฟเวอร์ขนาดเล็กหลัง Application Load Balancer (ALB) ของ AWS การ deploy อัปเดตเซิร์ฟเวอร์ทีละเครื่อง: มันระบาย container เก่า เริ่ม container ใหม่ แล้วไปเครื่องถัดไป watchdog บนแต่ละเซิร์ฟเวอร์ตรวจว่า container ทำงานอยู่ และรีสตาร์ตตัวที่ไม่ทำงาน มันมีไว้เพื่อให้โปรเซสที่ crash ตอนตีสามกลับมาทำงานได้โดยไม่มีใครต้องตื่นมา
ทั้งสองอย่างสมเหตุสมผลเมื่อแยกกันดู แต่เมื่อรวมกันแล้วไม่สมเหตุสมผล
ปัญหาที่ 1: watchdog แข่งกับการ deploy
watchdog ทำงานทุกนาทีและ "ซ่อม" เมื่อ health check ล้มเหลวหนึ่งครั้ง การ deploy จงใจระบายบริการนานถึง 150 วินาที ซึ่งระหว่างนั้น health check ควรจะล้มเหลว ดังนั้น watchdog จึงลงมือทุกครั้งที่มีการ deploy
ส่วนใหญ่มันแพ้การแข่งขันอย่างไม่เป็นอันตราย ครั้งหนึ่งมันชนะ: มันเขียนไฟล์ compose (คำอธิบายว่า container ใดควรรัน) ทับกลางการ deploy และทิ้งเซิร์ฟเวอร์ไว้โดยไม่มี container gateway เลย ขณะที่งาน deploy รายงานว่าสำเร็จ
การแก้ไข:
- ล็อกเดียว ตอนนี้การ deploy ใช้ file lock เดียวกับสคริปต์ bootstrap ของเซิร์ฟเวอร์ ดังนั้น watchdog และการ deploy จะไม่รันพร้อมกันได้เลย
- สี่ครั้ง ไม่ใช่หนึ่ง watchdog ต้องมี health check ที่ล้มเหลวติดต่อกันสี่ครั้งก่อนจะลงมือ บริการที่กำลังระบายจะไม่ดูเหมือนตายอีกต่อไป
- นิยามเดียว ไฟล์ compose ตอนนี้มีแหล่งเดียว เผยแพร่ไปยังเซิร์ฟเวอร์ แทนที่จะเป็นสำเนาที่อาจเคลื่อนไปคนละทาง
- ตรวจสอบ ไม่ใช่เชื่อ งาน deploy จะไม่ผ่านเพียงเพราะคำสั่งคืนค่าออกมา มันตรวจ API, orchestrator, gateway และ publish worker ของทุกเซิร์ฟเวอร์ก่อนจะสำเร็จได้
ปัญหาที่ 2: เซิร์ฟเวอร์ออกไปก่อนทราฟฟิก
ปัญหาที่สองเงียบกว่า เมื่อ deploy ระบายเซิร์ฟเวอร์ เซิร์ฟเวอร์จะเริ่มตอบ health endpoint ของมันด้วย 503 เพื่อให้ load balancer นำมันออกจากการหมุนเวียน ของเราทำอย่างนั้นเป็นเวลา 5 วินาที แล้วจึงปิดตัว
แต่ ALB ตรวจ health เป็นครั้งคราวเท่านั้น และต้องการการตรวจที่ล้มเหลวติดต่อกันหลายครั้งก่อนจะหยุดส่งเส้นทางไปยังเป้าหมาย รวมแล้วนานถึงราว 60 วินาที ดังนั้นประมาณ 55 วินาที load balancer ยังส่งคำขอจริงไปยังเซิร์ฟเวอร์ที่หยุดรับฟังไปแล้ว นั่นคือ 502 ของเรา
การแก้ไขคือทำให้สองเส้นเวลาตรงกัน:
- target group ตอนนี้ตรวจทุก 5 วินาที และทำเครื่องหมาย target ว่าไม่พร้อมใช้งานหลังล้มเหลว 2 ครั้ง ซึ่งใช้เวลาราว 10 วินาทีในการรู้ตัว
- การระบายยังให้บริการทราฟฟิกจริงต่ออีก 15 วินาที หลังจากเริ่มล้มเหลวการตรวจ health ดังนั้นจะมีการซ้อนทับเสมอ
- การตั้งค่าเหล่านี้อยู่ในสคริปต์ที่จัดสรรโครงสร้างพื้นฐาน ไม่ใช่ในคอนโซลที่มีคนคลิกครั้งเดียว
ทำให้ตัวเลขยืนยันกันเอง
ค่า timeout แบบนี้ขึ้นอยู่กับกันและกัน การระบายต้องนานกว่าเวลาที่ balancer ใช้ในการรู้ตัว การระบายบวกการปิดตัวต้องเสร็จก่อนที่ container จะถูกฆ่าแบบบังคับ มันง่ายที่ใครสักคนจะเปลี่ยนตัวเลขหนึ่งตัวในหกเดือนต่อมาและนำบั๊กกลับมาโดยไม่รู้ตัว
ดังนั้นเราจึงเขียนความสัมพันธ์เหล่านั้นเป็น test: การระบายต้องยาวกว่าช่วงเวลาตรวจ health คูณด้วยจำนวนครั้งที่ล้มเหลวเพื่อตัดสิน เวลาที่จะกลับมาถูกมองว่าปกติต้องสั้น และการระบายทั้งหมดต้องพอดีกับ stop grace period ของ container เปลี่ยนตัวเลขหนึ่งตัวโดยไม่เปลี่ยนตัวอื่น แล้ว CI จะล้มเหลว เราทดสอบ mutation ตัว test เองด้วย โดยเปลี่ยนแต่ละตัวเลขเป็นค่าผิดและตรวจว่ามี test จับได้
การวัดการ deploy ทำผิดได้ง่าย
การวัดครั้งแรกของเราบอกว่า deploy ที่สะอาดเสียหายราว 3% มันไม่ได้เป็นอย่างนั้น เรากำลังนับทุกการตอบกลับที่ไม่ใช่ 200 และ 503 จาก health endpoint ระหว่างการระบายไม่ใช่ความล้มเหลว มันคือการระบายที่ทำหน้าที่ของมัน บอก balancer ว่าอย่าเลือกเซิร์ฟเวอร์นั้นในขณะที่มันยังให้บริการคำขอจริงต่อ
สิ่งที่ผู้ใช้รู้สึกคือ 502: คำขอไปถึงสิ่งที่ไม่มีอะไร การนับเฉพาะตรงนั้นให้ภาพที่ถูกต้อง: 10, แล้ว 6, แล้ว 0 ในสาม deploy production
สิ่งที่เราเรียนรู้
- การซ่อมตัวเองต้องรู้เกี่ยวกับการหยุดทำงานที่วางแผนไว้ watchdog ที่แยกการ deploy ออกจากการ crash ไม่ได้ จะต่อสู้กับการ deploy ในที่สุด
- การระบายคือการจับมือ เซิร์ฟเวอร์ต้องให้บริการต่อจนกว่า load balancer จะหยุดส่งทราฟฟิกอย่างพิสูจน์ได้ และการกำหนดเวลานั้นถูกตั้งบน balancer ไม่ใช่บนเซิร์ฟเวอร์
- เข้ารหัสข้อกำหนดเป็น test ตัวเลขเชิงปฏิบัติการเคลื่อนไปเรื่อยๆ test ที่เกี่ยวโยงตัวเลขเข้าด้วยกันไม่เคลื่อน
- ตัดสินใจว่าคุณกำลังวัดอะไรก่อนวัด "ข้อผิดพลาด" เป็นตัววัดที่ผิด "คำขอที่ไปถึงสิ่งที่ไม่มีอะไร" คือตัววัดที่ถูกต้อง
หากคุณเผยแพร่แอปบน Mythex คุณจะไม่เห็นอะไรในเรื่องนี้เลย ซึ่งคือจุดประสงค์ เกี่ยวข้อง: วิธีที่แอปที่เผยแพร่ของคุณยังออนไลน์อยู่ระหว่างที่คุณเผยแพร่ซ้ำ