การขยาย Kubernetes ให้รองรับการใช้ CRD หลายพันรายการ
(blog.upbound.io)Crossplane ที่สร้างโดย Upbound มอบ cloud control plane บน Kubernetes ดังนั้นเพื่อรองรับทรัพยากรคลาวด์อย่าง AWS, Azure, GCP จึงมี CRD (Custom Resource Definition) อยู่หลายร้อยรายการ โดยใน Crossplane เรียกสิ่งเหล่านี้ว่า MR (Managed Resource)
แม้จะเป็นผู้ใช้ Kubernetes ระดับสูง โดยทั่วไปก็มักดูแล CR จำนวนที่เหมาะสมเพียงหลักสิบ แต่ใน Crossplane จำเป็นต้องใช้ MR หลายร้อยรายการ จึงเริ่มสำรวจข้อจำกัดว่า Kubernetes สามารถจัดการ CRD ได้มากเพียงใด
ปัญหานี้สามารถแยกดูได้เป็นปัญหาฝั่งไคลเอนต์และปัญหาฝั่งเซิร์ฟเวอร์
ปัญหาฝั่งไคลเอนต์
- ฝั่งไคลเอนต์ ปัญหาอยู่ที่กระบวนการ discovery
- ไคลเอนต์อย่าง kubectl ต้องทำ discovery เพื่อค้นหาว่าเซิร์ฟเวอร์มี API อะไรให้ใช้งานบ้าง ซึ่งต้องไล่ดู API endpoint ทั้งหมดหนึ่งรอบ
- CR ถูกให้บริการในรูปแบบ API endpoint
- หากต้องการดู MR อย่าง
https://example.org/apis/rds.aws.upbound.io/v1/instances/cool-dbต้องค้นหา API group ที่รองรับจากhttps://example.org/apis/จากนั้นค้นหาเวอร์ชันที่รองรับจากhttps://example.org/apis/rds.aws.upbound.ioและค้นหา CR ที่รองรับจากhttps://example.org/apis/rds.aws.upbound.io/v1 - Crossplane MR ที่ให้บริการ cloud provider อย่าง AWS, Azure, GCP มีประมาณ 2,000 รายการ และแบ่งออกเป็น 300 API group และเวอร์ชัน
- ไคลเอนต์จึงต้องส่ง HTTP request 300 ครั้งเพื่อทำ discovery
- ในสภาพเครือข่ายปัจจุบันนี่ไม่ใช่ปัญหาใหญ่ แต่ปัญหาที่พบคือ rate limit และแคช
Rate limit ฝั่งไคลเอนต์
- มีการตั้ง rate limit ไว้ที่เฉลี่ย 5 requests ต่อวินาที (burst ได้ถึง 100) และล้าง discovery cache ทุก 10 นาที
- เรื่องนี้แก้ได้ด้วยการเพิ่ม rate limit ซึ่งตอนนี้แม้ค่าเริ่มต้นจะยังเป็น 5 requests ต่อวินาที แต่สามารถเพิ่มได้ถึง 300 แล้ว
- มีการเปิด issue ขอเพิ่มขีดจำกัดนี้ใน kubectl v1.22 และ discovery cache ก็ถูกปรับจาก 10 นาทีเป็น 5 ชั่วโมง ทำให้ใน Kubernetes v1.25 สามารถใช้ขีดจำกัดฝั่งไคลเอนต์ที่เพิ่มขึ้นได้
แคชฝั่งไคลเอนต์
- แม้จะปิด rate limit แล้วทดสอบ การดึงข้อมูล 300 API group ก็ยังใช้เวลาเกือบ 20 วินาที
- ตอนแรกคิดว่าเป็นปัญหาเครือข่าย แต่เมื่อวิเคราะห์แล้วพบว่าเกิดจากการอ่านไฟล์แคช
- มีการแก้ไขใน Kubernetes 1.25 ทำให้บน macOS เร็วขึ้น 25 เท่า และบน Linux เร็วขึ้น 2 เท่า
การปรับปรุงฝั่งไคลเอนต์ในอนาคต
- การใส่ rate limit ฝั่งไคลเอนต์ถือว่าสมเหตุสมผล แต่จริง ๆ แล้วไม่ได้ช่วยปกป้องเซิร์ฟเวอร์ได้ดีนัก
- API Priority and Fairness (AP&F) ที่ถูกนำมาใช้ใน Kubernetes 1.20 มอบ queue และ traffic shaping ฝั่งเซิร์ฟเวอร์เพื่อปกป้อง API server
- มี KEP ที่ได้รับอนุมัติสำหรับ aggregated HTTP endpoint เดียวเพื่อใช้ในการ discovery และคาดว่าจะรองรับแบบ alpha ใน 1.26
ปัญหาฝั่งเซิร์ฟเวอร์
การคำนวณ OpenAPI schema
- หลังจากลงทะเบียน CRD หลายร้อยรายการ พบว่า API requests ช้ามากอยู่เกือบหนึ่งชั่วโมง
- จากการ profiling พบว่าปัญหานี้เกิดจาก logic ที่ใช้คำนวณ OpenAPI v2 schema
- เมื่อมีการเพิ่มหรืออัปเดต CRD, OpenAPI controller จะสร้าง swagger spec ของ CR แล้วรวม swagger ของ CR ทั้งหมดเป็นสเปกขนาดใหญ่หนึ่งชุด จากนั้น serialize เป็น JSON และเปิดให้บริการที่
/openapi/v2 - จึงแก้ไขให้
/openapi/v2คำนวณแบบ lazy และให้คำนวณเมื่อมีการร้องขอ endpoint ของ CR จริง ๆ - การแก้ไขนี้ถูกรวมใน v1.24.0 และ backport ไปยัง 1.20.13, 1.21.7, 1.22.4
ไคลเอนต์ etcd
- นี่คือคอขวดใหม่ที่พบหลังจากแก้ปัญหา OpenAPI แล้ว
- พบว่า API server ใช้หน่วยความจำ 4MiB ต่อ CRD หนึ่งรายการ
- เรื่องนี้เป็นปัญหามากขึ้นใน managed Kubernetes อย่าง GKE, EKS เพราะมีการจำกัด CPU และหน่วยความจำของ API server หากต้องการทรัพยากรเพิ่ม ระบบจะขยาย API server ให้อัตโนมัติ แต่โชคร้ายที่การเพิ่ม CRD ไม่ใช่ปัจจัยที่ใช้ตัดสินใจในการขยาย ดังนั้นหาก API server ยังไม่ถูก OOM killed ซ้ำ ๆ ก็จะไม่ขยาย
- เมื่อทดสอบบน GKE, AKE, EKS พบว่าระบบ auto-healing ทำงาน แต่ API server ใช้งานไม่ได้ตั้งแต่ 5 วินาทีถึง 1 ชั่วโมง แม้คลัสเตอร์จะไม่ได้หยุดทั้งระบบ แต่การ reconciliation ทั้งหมดหยุดลง
- จากการ profiling พบว่า Zap ซึ่งเป็น logging library ใช้หน่วยความจำถึง 20%
- API server จะสร้างไคลเอนต์ etcd หนึ่งตัวต่อหนึ่งเวอร์ชันของ CR และแต่ละไคลเอนต์ etcd ก็จะสร้าง Zap logger ของตัวเอง
- ผลคือไม่เพียงแต่หน่วยความจำเพิ่มขึ้นจาก logger ที่ซ้ำซ้อน แต่ยังเกิด TCP connection ที่ไม่จำเป็นระหว่าง API server กับ etcd ด้วย
- ผู้ดูแลโครงการก็เห็นตรงกันว่าควรใช้ไคลเอนต์ etcd ตัวเดียวสำหรับทุก CR endpoint แต่เนื่องจากใกล้ออกรุ่น Kubernetes 1.25 แล้ว จึงแก้ทั้งหมดได้ยาก เลยทำการปรับลดขอบเขตก่อนโดยให้ไคลเอนต์ etcd ทั้งหมดแชร์ logger ตัวเดียวกัน
- การแก้ไขนี้คาดว่าจะรวมใน 1.25 และจะ backport ไปยัง 1.22, 1.23, 1.24 ซึ่งจะช่วยลดการใช้หน่วยความจำลง 20%
การปรับปรุงฝั่งเซิร์ฟเวอร์ในอนาคต
- มีแผนจะเปลี่ยนจากการสร้างไคลเอนต์ etcd ต่อหนึ่งเวอร์ชันของ CR ไปเป็นสร้างหนึ่งตัวต่อ transport (ต่อหนึ่ง etcd cluster)
- กำลังร่วมมือกับทีมวิศวกรรมของ GKE, EKS, AKE เพื่อรองรับการติดตั้ง Crossplane CRD จำนวนมาก
2 ความคิดเห็น
ทำให้ใช้ฟรี -> ทำให้เป็นโมฆะ
ไคลอิง eonteu -> ไคลเอนต์