لماذا Kubernetes؟
لماذا Kubernetes؟
في عام 2013، جعل Docker من السهل للغاية تعبئة تطبيق مع بيئة تشغيله في صورة قابلة للنقل. بحلول عام 2015، كانت كل فرق الهندسة الجادة تبني صور حاويات — لكن ظهرت فئة جديدة من المشكلات على نطاق واسع: من يقرر أين تعمل كل حاوية، وماذا يحدث عند تعطّلها، وكيف تصل إليها عبر مئات المضيفين، وكيف تُطلق إصداراً جديداً دون توقف؟ هذه الأسئلة لها اسم جماعي واحد: مشكلة تنسيق الحاويات. و Kubernetes هو الإجابة المعيارية في الصناعة.
يُرسي هذا الدرس المشكلات الأربع الجوهرية التي يحلها Kubernetes — الجدولة والاسترداد الذاتي والتوسع واكتشاف الخدمات — ويشرح سبب تعقيد كل منها فور الانتقال إلى أكثر من حفنة من الحاويات على مضيف واحد.
المشكلة الأولى: الجدولة — أين تعمل كل حاوية؟
تخيّل 40 خدمة مصغّرة، كل منها تحتاج بين 0.1 و4 نواة CPU وبين 128 ميغابايت و8 غيغابايت ذاكرة وصول عشوائي، موزعة على 20 خادماً بطاقات مختلفة. تحتاج إلى توزيع الأحمال على العقد لتعظيم الاستخدام دون الإفراط في تخصيص أي جهاز. كما تحتاج إلى احترام قيود: يجب ألا تشارك خدمة المدفوعات نفس العقدة مع مهمة التحليلات التي تُشبع المعالج بنسبة 100%؛ يجب أن تُجدوَل قاعدة البيانات الحاملة للحالة على عقدة تحتوي أقراصاً محلية سريعة؛ يجب وضع نسختين من الخدمة نفسها في منطقتَي توافر مختلفتين لئلا يُخرج عطل مركز بيانات كل النسخ دفعة واحدة.
القيام بذلك يدوياً — حتى مع سكريبت شل مُتقن — لا يُناسب النطاق الواسع. في كل مرة تُضاف عقدة أو تُزال أو تعطل، يجب إعادة الحساب. يحل Kubernetes ذلك بـجدوَل تصريحي: تصف ما تحتاجه (CPU وذاكرة وتسميات العقد وقواعد الألفة/معادلتها وقيود توزيع الطوبولوجيا)، ويقرر الجدوَل أين يضع الحمل، مُعيداً التقييم باستمرار مع تغير حالة المجموعة.
المشكلة الثانية: الاسترداد الذاتي — البقاء على قيد الحياة في حالات الفشل
على نطاق Google، فشل الأجهزة ليس حالة طارئة — بل هو الوضع الافتراضي. مجموعة بها 10,000 عقدة تفقد إحصائياً عدة أجهزة كل يوم. على فريق أصغر بـ50 عقدة، لا تزال تفقد عقداً بسبب انهيارات نواة Linux وقتل OOM وتلف الأقراص ونوافذ صيانة مزود السحابة. بدون تنسيق، تبقى الحاوية المتعطلة ميتة حتى يلاحظها إنسان ويُعيد تشغيلها. في الساعة الثالثة فجراً، قد يستغرق ذلك 45 دقيقة.
يحل Kubernetes ذلك عبر حلقة التوفيق (يُغطّيها الدرس 8 بعمق). كل متحكم — متحكم ReplicaSet ومتحكم Deployment ومتحكم DaemonSet — يُقارن باستمرار الحالة المطلوبة التي أعلنتها مقابل الحالة الفعلية للمجموعة. إن اختلفت الفعلية عن المطلوبة، يتصرف: يُعيد جدولة Pods الفاشلة ويستبدل الحاويات غير الصحية ويُعيد توزيع العمل على العقد الصحية آلياً.
مجموعة الاسترداد الذاتي لها طبقات متعددة:
- فحوصات الحيوية (Liveness probes): يُعيد Kubernetes تشغيل حاوية إن فشل فحص حيويتها — للكشف عن الحالات المُغلقة والحلقات اللانهائية التي تُبقي العملية حية لكن غير وظيفية.
- فحوصات الجاهزية (Readiness probes): لا يُوجَّه التدفق إلى Pod إلا بعد اجتياز فحص جاهزيتها — لمنع حاوية بدأت للتو (لكن لم تدفأ بعد) من استقبال طلبات إنتاجية لا تستطيع معالجتها.
- فشل العقدة: إن توقفت عقدة عن الإبلاغ لمستوى التحكم، يُخلي Kubernetes Pods خاصتها ويُعيد جدولتها على عقد صحية خلال
pod-eviction-timeoutالمُهيأ (الافتراضي 5 دقائق في معظم المجموعات المُدارة). - ميزانيات تعطل Pod (PDBs): تُحدد عدد النسخ التي يمكن أن تكون غير متاحة في آنٍ واحد — لمنع تحديث متدحرج متسرع أو استنزاف عقدة من إخراج خدمة كلياً.
المشكلة الثالثة: التوسع — مطابقة الطاقة مع الطلب
حركة المرور ليست ثابتة أبداً. قد تعالج خدمة المدفوعات 50 طلباً في الثانية في الساعة الثانية فجراً و8,000 طلب في الثانية في ذروة الجمعة السوداء. ضبط عدد النسخ يدوياً — أو الأسوأ، التجهيز دائماً للذروة — إما غير قابل للإدارة أو مُهدِر للغاية.
يوفر Kubernetes ثلاثة آليات توسع متكاملة:
- HPA (توسع Pod الأفقي): يزيد أو يقلل آلياً عدد نسخ Pod بناءً على استخدام CPU أو الذاكرة أو مقاييس مخصصة من Prometheus. في Google، يعمل HPA على كل Deployment في الإنتاج تقريباً — وهو الآلية الرئيسية للتعامل مع ذرى التدفق دون تدخل يدوي.
- VPA (توسع Pod الرأسي): يضبط طلبات CPU والذاكرة للـ Pods الموجودة بناءً على الاستخدام الفعلي المُرصود — حاسم لضبط الأحمال بدقة دون إهدار الطاقة المحجوزة.
- CA (توسع المجموعة): حين لا يستطيع الجدوَل وضع Pod لأن أي عقدة موجودة لا تملك طاقة كافية، يُهيئ CA جهازاً افتراضياً سحابياً جديداً (EC2 أو GCE أو Azure VM) ويُضيفه للمجموعة. حين تكون العقد غير مُستَخدمة، يُفرّغها ويُنهيها. يتكامل هذا مباشرة مع AWS Auto Scaling Groups ومجموعات عقد GKE وAKS.
requests وlimits على كل حاوية. يتطلب HPA وجود requests لحساب نسب الاستخدام. حذف requests يعني أن HPA لا يمتلك خطاً أساسياً للتوسع منه — لن يفعل شيئاً. حذف limits يعني أن عملية منفلتة يمكنها تجويع كل Pods الأخرى على نفس العقدة (فئة من حوادث الإنتاج تُسمى "الجار المزعج"). بمعيار التقنية الكبرى، مانيفست يحذف requests وlimits يفشل في مراجعة الكود.المشكلة الرابعة: اكتشاف الخدمات — كيف تجد A الخدمة B في مجموعة ديناميكية؟
في عالم ما قبل الحاويات، كانت للخدمات عناوين IP ثابتة. يمكنك ترميز db.internal:5432 بشكل صلب ليعمل لسنوات. في Kubernetes، الـ Pods زائلة: تتعطل وتُعاد تشغيلها بعناوين IP جديدة، تتوسع وتنكمش ديناميكياً، وتُعاد جدولتها على عقد مختلفة. في أي لحظة، قد تعمل بين 3 و20 نسخة من خدمة API على 10 عقد مختلفة — كل منها بعنوان IP داخلي مختلف.
يحل Kubernetes هذا بكائن Service: عنوان IP افتراضي ثابت (يُسمى ClusterIP) واسم DNS يوزع التدفق على كل Pods الصحية المطابقة لمحدد التسمية. يُسجَّل اسم DNS تلقائياً في kube-dns (أو CoreDNS): خدمة باسم payments في namespace checkout تصل إليها عبر payments.checkout.svc.cluster.local من أي Pod في المجموعة، بغض النظر عن أين تعمل أي من الـ Pods أو ما إذا كانت قد أُعيد تشغيلها.
iptables (أو ipvs) — لا يتوافق مع أي واجهة شبكة حقيقية. إن ping-ت ClusterIP فستنتهي المهلة حتى عندما تكون الخدمة صحية، لأن ICMP لا يُعاد توجيهه عبر قواعد NAT في iptables. اختبر دائماً اتصال الخدمة بـcurl أو اتصال TCP مباشر، لا بـping. هذا يُفاجئ المهندسين المنتقلين من شبكات VM التقليدية في كل مرة.لماذا لا تكتفي بـ Docker Compose أو سكريبت بسيط؟
سؤال شائع من المهندسين في بداية رحلتهم مع Kubernetes: "لدينا بالفعل Docker Compose في الإنتاج — ما الذي يُضيفه Kubernetes فعلاً؟" الجواب الصادق: القليل جداً على مضيف واحد، وكل شيء على نطاق واسع.
- Docker Compose يعمل على مضيف واحد. لا يمتلك مفهوم توزيع الأحمال عبر أجهزة متعددة، ولا استرداد داخلي من فشل العقدة، ولا محاسبة موارد على مستوى المجموعة.
- عمليات إعادة التشغيل المكتوبة لا تسترد بشكل موثوق. سياسة
restart: alwaysتُعيد تشغيل حاوية متعطلة — لكن إن فشل المضيف، لا شيء يُعيد تشغيل أي شيء. إن كانت الحاوية في حلقة تعطل، يستمر السكريبت في إعادة تشغيلها مُخفياً المشكلة بدلاً من كشفها. - التوسع اليدوي ضريبة من الجهد. كل ارتفاع في التدفق يصبح تدخلاً يدوياً. على نطاق أي نظام إنتاجي حقيقي، هذا غير مستدام.
- لا يوجد DNS واكتشاف خدمات بشكل طبيعي في Compose. إما ترمز IPs بشكل صلب أو تبني منطق سجل خدمات خاص بك.
Kubernetes معقد — منحنى تعلمه حقيقي وشديد. لكن التعقيد عرضي في السطح فقط. النموذج الأساسي (أعلن الحالة المطلوبة، دع المتحكمات تُوفّق) بسيط وقوي. كل مفهوم في هذا الدرس التعليمي هو إجابة مباشرة لمشكلة تشغيلية محددة تصطدم بها كل فرقة عند تشغيل الحاويات في الإنتاج على أي نطاق ذي معنى.
ما القادم
الآن بعد أن فهمت لماذا يوجد Kubernetes وما المشكلات التي يحلها، تبني الدروس المتبقية في هذا الدرس التعليمي الصورة الكاملة:
- الدرس 2 — بنية المجموعة: مكونات مستوى التحكم (API Server وetcd والجدوَل ومدير المتحكمات) ومكونات عقدة العامل (kubelet وkube-proxy وبيئة التشغيل).
- الدرس 3 — Pods: الوحدة الذرية للجدولة — ما هي وكيف تختلف عن الحاويات وكيف تكتب مواصفة Pod صحيحة.
- الدروس 4-6 تبني على Pods بالكائنات ذات المستوى الأعلى:
kubectlوReplicaSets وDeployments وServices. - الدرس 10 يُلخّص كل شيء في نشر حقيقي شامل لتطبيق إنتاجي على Kubernetes.