يمكن أن يتسبب تباطؤ قاعدة البيانات لثلاث ثوانٍ فقط في انقطاع شامل للخدمة داخل المجموعة، وذلك إذا تمت تهيئة فحص الحيوية (Liveness Probe) في نظام Kubernetes بشكل خاطئ. بالنسبة لفرق هندسة موثوقية المواقع (SRE) التي تدير بنية الخدمات المصغرة، فإن التعامل مع فحوصات صحة النظام كمجرد إجراء روتيني يحول خللاً بسيطاً في التبعيات إلى فشل كارثي تعيد فيه عشرات الحاويات تشغيل نفسها في اللحظة ذاتها.
في هذه الحالة، لم يقم أحد بنشر كود برمجي معيب، ولم تتعطل أي عقدة خادم. النظام يدمر نفسه فعلياً لأن فحص الصحة الذي تمت كتابته قبل أشهر أساء فهم كيفية تعامل أنظمة تنظيم الحاويات مع حالات الفشل.
فحص الحيوية مقابل الجاهزية: الفارق الحاسم
يوفر نظام Kubernetes نوعين أساسيين من فحوصات الصحة يجيبان عن أسئلة مختلفة تماماً ويطلقان إجراءات متباينة. يحدد فحص الجاهزية (Readiness Probe) ما إذا كان يجب أن تتلقى الحاوية حركة المرور في الوقت الحالي. وإذا فشل، يزيل النظام الحاوية مؤقتاً من نقاط اتصال الخدمة بينما تستمر العملية في العمل، مما يسمح بعودة حركة المرور بمجرد نجاح الفحص مجدداً.
في المقابل، يسأل فحص الحيوية عما إذا كانت العملية قد تعطلت بشكل لا يمكن إصلاحه وتتطلب إعادة ضبط قاسية. وعندما يفشل هذا الفحص، يقوم نظام Kubernetes بقتل الحاوية وإعادة تشغيلها بلا رحمة. باختصار، فحص الجاهزية يوقف حركة المرور بشكل قابل للعكس، بينما فحص الحيوية يدمر العملية ويعيد إنشائها.
يتمثل الخطأ المعماري الأكثر شيوعاً في وضع فحص تبعية الشبكة داخل نقطة نهاية فحص الحيوية. يكتب العديد من المطورين نقاط نهاية تبدو مسؤولة ولكنها تعمل كقنبلة موقوتة موجهة نحو المجموعة:
@app.get("/healthz")
def healthz():
db.execute("SELECT 1") # check the database
cache.ping() # check redis
requests.get(AUTH_URL + "/health") # check the auth service
return "ok"تشريح انقطاع الخدمة الذاتي
عندما تواجه قاعدة بيانات مشتركة أو ذاكرة تخزين مؤقت تباطؤاً لفترة وجيزة، فإن كل حاوية تتحقق من تلك التبعية في فحص الحيوية الخاص بها ستفشل في نفس الوقت. يؤدي هذا إلى سلسلة مدمرة من الأحداث.
- تتباطأ قاعدة البيانات المشتركة لمدة ثلاث ثوانٍ.
- تبدأ كل حاوية تتحقق من قاعدة البيانات في فحص الحيوية الخاص بها في الفشل.
- يؤثر هذا على كل خدمة تشارك تلك التبعية في وقت واحد.
- يتم الوصول إلى حد الفشل (failureThreshold) عبر جميع الخدمات، مما يدفع نظام Kubernetes إلى قتل وإعادة تشغيل كل حاوية متأثرة.
- تبدأ عشرات الحاويات في بدء التشغيل البارد في وقت واحد، مما يعيد فتح تجمعات الاتصال وتسخين ذاكرة التخزين المؤقت.
- يخلق هذا تدافعاً جماعياً يضغط بشدة على التبعية التي تعاني بالفعل، مما يطيل أمد انقطاع الخدمة لفترة طويلة بعد انتهاء الخلل الأولي.
كيفية تصميم فحوصات مرنة
لمنع حلقة الملاحظات المدمرة هذه، يجب أن تكون فحوصات الحيوية محلية وأنانية تماماً. يجب أن تتحقق فقط مما إذا كانت العملية المحددة عالقة أو مستنفدة، دون الاعتماد أبداً على استدعاءات الشبكة الخارجية. غالباً ما يكون فحص الحيوية السليم بسيطاً للغاية:
@app.get("/livez")
def livez():
return "ok" # if the process can answer, it's aliveينتمي الوعي بالتبعيات حصرياً إلى فحص الجاهزية. إذا فشلت تبعية صلبة، فإن فشل الجاهزية يسحب الحاوية من التناوب دون تدمير العملية الأساسية. ومع ذلك، يجب أن تكون الفرق حذرة: إذا أبلغت كل حاوية عن عدم جاهزيتها بسبب خلل في تبعية مشتركة، فسيتم عزل الخدمة بأكملها. غالباً ما يكون البقاء في حالة جاهزية وإرجاع خطأ واضح أفضل من عدم وجود أي حاويات جاهزة.
ضبط معلمات الفحص لاستقرار النظام
غالباً ما تؤدي تكوينات YAML الافتراضية إلى حلقات انهيار لا نهائية، خاصة للتطبيقات بطيئة التشغيل. إذا استغرق التطبيق 40 ثانية للتمهيد، ولكن فحص الحيوية يبدأ في التحقق عند 10 ثوانٍ بحد أقصى 30 ثانية، فسيقوم نظام Kubernetes بقتله قبل أن يكمل بدء التشغيل.
يمنح تنفيذ فحص بدء التشغيل (Startup Probe) التطبيق وقتاً كافياً للتمهيد قبل أن تتولى فحوصات الحيوية والجاهزية المهمة. علاوة على ذلك، تحدد حدود الفشل وفترات الثواني نطاق تأثير أي خلل مؤقت.
startupProbe:
httpGet: { path: /livez, port: 8080 }
failureThreshold: 30 # 30 * 10s = up to 5 min to start
periodSeconds: 10
livenessProbe:
httpGet: { path: /livez, port: 8080 }
periodSeconds: 10
failureThreshold: 3 # only restart after a sustained, real failure
readinessProbe:
httpGet: { path: /readyz, port: 8080 }
periodSeconds: 5
failureThreshold: 2القائمة المرجعية قبل النشر
قبل إطلاق أي خدمة، يجب على الفرق الهندسية التحقق من هذه الشروط المحددة بدلاً من الاعتماد على التكوينات الافتراضية:
- يتحقق فحص الحيوية من العملية فقط، ولا يعتمد أبداً على تبعية الشبكة.
- يعكس فحص الجاهزية القدرة على خدمة الطلب من البداية إلى النهاية، مع مراعاة ما يحدث إذا فشلت جميع الحاويات في وقت واحد.
- يغطي فحص بدء التشغيل (Startup Probe) أسوأ حالة فعلية لوقت بدء التشغيل البارد.
- حد الفشل (failureThreshold) لفحص الحيوية يجب أن يكون سخياً؛ فأنت تعيد تشغيل العمليات العالقة، وليس البطيئة.
- نقاط نهاية الفحص غير مكلفة وتتجاوز مهلة الانتظار الخاصة بها زمن الاستجابة p99 تحت الضغط.
- تتم جدولة عمليات إعادة التشغيل أو تحديد معدلها على مستوى المنصة لمنع التدافع الجماعي.
- تم تصميم الخدمات لتخفيف الحمل أو التراجع بسلاسة عندما تكون التبعية المشتركة بطيئة لمدة 5 ثوانٍ، بدلاً من الانهيار التام.
التكلفة الخفية للأنماط المعمارية الخاطئة
في حين أن الألم المباشر لتهيئة فحص الحيوية بشكل خاطئ في نظام Kubernetes هو توقف الخدمة، فإن التأثير الثانوي يتمثل في ارتفاع هائل في تكاليف الحوسبة السحابية. عندما يجبر التدافع الجماعي عشرات الحاويات على بدء التشغيل البارد، يرتفع استهلاك المعالج (CPU Usage) بشكل كبير حيث يتم تهيئة المترجمات وتجمعات الاتصال في وقت واحد. غالباً ما يؤدي هذا إلى تشغيل التوسع التلقائي للمجموعة دون داعٍ، مما يضخم فواتير البنية التحتية بسبب انقطاع تسبب فيه النظام لنفسه.
علاوة على ذلك، يسلط هذا الضوء على فجوة حرجة في مسارات النشر الحديثة. تقضي الفرق أسابيع في تحسين أكواد التطبيقات ولكنها تنسخ وتلصق ملفات YAML الخاصة بفحوصات الصحة من خدمات قديمة. للمضي قدماً، يجب على فرق هندسة المنصات دمج أدوات التحليل الثابت الآلية في مسارات التكامل المستمر والتسليم المستمر (CI/CD) لمنع فحوصات الحيوية التي تحتوي على استدعاءات للشبكة بشكل صريح. لا ينبغي أبداً أن يحدد اليوم السيئ لإحدى التبعيات مصير عملياتك الداخلية.