لا يلغي Dynamic Resource Allocation، أو DRA، في Kubernetes مشروع HAMi لمشاركة وحدات معالجة الرسومات، لكنه يغيّر توزيع الأدوار بينهما. فبعد وصول DRA إلى التوافر العام في Kubernetes v1.34 وتفعيله افتراضياً منذ v1.35، أصبح Kubernetes قادراً على فهم طلبات الحصص الجزئية من موارد الجهاز وجدولتها بصورة أصلية. أما فرض هذه الحصص داخل الحاوية عند استدعاءات CUDA، فليس من مهام DRA، وهنا يبقى دور HAMi-core أساسياً.
يقدم مقال CNCF، الذي كتبه Mesut Oezdil، قراءة تحليلية للفارق بين المرحلتين، ويشرح كيف يعيد HAMi بناء جزء من منظومته فوق DRA بدلاً من التخلي عن المشروع. ويشير الكاتب إلى أن السؤال ليس ما إذا كان أحد المشروعين سيحل محل الآخر، بل أي وظيفة من وظائف HAMi أصبحت مغطاة بقدرات Kubernetes الأصلية.
لماذا احتاجت مشاركة GPU إلى حلول خاصة؟
كانت واجهة Device Plugin في Kubernetes قادرة أساساً على عدّ الأجهزة. وكان الطلب التقليدي، مثل nvidia.com/gpu: 1، يعني حجز بطاقة كاملة، من دون لغة أصلية لطلب 8,000 ميجابايت من ذاكرة بطاقة أو 10% من قدرتها الحسابية.
للتعامل مع ذلك، يستخدم HAMi موارد ممتدة مثل nvidia.com/gpumem وnvidia.com/gpucores. لكن المجدول الافتراضي في Kubernetes يتعامل مع هذه القيم كأعداد غامضة؛ فهو لا يعرف أن الذاكرة والقدرة الحسابية يجب أن تأتي من البطاقة الفيزيائية نفسها، ولا يستطيع وحده تحديد ما إذا كانت حصص متعددة ستتجاوز سعة بطاقة معينة.
لذلك يعتمد المسار التقليدي على webhook لتعديل الطلب، وامتداد خاص بالمجدول يرشح العقد ويختار معرف الجهاز، ثم يسجل القرار في annotation. ويقرأ Device Plugin هذا القرار لاحقاً ليحقن حدوداً مثل CUDA_DEVICE_MEMORY_LIMIT_0=8000m وCUDA_DEVICE_SM_LIMIT=10، إلى جانب تحميل مكتبة libvgpu.so مسبقاً لفرض الحدود.
وبحسب المقال، شغّل DaoCloud أكثر من 10,000 وحدة GPU عبر أكثر من 10 مراكز بيانات باستخدام هذا النهج. إلا أن بنيته تظل مرتبطة بصيغة annotations خاصة ومكونات يفهمها HAMi، وهي الفجوة التي صُمم DRA لمعالجتها.
ما الذي يضيفه DRA؟
يستبدل DRA نموذج عدّ الأجهزة بنموذج المطالبات، مع أربعة كائنات رئيسية في واجهة resource.k8s.io/v1:
- ResourceSlice: ينشره مشغل الجهاز ويصف العتاد الفعلي لكل عقدة، بما في ذلك الطراز والذاكرة والبنية.
- DeviceClass: يعرّف فئات الأجهزة ومرشحاتها باستخدام تعبيرات CEL.
- ResourceClaim: ينشئه مالك حمل العمل لطلب جهاز وفق الفئة والمحددات والقيود.
- ResourceClaimTemplate: ينشئ مطالبة مستقلة لكل نسخة من نسخ الحمل.
يخصص المجدول جهازاً محدداً للمطالبة قبل ربط الحاوية، وتظهر النتيجة في حالة ResourceClaim ككائن API منظم. وهذا يمنح القرار مكاناً أصلياً يمكن لـ kubectl قراءته، ويمكن لـ RBAC حمايته، ويمكن للمتحكمات الأخرى البناء عليه، بدلاً من تخزينه كسلسلة نصية في annotation.
لكن DRA الأساسي لا يكفي وحده لمشاركة الذاكرة بأسلوب HAMi. فالامتداد المهم هنا هو Consumable Capacity، الذي ظهر تجريبياً في v1.34 خلف بوابة DRAConsumableCapacity، ثم أصبح تجريبياً ومفعلاً افتراضياً منذ v1.36. يتيح هذا الامتداد للمشغل إعلان أن الجهاز يقبل تخصيصات متعددة، كما يسمح للمطالبة بطلب كمية محددة من مورد مسمى على الجهاز، مثل الذاكرة أو القدرة الحسابية.
بهذا تصبح مطابقة موارد HAMi مباشرة تقريباً: يتحول gpumem إلى طلب سعة للذاكرة، ويتحول gpucores إلى طلب سعة للحوسبة، بينما ينتقل حساب ما إذا كانت البطاقة ما زالت تملك السعة المطلوبة من امتداد جدولة HAMi إلى مجدول Kubernetes نفسه.
الجدولة لا تعني فرض الحدود
يشدد التحليل على أن DRA يتتبع الوعود التي يقدمها المجدول، لكنه لا يمنع الحاوية من تجاوزها أثناء التشغيل. فاستدعاءات CUDA لا تعرف ما تقوله ResourceClaim، وقد يحاول حمل عمل شره استهلاك ذاكرة إضافية على حساب حاوية أخرى.
يتولى HAMi-core هذه الوظيفة عبر مكتبة C، وهي libvgpu.so، التي تعترض استدعاءات CUDA وNVIDIA Management Library وتطبق الحدود من مساحة المستخدم. ووفق المثال الوارد في المقال، إذا حصلت حاويتان على 8,000 ميجابايت لكل منهما، فإن الحاوية التي تتجاوز حصتها تتلقى خطأ CUDA نفاد الذاكرة عند حدها، بينما تستمر الحاوية الأخرى في العمل.
هذه الحماية البرمجية ليست بديلاً عن التقسيم العتادي في بيئات تعدد المستأجرين العدائية. فالحمل الذي يتجاوز التحميل المسبق للمكتبة، أو يستخدم ربطاً ثابتاً بمشغل CUDA، أو يستفيد من إعدادات مثل CUDA_DISABLE_CONTROL، قد يفلت من الاعتراض. ويذكر الكاتب أن NVIDIA Multi-Instance GPU، أو MIG، أنسب للعزل العتادي، بينما يوفر الاعتراض البرمجي دقة تصل إلى خطوات مقدارها 1 ميجابايت للذاكرة و1% للحوسبة مقارنة بملفات MIG الثابتة.
كيف يعيد HAMi بناء منظومته فوق DRA؟
تتوزع البنية الجديدة على ثلاثة مستودعات تؤدي وظائف مختلفة:
- k8s-dra-driver: ينشر ذاكرة كل GPU وقدرته الحسابية كسعة قابلة للاستهلاك في ResourceSlices، ويشغل إضافة kubelet، ويربط الحاويات عبر CDI مع إرفاق فرض HAMi-core.
- HAMi-DRA: webhook لتعديل القبول يزيل الموارد الممتدة التقليدية من الطلبات وينشئ ResourceClaims مكافئة، مع الحفاظ على annotations الخاصة باستهداف UUID ونوع الجهاز. وبحسب المقال، أصبحت HAMi-DRA v0.2.0 جاهزة للإنتاج مع HAMi v2.9، ثم انتقلت سلسلة الإصدارات إلى v0.2.1.
- HAMi: يوثق وضع DRA كخيار تثبيت منذ الإصدار v2.8، كما يفعّل مكون المراقبة افتراضياً ويعرض مقاييس الأجهزة لكل حاوية عبر Prometheus على المنفذ 31995.
وتكمن إحدى فوائد HAMi-DRA في أنه يترك الجدولة للمجدول الذي يدير العنقود، ما يسمح باستخدام Volcano أو KAI Scheduler أو أي مجدول آخر يفهم DRA من دون إضافة تكامل خاص بـ HAMi. لكن لهذا القرار كلفة: HAMi-DRA لا يملك مجدولاً خاصاً به، ولذلك لا يضمن قرارات واعية بالطوبولوجيا، مثل اختيار زوج من وحدات GPU المرتبطة عبر NVLink.
المتطلبات والاختيار العملي
يتطلب وضع DRA Kubernetes v1.34 أو أحدث مع تفعيل DRAConsumableCapacity. ففي الإصدارين v1.34 وv1.35 تكون البوابة تجريبية ومعطلة افتراضياً، ما قد يمنع استخدامه في الخدمات المدارة التي لا تتيح تعديل إعدادات خادم API. أما في v1.36 فأصبحت تجريبية ومفعلة افتراضياً. كما يلزم runtime يدعم CDI، مثل containerd أو CRI-O مع تفعيل CDI، ومشغل NVIDIA بإصدار 440 أو أحدث، إضافة إلى مشغل DRA مناسب لنوع المسرّع.
يصف المقال مسار NVIDIA بأنه الأكثر نضجاً، مع وصول دعم Ascend وEnflame وتوثيق Hygon DCU عبر k8s-dcu-dra-driver. وفي المقابل، يغطي وضع HAMi التقليدي أكثر من 12 عائلة أجهزة منذ v2.9، بما في ذلك Cambricon MLUs وIluvatar وMetaX وMoore Threads وKunlunxin وAWS Neuron وVastai. لذلك تظل العناقيد متعددة الموردين مرشحة للاستمرار على المسار التقليدي إلى أن تتوسع تغطية مشغلات DRA.
وينبغي عدم تشغيل وضع DRA ووضع Device Plugin التقليدي في العنقود نفسه، لأن نظامي الجدولة سيبدوان كأنهما يديران السعة نفسها من دون رؤية وعود النظام الآخر. وبحسب تقييم الكاتب، يناسب الوضع التقليدي العناقيد المدارة التي لا تتيح بوابات الميزات، والإصدارات الأقدم، والأساطيل متعددة الموردين. أما عناقيد NVIDIA التي تتحكم في مستوى Kubernetes، خصوصاً على v1.36، فيمكنها تجربة وضع DRA في بيئة اختبار، بدءاً من HAMi-DRA لتجنب تعديل ملفات النشر الحالية.
ويظل Consumable Capacity غير مستقر نهائياً في Kubernetes، كما أن مخطط Helm الخاص بـ k8s-dra-driver ما زال مصنفاً على أنه قيد العمل، وتبقى تغطية الموردين في جانب DRA أقل من الوضع التقليدي. خلاصة المقال أن DRA يتولى لغة الطلب والجدولة، بينما يحتفظ HAMi-core بقدرة التنفيذ داخل الحاوية؛ أي أن العلاقة بينهما تتجه إلى التكامل لا الاستبدال.