حلقة التوفيق (Reconciliation Loop)
حلقة التوفيق (Reconciliation Loop)
كل متحكم (Controller) في Kubernetes — متحكم Deployment، ومتحكم ReplicaSet، ومتحكم Node، ومتحكم StatefulSet — مبني على نفس الفكرة الجوهرية: حلقة مراقبة تقارن باستمرار الحالة المطلوبة بالحالة الملاحظة وتتصرف لإلغاء الفرق بينهما. هذا النمط ليس عَرَضياً في Kubernetes؛ بل هو المعمارية بأكملها. فهمه بعمق يغير طريقة تفكيرك في سبب تصرف النظام كما يتصرف، ولماذا التفكير بمنطق الاتساق النهائي مهم، وكيف تصمم حمولات العمل التي تتعاون مع مستوى التحكم بدلاً من محاربته.
الحالة المطلوبة مقابل الحالة الملاحظة
حين تشغّل kubectl apply -f deployment.yaml، فأنت تكتب إعلاناً في etcd — مخزن المفاتيح والقيم الموزَّع في الكلاستر. أنت لا تصدر أمراً يقول "اذهب وشغّل ستة Pods الآن." بل تقول: "أريد ستة Pods بهذه الصورة. اجعل الأمر كذلك وحافظ عليه إلى الأبد." يتحقق خادم API من الكائن ويخزّنه. من تلك اللحظة، لا شيء مما قلته يهم سوى ما هو مخزَّن في etcd. سيسعى الكلاستر لتحقيق ذلك الهدف بغض النظر عن إعادات التشغيل أو انقطاع الشبكة أو الأعطال.
الحالة الملاحظة هي ما تكتشفه المتحكمات باستعلامها عن الكلاستر: كم عدد الـ Pods الجارية فعلاً، وأيها سليمة، وأي العقد يمكن الوصول إليها. الفجوة بين المطلوب والملاحظ تسمى الانجراف (drift)، وإزالة الانجراف هو الغرض الوحيد من كل متحكم.
حلقة المراقبة: كيف تعمل المتحكمات
كل متحكم هو goroutine يعمل داخل عملية kube-controller-manager (أو كـ operator منفصل). ينفّذ ثلاث خطوات في حلقة مستمرة:
- List & Watch — عند البدء، يسرد المتحكم كل الكائنات ذات الصلة من خادم API ثم يفتح تدفق Watch طويل الأمد. يدفع خادم API أحداثاً (ADDED, MODIFIED, DELETED) عند كل تغيير في كائن. هذا أكفأ بكثير من الاستطلاع — تتفاعل المتحكمات في أجزاء من الثانية.
- حساب الفرق — مقارنة
spec(المطلوب) معstatus(الملاحظ). لمتحكم ReplicaSet: عدد النسخ المطلوبة مقابل الـ Pods السليمة الجارية التي تطابق المحدِّد. - التصرف — إصدار استدعاءات API لسد الفجوة: إنشاء Pods، أو حذفها، أو تحديث حقول الـ status. ثم إعادة الإدراج في قائمة الانتظار لإعادة المراقبة. الحلقة لا تنتهي أبداً.
المُشغَّل بالمستوى مقابل المُشغَّل بالحدث
معظم أتمتة الأنظمة التقليدية مُشغَّلة بالحدث (edge-triggered): تتفاعل مع حدث ("تعطّل خادم — شغّل سكريبت الاسترداد"). إن فاتك الحدث — لأن المستمع كان متوقفاً، أو أسقطت الشبكة الإشعار — يظل النظام معطلاً حتى يتدخل إنسان. أما Kubernetes فهو بتصميم متعمد مُشغَّل بالمستوى (level-triggered): لا تهتم المتحكمات بما حدث؛ بل فقط بالحالة الحالية. حتى لو تعطّلت متحكمة وفاتها مئة حذف لـ Pods، عند إعادة تشغيلها ستسرد كل الـ Pods، وترى أن العدد خاطئ، وتُوفّق. النظام يُشفي ذاته بطبيعته دون أي منطق إعادة محاولة خاص.
kube-controller-manager بأمان في أي وقت. ستُعيد المتحكمات سرد كل شيء وتُوفّق من الصفر. لهذا السبب يعمل تشغيل نسخ متعددة من مستوى التحكم (وضع HA) دون split-brain: تستخدم المتحكمات انتخاب القائد عبر كائن Lease، ويتصرف القائد المنتخب فحسب. تظل البقية في وضع المراقبة جاهزةً للاستلام.مراقبة حلقة التحكم في الوقت الحقيقي
أفضل طريقة لرؤية حلقة التوفيق عملياً هي إنشاء انجراف عمداً ومشاهدة النظام وهو يُصحّحه.
الفجوة بين حذف الـ Pod ورؤية واحدة جديدة في ContainerCreating عادةً أقل من ثانية في كلاستر سليم. هذه الاستجابة السريعة تأتي من تدفق Watch — يستقبل متحكم ReplicaSet حدث DELETED للـ Pod فورياً تقريباً ويضع عنصر عمل التوفيق في قائمة الانتظار. لا تأخيرات لفترة استطلاع تُعيق التفاعل.
دور status مقابل spec
لكل كائن Kubernetes قسمان رئيسيان يُجسّدان الطبيعة المزدوجة للنظام. spec هو العقد الذي تكتبه أنت؛ وstatus هو ما تكتبه المتحكمة بعد مراقبة الواقع. لا تكتب status أبداً بنفسك — يديره مستوى التحكم. حين تفحص kubectl get deployment، يقرأ عمود READY من status.readyReplicas لا من spec.replicas.
أنماط الفشل في الإنتاج الناجمة عن سوء فهم الحلقة
معظم حوادث 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 ليس خطأ 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 تشغّل هذا النمط بالضبط.