OOMKilled يعني pod تجاوز حد الذاكرة والنواة أنهته (رمز الخروج 137). يحدث لأن حد الذاكرة منخفض جداً أو التطبيق به تسريب ذاكرة. يوضح هذا الدليل كيفية التشخيص وتحديد الحدود بشكل صحيح وفهم فئات QoS.
الأعراض
pod يعاد تشغيله بشكل متكرر و kubectl describe pod يظهر:
Name: example-app-5f7c9d4bfb-7x8kq
Namespace: default
Status: Running
Restarts: 12
Last State: Terminated
Reason: OOMKilled
Exit Code: 137
Signal: 9
Last Probe: 2m30s ago
Reason: None
بدلاً من ذلك، pod يظهر لا سبب OOMKilled ولكن ينطلق باستمرار برمز 137:
Exit Code: 137
Signal: 9
في kubectl get events:
pod-name OOMKilled The container was OOMKilled
قد يكون استخدام الذاكرة مرئياً في kubectl top pod:
NAME CPU(cores) MEMORY(bytes)
example-app-5f7c9d4bfb 100m 1536Mi # عند أو بالقرب من الحد
لماذا يحدث
مرتبة حسب الاحتمالية:
- حد الذاكرة منخفض جداً لعبء العمل العادي للتطبيق — التطبيق يحتاج لمزيد من الذاكرة لتخزين البيانات أو ذاكرة تخزين مؤقت أو معالجة الطلبات، لكن الحد لا يسمح بها.
- تسريب ذاكرة في التطبيق — التطبيق يخصص الذاكرة بمرور الوقت دون الإفراج عنها، يصل أخيراً للحد حتى لو كانت الحاجة الأولية أصغر.
- JVM أو heap وقت التشغيل غير مهيأ — Java أو Node.js أو Python يستخدمون جميع الذاكرة المتاحة بدلاً من احترام حجم heap صريح.
- ارتفاع مفاجئ في معالجة البيانات — وظيفة دفعية أو توليد تقرير يتطلب مؤقتاً أكثر من حد الذاكرة.
- لا توجد طلب ذاكرة معيّن — pod له حد لكن لا طلب، لذا Kubernetes يجدول على عقدة تبدو حرة لكنها تحت ضغط.
- فئة QoS هي Burstable، وليس Guaranteed — pod يمكن إخلاؤه إذا كانت العقدة ناقصة الذاكرة، حتى لو لم تتجاوز الحد تقنياً.
الحل
1. حد الذاكرة منخفض جداً
أولاً، قس كم من الذاكرة يستخدمها التطبيق فعلاً:
# ثبت metrics-server إذا لم يكن موجوداً (عادة مثبت مسبقاً على عناقيد السحابة)
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
# انتظر 1-2 دقيقة لتجميع المقاييس
sleep 120
# تحقق من الاستخدام الحي
kubectl top pod <pod-name>
# راقب بمرور الوقت (ابحث عن ذروات)
kubectl top pod <pod-name> --containers
سجّل استخدام الذاكرة بالذروة. ثم زد الحد بـ 20–50% كهامش أمان:
# إذا كانت الذروة 512 Mi، عيّن الحد إلى 768 Mi
kubectl set resources deployment <deployment-name> \
--limits=memory=768Mi \
--requests=memory=512Mi
أو حرّر Deployment مباشرة:
kubectl edit deployment <deployment-name>
وحدّث قسم الموارد:
containers:
- name: app
resources:
requests:
memory: "512Mi"
cpu: "100m"
limits:
memory: "768Mi" # زد من 512 Mi
cpu: "500m"
طبّق وتحقق:
kubectl rollout restart deployment/<deployment-name>
kubectl top pod <pod-name> # تحقق من pod جديد له ذاكرة كافية
2. تسريب ذاكرة في التطبيق
إذا كانت ذاكرة التطبيق تنمو بمرور الوقت حتى تصل للحد، يكون هناك تسريب. راقب الذاكرة لعدة ساعات أو أيام:
# جمّع استخدام الذاكرة كل 30 ثانية واحفظه في ملف
watch -n 30 "kubectl top pod <pod-name> >> memory-log.txt"
إذا كانت الذاكرة تنمو باستمرار، التطبيق له تسريب. أصلح التسريب في كود التطبيق:
- Java: استخدم heap dumps لإيجاد الكائنات المحتفظ بها:
jmap -dump:live,format=b,file=heap.bin <pid> - Node.js: استخدم clinic.js أو heap snapshots:
node --inspect=0.0.0.0:9229 app.js # ثم اتصل بـ DevTools وخذ heap snapshots - Python: استخدم memory_profiler أو objgraph لتتبع التخصيصات.
لحل بديل سريع أثناء إصلاح التسريب، عيّن حد ذاكرة أقل وزد سياسة الإعادة:
restartPolicy: Always
containers:
- name: app
resources:
limits:
memory: "512Mi" # حد أقل يفرض إعادة تشغيل أسرع
هذا ليس حلاً لكن يشتري وقتاً أثناء تصحيح التسريب الفعلي.
3. JVM أو runtime heap غير مهيأ
إذا كان التطبيق يعمل على JVM أو Node.js أو Python، هيّئ الـ heap أو حد الذاكرة صراحةً:
Java/JVM:
عيّن حجم الكومة في Dockerfile أو مواصفات pod:
FROM openjdk:17-slim
ENV JAVA_OPTS="-Xmx512m -Xms256m"
COPY app.jar /app.jar
ENTRYPOINT ["java", "$JAVA_OPTS", "-jar", "/app.jar"]
أو مرّره كمتغير بيئة:
containers:
- name: app
image: myapp:jvm
env:
- name: JAVA_OPTS
value: "-Xmx512m -Xms256m"
Node.js:
عيّن max-old-space-size:
FROM node:20-alpine
ENV NODE_OPTIONS="--max-old-space-size=512"
COPY . /app
WORKDIR /app
CMD ["node", "server.js"]
أو:
containers:
- name: app
env:
- name: NODE_OPTIONS
value: "--max-old-space-size=512"
Python:
بعض الأطر تخصص الذاكرة مسبقاً. تحقق إذا كان هناك تكوين ذاكرة:
containers:
- name: app
env:
- name: PYTHONUNBUFFERED
value: "1" # يساعد في التسجيل في الحاويات
أعد النشر:
kubectl rollout restart deployment/<deployment-name>
4. ارتفاع مفاجئ في معالجة البيانات
لوظائف دفعية أو توليد تقرير، استخدم فئة موارد منفصلة بحدود أعلى:
apiVersion: batch/v1
kind: Job
metadata:
name: heavy-report
spec:
template:
spec:
containers:
- name: report
image: myapp:latest
resources:
requests:
memory: "2Gi"
cpu: "1000m"
limits:
memory: "3Gi"
cpu: "1500m"
restartPolicy: Never
للتطبيقات طويلة المدى التي ترتفع أحياناً، فكّر في استخدام Vertical Pod Autoscaler (VPA) لضبط الحدود تلقائياً بناءً على الاستخدام التاريخي:
# ثبت VPA (إذا لم يكن مثبتاً بالفعل)
git clone https://github.com/kubernetes/autoscaler.git
cd autoscaler/vertical-pod-autoscaler
./hack/vpa-up.sh
# ثم ضيّق Deployment
kubectl annotate deployment <deployment-name> \
vpa.k8s.io/updateMode=auto \
vpa.k8s.io/updatePolicy.updateMode=auto
5. لا طلب ذاكرة معيّن
إذا عيّنت حداً لكن لا طلب، Kubernetes يعامل الطلب كمساوٍ للحد. هذا يسبب مشاكل جدولة. عيّن دائماً كليهما:
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
القاعدة الذهبية:
– الطلب = أدنى ذاكرة مطلوبة (للجدولة)
– الحد = الأقصى التطبيق يجب استخدامه (للتنفيذ)
– النسبة: الحد يجب أن يكون 1.5–2x الطلب
حدّث Deployment:
kubectl set resources deployment <deployment-name> \
--requests=memory=256Mi,cpu=100m \
--limits=memory=512Mi,cpu=500m
6. فئة QoS هي Burstable بدلاً من Guaranteed
تحقق من فئة QoS:
kubectl get pod <pod-name> -o yaml | grep qosClass
ثلاث فئات QoS موجودة:
– Guaranteed: الطلبات == الحدود (أعلى أولوية، لا تُخلى أبداً للذاكرة)
– Burstable: الطلبات < الحدود (أولوية متوسطة، يمكن إخلاؤها)
– BestEffort: لا طلبات أو حدود (أقل أولوية، تُخلى أولاً)
لتطبيقات الإنتاج، استخدم Guaranteed:
resources:
requests:
memory: "512Mi"
cpu: "100m"
limits:
memory: "512Mi" # نفس الطلب
cpu: "100m"
طبّق وأعد التشغيل:
kubectl apply -f deployment.yaml
kubectl rollout restart deployment/<deployment-name>
كيف تتجنبه
-
قِس دائماً قبل تعيين الحدود:
kubectl top pod <pod-name>عيّن الطلبات والحدود بناءً على الاستخدام بالذروة المرصود بالفعل، وليس التخمينات.
-
عيّن الطلبات والحدود معاً، لا أحدهما فقط:
resources: requests: memory: "256Mi" limits: memory: "512Mi" -
هيّئ JVM/runtime heap صراحةً:
ENV JAVA_OPTS="-Xmx512m -Xms256m" ENV NODE_OPTIONS="--max-old-space-size=512" -
فعّل metrics-server لمراقبة استخدام الذاكرة:
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml -
استخدم Vertical Pod Autoscaler (VPA) للتحديد الحجمي التلقائي:
apiVersion: autoscaling.k8s.io/v1 kind: VerticalPodAutoscaler metadata: name: example-vpa spec: targetRef: apiVersion: apps/v1 kind: Deployment name: example-app updatePolicy: updateMode: "Auto" -
ملخّص التطبيق لتسرب الذاكرة أثناء التطوير:
– Java: heap dumps
– Node.js: clinic.js أو DevTools
– Python: memory_profiler -
عيّن مسابير البدء لإعطاء التطبيق وقتاً لتخصيص الذاكرة الأولية:
startupProbe: httpGet: path: /health port: 8080 failureThreshold: 30 periodSeconds: 10
الأسئلة الشائعة
كيف أعرف إذا كان pod قد تم OOMKilled؟
شغّل kubectl describe pod <pod> وابحث عن ‘Reason: OOMKilled’ في Last State، أو رمز الخروج 137. تحقق أيضاً من kubectl get events.
لماذا حد الذاكرة الخاص بـ pod مختلف عما وضعته؟
Kubernetes يعيّن الحدود والطلبات بشكل منفصل. إذا عيّنت حداً فقط، يُعيّن الطلب للحد. اعرض كليهما مع kubectl get pod -o yaml | grep -A 3 resources:.
كيف أقيس كم من الذاكرة يستخدمه التطبيق فعلاً؟
استخدم kubectl top pod <pod> لترى استخدام الذاكرة الحي. إذا لم يكن metrics-server مثبتاً، ثبته أولاً (عادة مثبت مسبقاً على عناقيد السحابة).
ما الفرق بين الطلبات والحدود؟
الطلبات ضمانات (للجدولة)؛ الحدود أغطية (للتنفيذ). إذا تجاوز pod حده، يتم OOMKilled. عيّن دائماً كليهما، مع حد 1.5–2x الطلب.
كيف تم التحقق من هذا الدليل
كُتب يدويًا من الوثائق الرسمية، وأُعيد فحص كل أمر، ثم رُوجع مرة ثانية قبل النشر. آخر مراجعة في .
المصادر الرسمية التي قُرئت لهذا المقال

