أساسيات Kubernetes

لماذا Kubernetes؟

18 دقيقة الدرس 1 من 32

لماذا Kubernetes؟

في عام 2013، جعل Docker من السهل للغاية تعبئة تطبيق مع بيئة تشغيله في صورة قابلة للنقل. بحلول عام 2015، كانت كل فرق الهندسة الجادة تبني صور حاويات — لكن ظهرت فئة جديدة من المشكلات على نطاق واسع: من يقرر أين تعمل كل حاوية، وماذا يحدث عند تعطّلها، وكيف تصل إليها عبر مئات المضيفين، وكيف تُطلق إصداراً جديداً دون توقف؟ هذه الأسئلة لها اسم جماعي واحد: مشكلة تنسيق الحاويات. و Kubernetes هو الإجابة المعيارية في الصناعة.

يُرسي هذا الدرس المشكلات الأربع الجوهرية التي يحلها Kubernetes — الجدولة والاسترداد الذاتي والتوسع واكتشاف الخدمات — ويشرح سبب تعقيد كل منها فور الانتقال إلى أكثر من حفنة من الحاويات على مضيف واحد.

المشكلة الأولى: الجدولة — أين تعمل كل حاوية؟

تخيّل 40 خدمة مصغّرة، كل منها تحتاج بين 0.1 و4 نواة CPU وبين 128 ميغابايت و8 غيغابايت ذاكرة وصول عشوائي، موزعة على 20 خادماً بطاقات مختلفة. تحتاج إلى توزيع الأحمال على العقد لتعظيم الاستخدام دون الإفراط في تخصيص أي جهاز. كما تحتاج إلى احترام قيود: يجب ألا تشارك خدمة المدفوعات نفس العقدة مع مهمة التحليلات التي تُشبع المعالج بنسبة 100%؛ يجب أن تُجدوَل قاعدة البيانات الحاملة للحالة على عقدة تحتوي أقراصاً محلية سريعة؛ يجب وضع نسختين من الخدمة نفسها في منطقتَي توافر مختلفتين لئلا يُخرج عطل مركز بيانات كل النسخ دفعة واحدة.

القيام بذلك يدوياً — حتى مع سكريبت شل مُتقن — لا يُناسب النطاق الواسع. في كل مرة تُضاف عقدة أو تُزال أو تعطل، يجب إعادة الحساب. يحل Kubernetes ذلك بـجدوَل تصريحي: تصف ما تحتاجه (CPU وذاكرة وتسميات العقد وقواعد الألفة/معادلتها وقيود توزيع الطوبولوجيا)، ويقرر الجدوَل أين يضع الحمل، مُعيداً التقييم باستمرار مع تغير حالة المجموعة.

# افحص كيف يرى الجدوَل عقدك — الطاقة الكاملة مقابل القابلة للتخصيص kubectl describe nodes | grep -A 8 "Capacity:\|Allocatable:\|Non-terminated Pods:" # تحقق من ضغط الموارد الحالي على كل عقدة kubectl top nodes # انظر أين جُدولت Pod معينة ولماذا kubectl get pod <pod-name> -o wide # تُظهر العقدة kubectl describe pod <pod-name> | grep -A 5 "Events:" # سجل قرار الجدوَل
الفكرة الأساسية — التصريحي مقابل الأمري: لا تقول لـ Kubernetes أبداً "شغّل هذه الحاوية على node-07." بل تقول له "شغّل 3 نسخ من هذه الصورة، كل منها تحتاج 500m CPU و512Mi ذاكرة." الجدوَل يكتشف مكان الوضع. هذا الفصل بين النية والتنفيذ هو أساس كل ما يفعله Kubernetes.

المشكلة الثانية: الاسترداد الذاتي — البقاء على قيد الحياة في حالات الفشل

على نطاق 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): تُحدد عدد النسخ التي يمكن أن تكون غير متاحة في آنٍ واحد — لمنع تحديث متدحرج متسرع أو استنزاف عقدة من إخراج خدمة كلياً.
Kubernetes Self-Healing: Node Failure and Rescheduling Before: 3 Healthy Nodes Node 1 Pod A Pod B Pod C Node 2 Pod D Pod E Node 3 Pod F Pod G Control Plane API Server Scheduler Controller Mgr etcd After: Node 3 Fails — Pods Rescheduled Node 1 Pod A Pod B Pod C Pod F* Node 2 Pod D Pod E Pod G* Node 3 NODE FAILED NotReady detect reschedule * أُعيد جدولتها من Node 3
عند فشل العقدة 3، يكتشف مستوى التحكم في Kubernetes حالة NotReady ويُعيد جدولة Pods الخاصة بها آلياً على العقد الصحية المتبقية.

