أساسيات Kubernetes

حلقة التوفيق (Reconciliation Loop)

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

حلقة التوفيق (Reconciliation Loop)

كل متحكم (Controller) في Kubernetes — متحكم Deployment، ومتحكم ReplicaSet، ومتحكم Node، ومتحكم StatefulSet — مبني على نفس الفكرة الجوهرية: حلقة مراقبة تقارن باستمرار الحالة المطلوبة بالحالة الملاحظة وتتصرف لإلغاء الفرق بينهما. هذا النمط ليس عَرَضياً في Kubernetes؛ بل هو المعمارية بأكملها. فهمه بعمق يغير طريقة تفكيرك في سبب تصرف النظام كما يتصرف، ولماذا التفكير بمنطق الاتساق النهائي مهم، وكيف تصمم حمولات العمل التي تتعاون مع مستوى التحكم بدلاً من محاربته.

الحالة المطلوبة مقابل الحالة الملاحظة

حين تشغّل kubectl apply -f deployment.yaml، فأنت تكتب إعلاناً في etcd — مخزن المفاتيح والقيم الموزَّع في الكلاستر. أنت لا تصدر أمراً يقول "اذهب وشغّل ستة Pods الآن." بل تقول: "أريد ستة Pods بهذه الصورة. اجعل الأمر كذلك وحافظ عليه إلى الأبد." يتحقق خادم API من الكائن ويخزّنه. من تلك اللحظة، لا شيء مما قلته يهم سوى ما هو مخزَّن في etcd. سيسعى الكلاستر لتحقيق ذلك الهدف بغض النظر عن إعادات التشغيل أو انقطاع الشبكة أو الأعطال.

الحالة الملاحظة هي ما تكتشفه المتحكمات باستعلامها عن الكلاستر: كم عدد الـ Pods الجارية فعلاً، وأيها سليمة، وأي العقد يمكن الوصول إليها. الفجوة بين المطلوب والملاحظ تسمى الانجراف (drift)، وإزالة الانجراف هو الغرض الوحيد من كل متحكم.

النموذج الذهني الأساسي: متحكمات Kubernetes هي ثرموستات تعريفية. تضبط درجة الحرارة (الحالة المطلوبة)؛ يقيس الثرموستات باستمرار درجة حرارة الغرفة (الحالة الملاحظة) ويشغّل المدفأة أو المكيف (أفعال التوفيق) حتى تطابق الغرفة الإعداد المطلوب. لا تقول للثرموستات أبداً "شغّل المدفأة لأربع دقائق بالضبط" — بل فقط تعلن عما تريد.

حلقة المراقبة: كيف تعمل المتحكمات

كل متحكم هو goroutine يعمل داخل عملية kube-controller-manager (أو كـ operator منفصل). ينفّذ ثلاث خطوات في حلقة مستمرة:

  1. List & Watch — عند البدء، يسرد المتحكم كل الكائنات ذات الصلة من خادم API ثم يفتح تدفق Watch طويل الأمد. يدفع خادم API أحداثاً (ADDED, MODIFIED, DELETED) عند كل تغيير في كائن. هذا أكفأ بكثير من الاستطلاع — تتفاعل المتحكمات في أجزاء من الثانية.
  2. حساب الفرق — مقارنة spec (المطلوب) مع status (الملاحظ). لمتحكم ReplicaSet: عدد النسخ المطلوبة مقابل الـ Pods السليمة الجارية التي تطابق المحدِّد.
  3. التصرف — إصدار استدعاءات API لسد الفجوة: إنشاء Pods، أو حذفها، أو تحديث حقول الـ status. ثم إعادة الإدراج في قائمة الانتظار لإعادة المراقبة. الحلقة لا تنتهي أبداً.
The Kubernetes Reconciliation Loop etcd Desired State (spec stored here) API Server Validates & persists Watch stream kubectl apply Writes desired state to API Controller 1. List & Watch 2. Compute diff (desired - actual) 3. Act to close the gap Watch events Observed State Actual running Pods reads Create / Delete Pods via API cluster state changes, controller re-observes Continuous loop — never terminates
حلقة التوفيق في Kubernetes: تراقب المتحكمات خادم API، وتقارن الحالة المطلوبة بالملاحظة، وتتصرف باستمرار حتى تتطابقا.

المُشغَّل بالمستوى مقابل المُشغَّل بالحدث

