عندما يدخل pod في Kubernetes إلى حالة CrashLoopBackOff، تبدأ الحاوية ثم تنطلق فوراً. ينتظر Kubernetes، يرى الانطلاق، وينعشها مرة أخرى—حلقة بلا نهاية. يوضح هذا الدليل كيفية العثور على السبب الجذري وإصلاحه.

الأعراض

حالة pod تظهر CrashLoopBackOff. عدد الإعادات مستمر في الزيادة. الأحداث تظهر رسائل مثل:

Name:           example-app-5f7c9d4bfb-7x8kq
Namespace:      default
Status:         Running
Restarts:       47 (2 minutes ago)
Last State:     Terminated
  Reason:       Error
  Exit Code:    1

أو في kubectl get events:

pod-name   CrashLoopBackOff   exit status 1
pod-name   BackOff            Back-off restarting failed container

لماذا يحدث

مرتبة حسب الاحتمالية:

  1. التطبيق يتعطل عند البدء — الكود يحتوي على خطأ، يرمي استثناء غير معالج، أو ينهار قبل الوصول لحلقة الخادم الرئيسية.
  2. أمر أو وسائط خاطئة — command أو args في مواصفات pod خاطئة، تفتقد علامات اقتباس، أو تشير لملف تنفيذي غير موجود.
  3. متغير بيئة مفقود، ملف إعدادات، أو سري — التطبيق يتوقع ملف إعدادات أو متغير بيئة غير مثبت أو معين.
  4. مسبار الحياة أو بدء التشغيل يفشل فوراً — يقتل Kubernetes pod معتقداً أنه غير صحي، حتى وإن لم ينته من البدء.
  5. نفاد الذاكرة (OOM) — التطبيق يخصص ذاكرة كثيرة جداً بسرعة والنواة تنهيه (رمز الخروج 137).
  6. التبعية غير جاهزة — التطبيق يحاول الاتصال بقاعدة بيانات أو خدمة لم تبدأ بعد ويفشل بشدة بدلاً من الإعادة.
  7. إذن مرفوض أو مشكلة نظام ملفات — التطبيق لا يستطيع الكتابة للدلائل المطلوبة أو قراءة ملفات الإعدادات بسبب ملكية أو قواعد الإذن.

الحل

1. التطبيق يتعطل عند البدء

احصل على رسالة الخطأ الفعلية:

# اعرض آخر إخراج قبل التعطل
kubectl logs <pod-name> --previous

# كرّر أمر السجلات عدة مرات لمراقبة دورات إعادة تشغيل متعددة
kubectl logs <pod-name> --previous
kubectl logs <pod-name> --previous

إذا أعادت --previous لا شيء (لا توجد حاوية سابقة)، تعطل pod بسرعة جداً بحيث لم تُكتب السجلات. استخدم حاوية تصحيح مؤقتة لفحص نظام الملفات:

# ابدأ pod تصحيح بنفس نقاط التثبيت والصورة الأساسية
kubectl debug <pod-name> -it --image=<same-base-image>

داخل حاوية التصحيح، تحقق من:
– أخطاء البناء في نصوص البدء: cat /app/start.sh | bash -x
– المكتبات المفقودة: ldd /app/binary
– هيكل الدليل: ls -la /app

2. أمر أو وسائط خاطئة

افحص الأمر الدقيق:

kubectl get pod <pod-name> -o yaml

ابحث عن حقول command وargs:

containers:
- name: app
  command: ["/bin/sh"]
  args: ["-c", "node /app/server.js --port=8080"]

الأخطاء الشائعة:
– علامة وقيمتها منقسمة بالخطأ: args: ["--config", "=/etc/app/config.yaml"] يجب أن تكون args: ["--config=/etc/app/config.yaml"] أو args: ["--config", "/etc/app/config.yaml"]
– مسار خاطئ: command: ["/bin/bash"] لكن الصورة مبنية على Alpine (استخدم /bin/sh بدلاً من ذلك)
– شرطة مفقودة: command: ["node"] بدلاً من command: ["/usr/bin/node"]

أصلحها في Deployment:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: example-app
spec:
  template:
    spec:
      containers:
      - name: app
        image: example:latest
        command: ["/bin/sh"]
        args: ["-c", "exec node /app/server.js"]