المشكلة الثالثة: التوسع — مطابقة الطاقة مع الطلب

حركة المرور ليست ثابتة أبداً. قد تعالج خدمة المدفوعات 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.
# أنشئ HPA يستهدف 60% متوسط استخدام CPU عبر النسخ # يتوسع بين 2 و20 نسخة آلياً kubectl autoscale deployment api-server \ --cpu-percent=60 \ --min=2 \ --max=20 # راقب HPA أثناء اختبار الحمل kubectl get hpa api-server --watch # أو عرّفه تصريحياً (الأفضل — تحت إدارة الإصدارات) # hpa.yaml apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: api-server spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: api-server minReplicas: 2 maxReplicas: 20 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 60 - type: Resource resource: name: memory target: type: Utilization averageUtilization: 75
ممارسة احترافية: اضبط دائماً كلاً من 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 أو ما إذا كانت قد أُعيد تشغيلها.

# تعريف Service بسيط # الـ Service تجد Pods بمطابقة التسميات (app: payments) — لا ترميز IP صلب apiVersion: v1 kind: Service metadata: name: payments namespace: checkout spec: selector: app: payments # تختار كل Pods بهذه التسمية ports: - port: 80 targetPort: 8080 # المنفذ الذي تستمع إليه حاويتك فعلاً type: ClusterIP # عنوان IP افتراضي ثابت، داخلي للمجموعة فقط --- # أي Pod أخرى في المجموعة يمكنها الآن الوصول لـ payments عبر DNS: # curl http://payments.checkout.svc.cluster.local/health # أو: curl http://payments.checkout/health (داخل نفس سلسلة namespace) # تحقق من دقة DNS من داخل Pod تصحيح kubectl run -it --rm debug --image=nicolaka/netshoot -- bash # داخل الحاوية: # nslookup payments.checkout.svc.cluster.local # curl http://payments.checkout/health
مصيدة إنتاجية: الـ ClusterIP هو عنوان IP افتراضي مُنفَّذ في قواعد 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 معقد — منحنى تعلمه حقيقي وشديد. لكن التعقيد عرضي في السطح فقط. النموذج الأساسي (أعلن الحالة المطلوبة، دع المتحكمات تُوفّق) بسيط وقوي. كل مفهوم في هذا الدرس التعليمي هو إجابة مباشرة لمشكلة تشغيلية محددة تصطدم بها كل فرقة عند تشغيل الحاويات في الإنتاج على أي نطاق ذي معنى.

سياق التقنية الكبرى: تُشغّل Google نظاماً مشابهاً لـ Kubernetes (Borg، ثم Omega) داخلياً منذ عام 2003 تقريباً. مشكلات تنسيق الحاويات التي يصفها هذا الدرس ليست نظرية — هي المشكلات بالضبط التي حلّتها Google وUber وAirbnb وSpotify وNetflix قبل أن تُساهم في أو تتبنى Kubernetes. كل مفهوم في الدروس القادمة يُقابل مباشرة قراراً إنتاجياً مُختبَراً اتخذته تلك الشركات.

ما القادم

الآن بعد أن فهمت لماذا يوجد Kubernetes وما المشكلات التي يحلها، تبني الدروس المتبقية في هذا الدرس التعليمي الصورة الكاملة:

  • الدرس 2 — بنية المجموعة: مكونات مستوى التحكم (API Server وetcd والجدوَل ومدير المتحكمات) ومكونات عقدة العامل (kubelet وkube-proxy وبيئة التشغيل).
  • الدرس 3 — Pods: الوحدة الذرية للجدولة — ما هي وكيف تختلف عن الحاويات وكيف تكتب مواصفة Pod صحيحة.
  • الدروس 4-6 تبني على Pods بالكائنات ذات المستوى الأعلى: kubectl وReplicaSets وDeployments وServices.
  • الدرس 10 يُلخّص كل شيء في نشر حقيقي شامل لتطبيق إنتاجي على Kubernetes.