هذا المقال يتطلب مستوى: محترف
ليه الـ Pod بيتقتل بـ OOMKilled والعقدة نصها فاضي؟
لو الـ Pod بيتقتل ويرجع بـ OOMKilled رغم إن العقدة (Node) نصها ذاكرة فاضية، المشكلة مش في حجم العقدة. المشكلة في الـ memory limit اللي انت حاططه على الحاوية. المقال ده هيوريك بالظبط ليه بيحصل، وإزاي تظبط الـ Requests والـ Limits علشان الخدمة تثبت.
المشكلة باختصار
الـ limit مش بيتحسب على مستوى العقدة، بيتحسب على مستوى الحاوية لوحدها. يعني ممكن العقدة عندها 32 جيجا فاضية، ولسه الحاوية بتتقتل لأنها عدّت الـ 512 ميجا اللي انت سامحلها بيها. الـ kernel بيشوف الحاوية بس، مش الصورة الكبيرة.
الفكرة بمثال بسيط الأول
تخيّل مطعم مزحوم. الـ request هو عدد الكراسي المحجوزة باسمك، مضمونة مهما حصل. الـ limit هو أقصى عدد كراسي مسموح تسحبها لطاولتك. لو عدّيت الحد ده، البواب بيشيلك بره على طول من غير تفاوض. ولو المطعم كله اتزحم وقلّت الكراسي، المدير بيطرد الأول اللي قاعد من غير حجز أصلًا.
الـ kernel هو البواب، والـ kubelet هو المدير. تعالى نشوفها علميًا.
الفرق بين Request و Limit علميًا
الـ request بيستخدمه الـ scheduler وقت التوزيع. هو بيقول للعقدة "احجزلي القدر ده مضمون" وعلى أساسه بيتقرر الـ Pod ينزل على أنهي عقدة (bin-packing). الـ limit حاجة تانية خالص: ده سقف بيتفرض عبر الـ cgroup على مستوى الـ kernel. في cgroup v2 القيمة دي اسمها memory.max.
الفرق الجوهري: تجاوز الـ CPU limit بيعمل throttling يعني بطء مؤقت. تجاوز الـ memory limit مفيش فيه بطء، فيه قتل. الذاكرة مش قابلة للـ throttle، فالـ kernel بيطلّق الـ OOM killer اللي بيقتل العملية فورًا.
ليه Exit Code بيطلع 137؟
لما الـ OOM killer يقتل العملية، بيبعتلها SIGKILL (الإشارة رقم 9). الحاوية بتخرج بكود 128 + 9 = 137. ده مش رقم عشوائي، ده توقيعك إن السبب قتل بالذاكرة. تشوفه كده:
# شوف آخر حالة للحاوية وسبب موتها
kubectl describe pod my-app-7d9f | grep -A6 "Last State"
# المتوقع تشوف:
# Last State: Terminated
# Reason: OOMKilled
# Exit Code: 137
# قيس الاستهلاك الفعلي دلوقتي
kubectl top pod my-app-7d9fفئات الجودة (QoS) ومين بيتقتل الأول
وقت ضغط الذاكرة على العقدة، الـ kubelet بيطرد الـ Pods بترتيب حسب فئة الجودة بتاعتها. الترتيب ده بيتحدد أوتوماتيك من الـ Requests والـ Limits:
| الفئة | الإعداد | ترتيب الطرد عند الضغط |
|---|---|---|
| Guaranteed | request = limit للذاكرة والمعالج | آخر واحد يُطرد — أعلى ثبات |
| Burstable | request أقل من limit | عرضة للطرد لو عدّى الـ request |
| BestEffort | لا request ولا limit | أول ضحية — يُطرد فورًا |
خلي بالك من الفرق: الـ OOMKilled بيحصل لما الحاوية تعدّي الـ limit بتاعها هي (حتى لو العقدة مرتاحة). أما الطرد (Eviction) حسب الـ QoS فبيحصل لما العقدة نفسها تحت ضغط ذاكرة.
الحل عمليًا
الافتراض هنا إنك على cgroup v2 و Kubernetes 1.28 أو أحدث (الوضع الافتراضي على أغلب التوزيعات الحديثة). خد سيناريو واقعي: خدمة Node.js الـ working set بتاعها حوالي 480Mi، وبتقفز لـ 600Mi وقت الذروة. لو حاطط limit على 512Mi، هتتقتل كل مرة الحمل يعلى. الحل بترتيب أولوية:
- اقيس الاستهلاك الحقيقي وقت الذروة بـ
kubectl topعلى مدى يوم، مش لحظة. - حط الـ memory limit فوق أعلى working set بهامش 20-30% (يعني هنا 768Mi).
- خلي الـ memory request مساوي للـ limit علشان تدخل فئة Guaranteed وتبقى آخر واحد يُطرد.
resources:
requests:
memory: "768Mi"
cpu: "250m"
limits:
memory: "768Mi" # request = limit للذاكرة = Guaranteed
# لاحظ: مفيش cpu limit عن قصد — اقرا الـ trade-off تحتالنتيجة في السيناريو ده: من قتل متكرر عند كل ذروة، لصفر OOMKills، بزيادة حجز ذاكرة قدرها 256Mi لكل نسخة.
الـ trade-offs
خلي الـ request مساوي للـ limit بيديك ثبات وأولوية أعلى، بس بتخسر كثافة التعبئة: العقدة بتحجز الـ 768Mi كاملة حتى لو الحاوية مستخدمة 480Mi دلوقتي، فبتقدر تركّب Pods أقل على نفس العقدة.
أما الـ CPU limit فقصة تانية. الـ trade-off هنا إن فرض CPU limit بيعمل throttling حتى لو المعالج فاضي، وده بيزوّد الـ latency من غير فايدة غالبًا. الأفضل تحط CPU request بس (علشان الجدولة) وتسيب المعالج يوصل لأقصاه لما يكون متاح. الذاكرة عكسها: دايمًا حط لها limit، لأن غيابه بيخلّي حاوية واحدة تبلع ذاكرة العقدة كلها وتوقّع الباقي.
متى لا تستخدم هذه الطريقة
خلي الـ request = limit مش دايمًا صح. في بيئات الـ dev أو الـ batch اللي الكثافة فيها أهم من الثبات، سيب فرق بين request و limit (Burstable) علشان تركّب Pods أكتر. وكمان لو خدمتك استهلاكها متذبذب جدًا وصعب تتوقعه، ابدأ Burstable مع مراقبة، وثبّتها بعد ما تجمع بيانات حقيقية. تثبيت request = limit على تخمين غلط بيهدر موارد كتير.
الخطوة التالية
روح على الخدمة اللي بتتقتل عندك، شغّل kubectl describe pod ودوّر على Exit Code: 137. لو لقيته، اقيس الـ working set بـ kubectl top وقت الذروة، وحط الـ memory limit فوقه بـ 30% مع request مساوي ليه. لو لسه بيتقتل بعد كده، المشكلة بقت تسريب ذاكرة في التطبيق نفسه مش في الإعداد.
المصادر
- Kubernetes — Resource Management for Pods and Containers: kubernetes.io/docs/concepts/configuration/manage-resources-containers
- Kubernetes — Pod Quality of Service Classes: kubernetes.io/docs/concepts/workloads/pods/pod-qos
- Kubernetes — Node-pressure Eviction: kubernetes.io/docs/concepts/scheduling-eviction/node-pressure-eviction
- Linux Kernel — Control Group v2 (Memory Controller / memory.max): docs.kernel.org/admin-guide/cgroup-v2.html