ثم أعد النشر:

kubectl rollout restart deployment/example-app

3. متغير بيئة مفقود، ملف إعدادات، أو سري

تحقق مما يتوقعه التطبيق:

kubectl logs <pod-name> --previous | grep -i "not found\|undefined\|missing"

تحقق من وجود جميع متغيرات البيئة والتثبيتات:

kubectl get pod <pod-name> -o yaml | grep -A 20 "env:\|volumeMounts:"

أضف الإعدادات المفقودة كـ ConfigMap:

# أنشئ ConfigMap من ملف
kubectl create configmap app-config --from-file=config.yaml

ثبتها في Deployment:

containers:
- name: app
  volumeMounts:
  - name: config
    mountPath: /etc/app
    readOnly: true
volumes:
- name: config
  configMap:
    name: app-config

للأسرار (مفاتيح API، كلمات المرور)، استخدم Secret:

kubectl create secret generic app-secrets --from-literal=DB_PASSWORD=mypass

ثم ارجع إليه:

containers:
- name: app
  env:
  - name: DB_PASSWORD
    valueFrom:
      secretKeyRef:
        name: app-secrets
        key: DB_PASSWORD

أعد النشر:

kubectl rollout restart deployment/example-app

4. مسبار الحياة أو بدء التشغيل يفشل فوراً

المسبار يقتل pod قبل أن يكون لديه وقت للبدء. تحقق من المسبار الحالي:

kubectl get pod <pod-name> -o yaml | grep -A 10 "livenessProbe:\|startupProbe:"

المشكلة الأكثر شيوعاً: initialDelaySeconds قصير جداً. إذا استغرق التطبيق 10 ثوان للبدء والمسبار يعمل بعد 5 ثوان، سيفشل و pod سيعاد تشغيله.

أصلحها في Deployment:

containers:
- name: app
  livenessProbe:
    httpGet:
      path: /health
      port: 8080
    initialDelaySeconds: 30        # أعط التطبيق وقتاً للبدء
    periodSeconds: 10              # تحقق كل 10 ثوان
    failureThreshold: 3            # أعد التشغيل بعد 3 فشل
  startupProbe:
    httpGet:
      path: /health
      port: 8080
    failureThreshold: 30           # السماح بما يصل إلى 300 ثانية (30 * 10s) للأصحة
    periodSeconds: 10

لقاعدة بيانات تستغرق وقتاً للتهيئة:

startupProbe:
  exec:
    command: ["pg_isready", "-h", "localhost"]
  failureThreshold: 30
  periodSeconds: 10
livenessProbe:
  exec:
    command: ["pg_isready", "-h", "localhost"]
  initialDelaySeconds: 5
  periodSeconds: 10

أعد النشر والمراقبة:

kubectl rollout restart deployment/example-app
kubectl describe pod <pod-name>

5. نفاد الذاكرة (OOM)

رمز الخروج 137 مع السبب OOMKilled يعني أن النواة أنهت العملية لاستخدام ذاكرة كثيرة جداً. تحقق من استخدام الذاكرة:

# إذا كان metrics-server مثبتاً
kubectl top pod <pod-name>

# أو وصف لترى إذا كان OOMKilled في الحالة
kubectl describe pod <pod-name> | grep -A 5 "Last State"

مواصفات pod على الأرجح تحتوي على حد ذاكرة منخفض جداً. زده:

containers:
- name: app
  resources:
    requests:
      memory: "256Mi"
      cpu: "100m"
    limits:
      memory: "512Mi"        # زد هذا
      cpu: "500m"

إذا كان لديك تطبيق JVM، عيّن أيضاً حدود الكومة:

JAVA_OPTS="-Xmx512m -Xms256m"

لـ Node.js، عيّن max old space size:

node --max-old-space-size=512 app.js

طبّق المواصفات المحدّثة:

kubectl apply -f deployment.yaml
kubectl rollout restart deployment/example-app

6. التبعية غير جاهزة

التطبيق يفشل بشدة عندما لا يتمكن من الاتصال بقاعدة بيانات أو خدمة أخرى عند البدء. استخدم init container للانتظار للتبعية:

containers:
- name: app
  image: example:latest
initContainers:
- name: wait-for-db
  image: busybox:1.36
  command: ['sh', '-c', 'until nc -z postgres.default.svc.cluster.local 5432; do echo waiting for postgres; sleep 2; done']

