مستوى المقال: متوسط — مناسب لمن يشغّل خدمات على Kubernetes ويعرف أساسيات الـ Pod والـ YAML، لكن لسه بيتلخبط في فرق الـ requests عن الـ limits.
OOMKilled و Exit 137: ليه الـ Pod بتاعك بيموت فجأة وإزاي تمنعه
لو فتحت kubectl get pods ولقيت خدمتك عمّالة تعمل Restart والحالة OOMKilled أو الكود 137، خلّيني أوفر عليك ساعة تشخيص: المشكلة مش في كودك ولا في السيرفر، الحاوية تخطّت حدّ الذاكرة المسموح بيه فالكيرنل قتلها في لحظة. في المقال ده هتعرف ليه بيحصل بالظبط، تكتشفه بأمر واحد، وتصلّحه صح.
المشكلة باختصار
الـ Pod بيشتغل تمام لمدة، وفجأة يموت من غير رسالة خطأ في اللوج. تفتح تلاقي reason: OOMKilled و exitCode: 137. ده مش عطل عشوائي. ده الكيرنل بيطبّق حدّ ذاكرة إنت (أو الـ default) حطيته. لو الحاوية عدّت الحدّ، بترمي إشارة SIGKILL فورًا. مفيش مهلة، مفيش فرصة تنظيف، مجرد قتل نظيف.
مثال يقرّبها قبل التعريف العلمي
تخيّل إنك رايح المطار وحقيبتك مسموح لها 23 كيلو. لو وزنها 23 كيلو أو أقل، تعدّي عادي. لو بقت 30 كيلو، الموظف بيوقفك عند البوابة على طول. مش بيقولك خفّف شوية، ومش بيديك مهلة. الحدّ حدّ.
الـ limit في Kubernetes هو نفس الميزان بالظبط. إنت بتقول للكلاستر: الحاوية دي مسموح لها 512 ميجا ذاكرة. طول ما هي تحت الرقم ده، ماشي. أول ما تلمسه، الكيرنل بيوقفها زي موظف البوابة. الفرق الوحيد إن الكيرنل أسرع وأقسى.
التفسير العلمي: cgroups والـ OOM Killer
كل حاوية بتشتغل جوّه مجموعة تحكّم اسمها cgroup (control group). الـ cgroup بتحدّد سقف الذاكرة عبر قيمة memory.max في cgroups v2. لمّا استهلاك الحاوية يوصل السقف ده، الكيرنل بيشغّل الـ OOM Killer اللي بيختار العملية ويبعتلها SIGKILL (الإشارة رقم 9).
وليه الكود 137 بالتحديد؟ لأن اتفاقية يونكس بتقول إن العملية اللي بتموت بإشارة بترجّع كود = 128 + رقم الإشارة. يعني 128 + 9 = 137. فأي مرة تشوف 137 اعرف على طول إنها SIGKILL، وفي سياق الحاويات ده غالبًا نفاد ذاكرة.
الفرق اللي بيلخبط الكل: request مقابل limit
دي أهم نقطة في الموضوع كله. الاتنين مختلفين تمامًا:
- request: الذاكرة المضمونة اللي المجدوِل بيحجزها للحاوية على العقدة. بيتحكّم في المكان اللي الـ Pod يترمي فيه.
- limit: السقف الأقصى. لو عدّيته، OOMKilled. بيتحكّم في القتل.
الافتراض هنا إن عندك عقدة فيها ذاكرة محدودة وأكتر من Pod بيتشاركوها. لو حطّيت limit أقل من الاستهلاك الحقيقي وقت الضغط، هتتقتل. ولو حطّيته عالي جدًا لكل الخدمات، ممكن العقدة نفسها تدخل في OOM وتقتل خدمات مش ليها ذنب. الـ trade-off حقيقي: رقم صغير يخنقك، رقم كبير يهدر ويخاطر بالجار.
كمان نسبة الـ request للـ limit بتحدّد فئة الجودة (QoS): لو الاتنين متساويين تبقى Guaranteed وهي آخر واحدة الكيرنل يفكّر يقتلها تحت الضغط. لو الـ limit أعلى من الـ request تبقى Burstable. ولو مفيش الاتنين خالص تبقى BestEffort وهي أول ضحية.
إزاي تكتشفها في دقيقة
أول أمر تشغّله على الـ Pod اللي بيقع:
# شوف سبب آخر إنهاء للحاوية
kubectl describe pod <pod-name> | grep -A5 "Last State"
# المخرج المتوقّع:
# Last State: Terminated
# Reason: OOMKilled
# Exit Code: 137وعلشان تشوف كام مرة اتقتلت فعلًا واستهلاكها الحالي:
kubectl get pod <pod-name> -o jsonpath='{.status.containerStatuses[0].restartCount}'
kubectl top pod <pod-name> # محتاج metrics-server مثبّتسيناريو واقعي: خدمة API بتستقبل حوالي 50 ألف طلب في الدقيقة، الـ limit عندها 512Mi. تحت أوقات الذروة الـ heap بيوصل 648Mi بسبب buffering للطلبات، فبتتقتل كل 3 أو 4 دقايق، والـ restartCount يعدّي 200 في اليوم. النتيجة: أخطاء 502 متقطّعة يشوفها المستخدم، ومحدش لاقي سبب في لوج التطبيق لأن القتل بيحصل من برّه.
إزاي تصلحها صح
- قيس الاستهلاك الحقيقي وقت الذروة، مش وقت الراحة. استخدم kubectl top أو Prometheus على مدى يوم على الأقل.
- حطّ الـ request على متوسط الاستهلاك، والـ limit فوق أعلى قمة شُفتها بهامش 20 إلى 30%.
- طبّق الإعداد ده كنقطة بداية لخدمة قمتها 650Mi:
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "850Mi"
cpu: "1"لو المشكلة تسرّب ذاكرة حقيقي مش مجرد حدّ منخفض، رفع الـ limit بيأجّل الموت مش بيمنعه. ساعتها الحل يبقى في الكود، والـ limit دوره إنه يحمي باقي العقدة لحدّ ما تصلّح التسريب.
المقايضات اللي لازم تعرفها
ضبط الـ limit عالي بيدّيك استقرار أكتر، لكن بيقلّل عدد الـ Pods اللي العقدة تقدر تشيلهم، فبتدفع عقد زيادة. تقديريًا، رفع limit كل خدمة من 512Mi لـ 850Mi على 40 خدمة معناه حجز حوالي 13GB إضافية، يعني ممكن عقدة كاملة زيادة. المكسب: صفر OOMKilled. الخسارة: فاتورة أعلى. اختار حسب حساسية خدمتك للتوقّف.
متى لا تستخدم هذه الطريقة
لو خدمتك batch job بيخلّص وينتهي، أو أداة تشغّلها مرة، متتعبش في ضبط limits دقيقة؛ الافتراضات بتكفي. وكمان لو بتشتغل على كلاستر تطوير محلي على جهازك، الضغط الحقيقي مش موجود فالضبط الدقيق مضيعة وقت. الحكاية دي تهمّك في الإنتاج تحت حمل حقيقي بس.
الخطوة التالية
روح دلوقتي على أكتر Pod عنده restartCount عالي، شغّل عليه kubectl describe pod ودوّر على OOMKilled. لو لقيتها، قيس قمة استهلاكه بـ kubectl top pod على مدار يوم، وعدّل الـ limit فوق القمة بهامش 25%. لو الـ restartCount وقف بعدها، يبقى المشكلة اتحلّت؛ لو فضل بيزيد، عندك تسرّب ذاكرة في الكود محتاج تشخيص أعمق.
المصادر
- Kubernetes Documentation — Resource Management for Pods and Containers (requests و limits).
- Kubernetes Documentation — Configure Quality of Service for Pods (فئات Guaranteed / Burstable / BestEffort).
- Kubernetes Documentation — Assign Memory Resources to Containers and Pods (سلوك OOMKilled عند تخطّي الحدّ).
- The Linux Kernel Documentation — Control Group v2 (قيمة memory.max وآلية الـ OOM على مستوى الـ cgroup).
- اتفاقية أكواد الخروج في يونكس: كود الإنهاء بإشارة = 128 + رقم الإشارة، ومنها SIGKILL (9) ← 137.