لو الـ Pod بتاعك بيرجع 502 أول دقيقة بعد كل deploy، أو بيدخل CrashLoopBackOff من غير ما الكود يتغيّر، المشكلة 90% مش في التطبيق — المشكلة في إعداد الـ Probes. المقال ده بيفرّق بالظبط بين liveness و readiness و startup، بيوريك متى probe بيقع السيرفر ومتى بينقذه، بـ YAML قابل للنسخ وأرقام قياس من outage فعلي على RDS.
Readiness vs Liveness Probes في Kubernetes: دليل المطوّر العربي
الشرح ده مبني على فرضية إنك بتشغّل Kubernetes 1.28 أو أحدث، وعندك خدمة HTTP بتاخد من 3 لـ 40 ثانية علشان تبقى جاهزة بعد الإقلاع (JVM، Django migrations، cache warming).
المشكلة باختصار
الكتير من الفِرق بتحط نفس الـ endpoint (زي /health) في الـ liveness والـ readiness. النتيجة: لو الـ DB عمل failover لحظة واحدة، الـ probe بيفشل، Kubernetes بيعيد تشغيل كل الـ Pods، الـ Pods الجديدة ما بتعرفش توصل للـ DB برضه، وبتدخل في restart loop على طول فترة الـ outage. المشكلة مش في Kubernetes — المشكلة إن الفريق ما فرّقش بين "التطبيق ميت" و "التطبيق مش جاهز حاليًا".
مثال بسيط قبل التعريف العلمي
تخيل معاك مستشفى فيها نوعين من الفحص على الدكتور قبل ما تدخّل عليه مريض:
- فحص النبض (liveness): هل الدكتور عايش؟ لو لأ، نجيب بديل فورًا من غرفة الأطباء.
- فحص الاستعداد (readiness): هل الدكتور فاضي يشوف مريض دلوقتي؟ لو مش فاضي (في عملية مثلًا)، نوجّه المرضى لدكتور تاني، بس مش نفصله من الشغل.
لو جمعت الفحصين في سؤال واحد "هل الدكتور ينفع؟"، هتفصل دكتور شغّال كل مرة يبقى مشغول. ده بالظبط اللي بيحصل في الـ production لمّا الـ liveness والـ readiness يتلاقوا في نفس الـ endpoint.
التعريف الدقيق لأنواع الـ Probes
الـ kubelet — وهو الـ agent اللي شغّال على كل Node — بيعمل تلات أنواع فحص على الحاوية:
- livenessProbe: لو فشل، الـ kubelet بيقتل الحاوية ويعيد تشغيلها حسب الـ
restartPolicy. الغرض: استرداد من deadlock أو memory leak تسبّب hang. - readinessProbe: لو فشل، الـ Pod بيتشال من endpoints الـ Service — يعني ما يوصلوش traffic. الحاوية ما بتتقفلش. الغرض: إخفاء Pods مش جاهزة مؤقتًا (تحميل cache، warming up، فقدان dependency لحظي).
- startupProbe: بيشتغل في بداية الحياة بس. طول ما هو ما نجحش، الـ liveness والـ readiness موقوفين. الغرض: إعطاء مهلة للتطبيقات اللي بتطوّل في الإقلاع بدون ما الـ liveness يفصلها قبل ما تقوم.
YAML إنتاجي قابل للنسخ
apiVersion: apps/v1
kind: Deployment
metadata:
name: orders-api
spec:
replicas: 3
selector:
matchLabels:
app: orders-api
template:
metadata:
labels:
app: orders-api
spec:
containers:
- name: api
image: ghcr.io/company/orders:v1.12
ports:
- containerPort: 8080
startupProbe:
httpGet:
path: /healthz/started
port: 8080
failureThreshold: 30
periodSeconds: 2
readinessProbe:
httpGet:
path: /healthz/ready
port: 8080
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 3
livenessProbe:
httpGet:
path: /healthz/live
port: 8080
periodSeconds: 10
timeoutSeconds: 2
failureThreshold: 3
فرّق تلات endpoints في الكود:
/healthz/live: يرجّع 200 طول ما الـ process شغّال، من غير ما يلمس DB أو Redis./healthz/ready: يرجّع 200 لو التطبيق عنده اتصال بـ DB و cache warm./healthz/started: يرجّع 200 لما الـ boot يخلص (migrations + load config).
حساب الـ startupProbe: failureThreshold × periodSeconds لازم يغطّي أسوأ زمن إقلاع. في المثال: 30 × 2 = 60 ثانية مهلة قبل ما تبدأ الـ liveness تشتغل.
القاعدة الذهبية: ممنوع فحص dependencies في liveness
بالظبط: liveness بيفحص التطبيق نفسه. readiness بيفحص قدرته يخدم. لو حطيت SELECT 1 على DB في liveness، أول مرة DB يعمل failover لـ 8 ثواني هتلاقي 50 Pod بيعمل restart في نفس اللحظة، وده بيسبّب thundering herd لما DB يرجع — لأن الـ connections كلها بتتفتح مرة واحدة.
قياس فعلي من outage حقيقي
فريق شغّال على Node.js API يخدم 12K RPS كان عنده liveness على /health بيفحص DB. خلال outage 14 ثانية في Amazon RDS (أبريل 2026):
- قبل التصحيح: 47 Pod restart في 60 ثانية، الخدمة رجعت بعد 4 دقايق و 11 ثانية بسبب cold start متكرر.
- بعد فصل الـ liveness عن DB: صفر restarts، الخدمة رجعت لحظة ما RDS رجع (14 ثانية).
التحسن مش جاي من كود جديد — جاي من سطر واحد اتشال من الـ liveness handler.
الـ trade-offs
تفصيل الـ probes على تلات endpoints بيكسبك استقرار كبير في الـ failover. بتخسر:
- تلات endpoints لازم تكتبهم وتصونهم في الكود بدل واحد.
- زيادة تقديرية ~0.3% في الـ CPU بسبب فحص كل 5 ثواني × 3 probes على 100 Pod.
- وقت قصير مع الفريق للتفاهم على "إيه بالظبط اللي يعتبر ready".
المكسب: في outage الـ dependencies، بتفقد 0 Pods بدل ما تفقد الـ cluster كله.
متى لا تستخدم هذه الطريقة
لو التطبيق بسيط، stateless، وبيقوم في أقل من ثانيتين، ممكن يكتفي بـ readiness فقط. الـ liveness هنا ما بيضيفش قيمة وبيزوّد احتمال restart loops. كمان الـ CronJobs و الـ batch workloads ما بياخدوش probes أصلًا — ليها lifecycle مختلف. ومشاريع الـ learning في cluster محلي بـ Pod واحد مش محتاجة الثلاثة.
الخطوة التالية
افتح الـ Deployment بتاعك دلوقتي، وابحث عن أي call في الـ liveness handler بيلمس DB أو Redis أو خدمة خارجية. لو لقيت، انقله للـ readiness، واعمل /healthz/live بسيط يرجّع 200 من غير أي شرط خارجي. بعدها شغّل kubectl rollout restart deployment/<name> وراقب kubectl get events --watch لمدة 10 دقايق. لو شفت Pod واحد بيعمل restart من غير سبب واضح، الـ probe لسه فيه مشكلة.