معظم أتمتة الأنظمة التقليدية مُشغَّلة بالحدث (edge-triggered): تتفاعل مع حدث ("تعطّل خادم — شغّل سكريبت الاسترداد"). إن فاتك الحدث — لأن المستمع كان متوقفاً، أو أسقطت الشبكة الإشعار — يظل النظام معطلاً حتى يتدخل إنسان. أما Kubernetes فهو بتصميم متعمد مُشغَّل بالمستوى (level-triggered): لا تهتم المتحكمات بما حدث؛ بل فقط بالحالة الحالية. حتى لو تعطّلت متحكمة وفاتها مئة حذف لـ Pods، عند إعادة تشغيلها ستسرد كل الـ Pods، وترى أن العدد خاطئ، وتُوفّق. النظام يُشفي ذاته بطبيعته دون أي منطق إعادة محاولة خاص.

الأثر في الإنتاج: يمكنك إعادة تشغيل kube-controller-manager بأمان في أي وقت. ستُعيد المتحكمات سرد كل شيء وتُوفّق من الصفر. لهذا السبب يعمل تشغيل نسخ متعددة من مستوى التحكم (وضع HA) دون split-brain: تستخدم المتحكمات انتخاب القائد عبر كائن Lease، ويتصرف القائد المنتخب فحسب. تظل البقية في وضع المراقبة جاهزةً للاستلام.

مراقبة حلقة التحكم في الوقت الحقيقي

أفضل طريقة لرؤية حلقة التوفيق عملياً هي إنشاء انجراف عمداً ومشاهدة النظام وهو يُصحّحه.

# الخطوة 1: أنشئ Deployment بـ 3 نسخ kubectl create deployment demo --image=nginx:1.25 --replicas=3 # الخطوة 2: اسرد الـ Pods kubectl get pods -l app=demo # NAME READY STATUS RESTARTS AGE # demo-7d9f4b6c9d-2xkrp 1/1 Running 0 30s # demo-7d9f4b6c9d-h7qnl 1/1 Running 0 30s # demo-7d9f4b6c9d-pj8tm 1/1 Running 0 30s # الخطوة 3: احذف Pod واحدة يدوياً — أدخل انجرافاً kubectl delete pod demo-7d9f4b6c9d-2xkrp # الخطوة 4: اسرد مباشرةً — المتحكم تصرف بالفعل kubectl get pods -l app=demo # NAME READY STATUS RESTARTS AGE # demo-7d9f4b6c9d-h7qnl 1/1 Running 0 45s # demo-7d9f4b6c9d-pj8tm 1/1 Running 0 45s # demo-7d9f4b6c9d-r9mxq 0/1 ContainerCreating 0 1s <-- Pod جديدة # الخطوة 5: راقب أحداث المتحكم — هذه هي أفعال التوفيق kubectl describe replicaset -l app=demo | grep -A 10 "Events:" # Events: # Normal SuccessfulCreate 2m replicaset-controller Created pod: demo-7d9f4b6c9d-r9mxq

الفجوة بين حذف الـ Pod ورؤية واحدة جديدة في ContainerCreating عادةً أقل من ثانية في كلاستر سليم. هذه الاستجابة السريعة تأتي من تدفق Watch — يستقبل متحكم ReplicaSet حدث DELETED للـ Pod فورياً تقريباً ويضع عنصر عمل التوفيق في قائمة الانتظار. لا تأخيرات لفترة استطلاع تُعيق التفاعل.

دور status مقابل spec

لكل كائن Kubernetes قسمان رئيسيان يُجسّدان الطبيعة المزدوجة للنظام. spec هو العقد الذي تكتبه أنت؛ وstatus هو ما تكتبه المتحكمة بعد مراقبة الواقع. لا تكتب status أبداً بنفسك — يديره مستوى التحكم. حين تفحص kubectl get deployment، يقرأ عمود READY من status.readyReplicas لا من spec.replicas.

# افحص spec و status الخاماً kubectl get deployment demo -o yaml | grep -A 12 "^status:" # status: # availableReplicas: 3 # conditions: # - lastTransitionTime: "2025-03-15T10:22:31Z" # message: Deployment has minimum availability. # reason: MinimumReplicasAvailable # status: "True" # type: Available # observedGeneration: 2 # readyReplicas: 3 # replicas: 3 # updatedReplicas: 3 # observedGeneration مقابل metadata.generation يخبرك إن كانت المتحكمة قد عالجت آخر تغيير kubectl get deployment demo \ -o jsonpath='{.metadata.generation} {.status.observedGeneration}{"\n"}' # 2 2 <-- مُوفَّق بالكامل؛ "3 2" تعني أن rollout لا يزال جارياً