أو أضف منطق الإعادة لبدء التطبيق:

for i in {1..30}; do
  if psql -h postgres.default.svc.cluster.local -U user -d mydb -c "SELECT 1"; then
    break
  fi
  echo "Database not ready, retry $i/30"
  sleep 2
done
exec node /app/server.js

تأكد من وجود Service للتبعية وتشغيله:

kubectl get svc postgres
kubectl get pod -l app=postgres

7. إذن مرفوض أو مشكلة نظام ملفات

التطبيق لا يستطيع قراءة أو كتابة ملفات مطلوبة. تحقق من المستخدم الذي يعمل عليه pod وأذونات الملف:

kubectl debug <pod-name> -it --image=<same-image>

داخل حاوية التصحيح:

# من أنت؟
id

# هل يمكنك قراءة ملف الإعدادات؟
ls -la /etc/app/config.yaml
cat /etc/app/config.yaml

# هل يمكنك الكتابة لدليل البيانات؟
touch /data/test.txt
rm /data/test.txt

إذا تم رفع الإذن، أصلحه في Dockerfile أو مواصفات pod:

FROM alpine:3.20
RUN adduser -D appuser
RUN mkdir -p /app /data && chown -R appuser:appuser /app /data
USER appuser

أو في Deployment:

containers:
- name: app
  securityContext:
    runAsUser: 1000
    runAsGroup: 3000
    fsGroup: 2000
  volumeMounts:
  - name: data
    mountPath: /data
volumes:
- name: data
  emptyDir: {}

كيف تتجنبه

  1. اختبر الحاوية محلياً أولاً:

    docker run --rm example:latest
    

    دعها تعمل لمدة 10 ثوان على الأقل لالتقاط التعطلات الفورية.

  2. عيّن طلبات وحدود الموارد الصحيحة لتجنب OOM:

    resources:
      requests:
        memory: "256Mi"
        cpu: "100m"
      limits:
        memory: "512Mi"
        cpu: "500m"
    

  3. استخدم مسابير البدء إذا استغرق التطبيق وقتاً للبدء:

    startupProbe:
      httpGet:
        path: /health
        port: 8080
      failureThreshold: 30
      periodSeconds: 10
    

  4. شغّل التطبيق كمستخدم غير root في Dockerfile.

  5. استخدم init containers للانتظار للتبعيات قبل بدء التطبيق الرئيسي.

  6. سجّل أخطاء البدء إلى stderr بحيث تظهر في kubectl logs --previous.

الأسئلة الشائعة

كيف أرى سبب كون pod في حالة CrashLoopBackOff؟

شغّل kubectl logs <pod> --previous لترى آخر تعطل. ثم kubectl describe pod <pod> لترى عدد الإعادات وفشل المسبارات. أخيراً، kubectl get events لترى التحذيرات الحديثة.

هل CrashLoopBackOff يعني أن سحب الصورة فشل؟

لا، هذا سيكون ImagePullBackOff. CrashLoopBackOff يعني أن الحاوية بدأت ولكن انطلقت برمز خطأ غير صفري. تحقق من kubectl logs --previous لترى الخطأ الفعلي.

كم من الوقت ينتظر Kubernetes قبل الإعادة بعد تعطل؟

يستخدم exponential backoff: 10 ثانية، 20 ثانية، 40 ثانية، 80 ثانية، 160 ثانية، 300 ثانية (محدود بـ 5 دقائق). لذا بعد 5 دقائق من التعطلات، تحدث الإعادات كل 5 دقائق.

هل يمكن لمسبار الحياة أن يسبب CrashLoopBackOff؟

نعم. إذا فشل المسبار و restartPolicy هو Always، يعيد Kubernetes تشغيل pod. زد initialDelaySeconds لإعطاء التطبيق وقتاً للبدء. اعرض تكوين المسبار باستخدام kubectl get pod -o yaml.

كيف تم التحقق من هذا الدليل

كُتب يدويًا من الوثائق الرسمية، وأُعيد فحص كل أمر، ثم رُوجع مرة ثانية قبل النشر. آخر مراجعة في .

المصادر الرسمية التي قُرئت لهذا المقال

شاركها.

التعليقات مغلقة.