أنماط الفشل في الإنتاج الناجمة عن سوء فهم الحلقة

معظم حوادث Kubernetes في الإنتاج تنشأ من تفكير المهندسين بأسلوب أمري بينما النظام تعريفي. إليك أكثر المزالق شيوعاً على نطاق واسع:

  • تعديل Pods يدوياً وتوقّع استمرار التغيير. إن دخلت إلى Pod بـ kubectl exec وثبّتت حزمة، أو غيّرت متغير بيئة بـ kubectl edit pod، يختفي التغيير فور استبدال Kubernetes لتلك الـ Pod (عند إخلاء العقدة، أو الإخراج القسري، أو الانهيار). مصدر الحقيقة هو spec في الـ Deployment — دائماً حدّث الملف، ليس الـ Pod الحية.
  • مقاومة المتحكم بسكريبتات. بعض الفرق تكتب سكريبتات تُصغّر Deployment مؤقتاً ("اجعل النسخ صفراً للصيانة"). المتحكم يُسجّل ذلك في status — جيد. لكن إن لم يُحدَّث الـ spec وأعاد شيء آخر (HPA أو operator) ضبطه، فإن المتحكم يُعيد التوسع باتباع التعليمات. الطريقة الآمنة الوحيدة لمنع المتحكم من التصرف هي تحديث الـ spec، أو إيقافه مؤقتاً بـ kubectl rollout pause.
  • تجاهل التراجع التدريجي للمتحكم. حين تفشل حلقة التوفيق مراراً (أخطاء سحب الصور، تجاوز الحصة، فشل الجدولة)، يطبق Kubernetes تراجعاً أسياً — حتى 5 دقائق بين المحاولات افتراضياً. الكلاستر ليس عالقاً؛ بل يتراجع. افحص أحداث kubectl describe deployment للتشخيص، لا kubectl get pods وحده.
مزلق الإنتاج — CrashLoopBackOff مُفسَّر خطأً: CrashLoopBackOff ليس خطأ Kubernetes — إنه حلقة التوفيق تعمل بشكل صحيح. الحاوية تخرج فوراً (خلل في تطبيقك)، ويُعيد Kubernetes إنشاءها (الحالة المطلوبة تقول يجب تشغيلها)، ثم يراها تنهار مجدداً ويتراجع. الإصلاح دائماً في تطبيقك، ليس في إعدادات Kubernetes. لا تضبط restartPolicy: Never على Deployment لإخفاء CrashLoopBackOff — ذلك يجعل المتحكم ينشئ Pod جديدة بدلاً من إعادة تشغيل نفسها، وهذا أسوأ. أصلح السبب الجذري.

المتحكمات المخصصة ونمط Operator

حلقة التوفيق لا تقتصر على موارد Kubernetes المدمجة. يمتد نمط Operator مستوى التحكم بكتابة متحكمات مخصصة لـ CustomResourceDefinitions (CRDs) الخاصة بك. Operator قاعدة بيانات (كـ Postgres Operator أو Cassandra Operator) يراقب كائنات من نوع PostgresCluster، ثم يُوفّق بإنشاء StatefulSets وServices وConfigMaps وSecrets لتطابق الـ spec المُعلنة. نفس ضمان التشغيل ذاتي الشفاء المُشغَّل بالمستوى ينطبق هنا. في شركات التكنولوجيا الكبرى، معظم البنية التحتية الإنتاجية — قواعد البيانات والطوابير والذاكرات المؤقتة ووظائف ML — يُديرها operators تشغّل هذا النمط بالضبط.

# اعرض كل المتحكمات المُسجَّلة وحالة انتخاب قائدها kubectl -n kube-system get lease # NAME HOLDER AGE # kube-controller-manager controlplane-node-1_abc123... 10d # kube-scheduler controlplane-node-1_abc123... 10d # تابع سجلات مدير المتحكم لرؤية التوفيق عملياً kubectl -n kube-system logs -l component=kube-controller-manager --tail=50 -f # افحص conditions على Deployment تشير إلى مشاكل في التوفيق kubectl get deployment demo \ -o jsonpath='{range .status.conditions[*]}{.type}{"\t"}{.status}{"\t"}{.message}{"\n"}{end}' # Available True Deployment has minimum availability. # Progressing True ReplicaSet "demo-abc123" has successfully progressed.