أساسيات Kubernetes

تشخيص أخطاء الـ Pods والأعباء

18 دقيقة الدرس 9 من 32

تشخيص أخطاء الـ Pods والأعباء

حين يحدث خلل في مجموعة Kubernetes، كثيرًا ما يبقى نطاق الضرر غير واضح في البداية. تظهر حالة CrashLoopBackOff على لوحة التحكم، أو يتساقط الحمل على Service بصمت، أو يتوقف Deployment عند مرحلة 2 من 3 نسخ متطلوبة دون أن يكتمل التقارب. على عكس العملية التي تعمل على خادم يمكنك الدخول إليه مباشرةً، تُخفي Kubernetes وقت التشغيل خلف طبقات متعددة — API Server وكيول kubelet ووقت تشغيل الحاويات وشبكة التراكب. التشخيص الفعّال يعني معرفة أي طبقة تستجوبها وأي أمر kubectl يكشف تلك الطبقة.

يمنحك هذا الدرس مجموعة الأدوات الكاملة التي يستخدمها مهندسو SRE في الشركات التي تُشغّل أسطول Kubernetes الضخمة: الأوامر الأربعة الأساسية للتشخيص، وكيفية قراءة مخرجاتها، والحالات الثلاث الأكثر شيوعًا لفشل الـ Pod مع جذور أسبابها، والنموذج الذهني المنهجي الذي ينقلك من التنبيه إلى الإصلاح في دقائق لا ساعات.

هرمية التشخيص

دائمًا اعمل من الأعم إلى الأخص: أحداث المجموعة ← وصف الكائن ← سجلات الحاوية ← Shell مباشر. القفز مباشرةً إلى السجلات قبل مراجعة الأحداث هو الخطأ الأكثر شيوعًا — ستفوت قرار المُجدوِل، أو فشل سحب الصورة، أو رفض المسبار الذي يُفسّر كل شيء.

Kubernetes Debugging Hierarchy kubectl get events Cluster-wide signal kubectl describe Object state + events kubectl logs Container stdout/stderr kubectl exec Live shell inside ImagePullBackOff Registry / tag problem CrashLoopBackOff Container exits repeatedly Pending / OOMKilled Scheduling / resource issue Common failure states resolved by the debugging hierarchy above
هرمية التشخيص بأوامرها الأربعة وحالات الفشل الأكثر شيوعًا التي تساعد كل طبقة على تشخيصها.

kubectl get events — سجل التدقيق في المجموعة

الأحداث هي سجل Kubernetes المنظّم لكل ما حدث لكل كائن في namespace معيّنة. على عكس سجلات الحاوية التي لا تُوجد إلا أثناء تشغيل الحاوية، تُكتب الأحداث بواسطة مستوى التحكم — المُجدوِل والـ kubelet ومتحكم النشر — وتظل محفوظةً افتراضيًا لمدة ساعة. حين تفشل Pod قبل أن تتمكن من قراءة سجلاتها، تكون الأحداث في الغالب السجل الوحيد الذي يشرح السبب.

# جميع الأحداث في namespace الحالية مرتبةً من الأقدم إلى الأحدث kubectl get events --sort-by='.lastTimestamp' # تصفية الأحداث المتعلقة بـ Pod واحدة بعينها kubectl get events --field-selector involvedObject.name=<pod-name> # مراقبة الأحداث لحظةً بلحظة (مفيد جدًا أثناء النشر المتدرج) kubectl get events -w # عرض الأحداث في جميع الـ namespaces (مفيد في المجموعات متعددة المستأجرين) kubectl get events -A --sort-by='.lastTimestamp' | tail -40
فكرة أساسية: للأحداث حقل Reason (مثلًا FailedScheduling أو BackOff أو Pulled أو Started أو Killing) وحقل Message يحمل تفاصيل قابلة للقراءة البشرية. الـ Reason وحده كثيرًا ما يخبرك أي طبقة مكسورة — المُجدوِل أم سحب الصورة أم المسبار أم قاتل الذاكرة OOM.

kubectl describe — الحالة الكاملة للكائن + سجل الأحداث

kubectl describe pod <name> هو أغنى أمر تشخيصي بالمعلومات. يُصيّر المواصفات الكاملة للكائن مدموجةً مع حقول الحالة الحية، ويُلحق دفق الأحداث المحدد لذلك الكائن. تعلّم قراءته بالأقسام:

  • Status / Phase: Pending أو Running أو Succeeded أو Failed أو Unknown — الإشارة العامة.
  • Conditions: أعلام منطقية مثل PodScheduled وInitialized وContainersReady وReady. إن كانت PodScheduled=False فالمشكلة في الجدولة لا في صورتك.
  • Containers → State: Waiting (مع Reason) أو Running أو Terminated (مع ExitCode). كود الخروج 137 يعني قتل OOM، و1 يعني خطأ في التطبيق، و0 يعني إنهاء نظيف لم يكن ينبغي أن يحدث.
  • Containers → Last State: تشغيل الحاوية السابق — ضروري لتشخيص CrashLoopBackOff؛ يُظهر كود الخروج من آخر انهيار.
  • Events: مُحدَّدة لهذه الـ Pod — تُظهر تقدم سحب الصورة وفشل المسابر وقرارات الجدولة.
# وصف كامل لـ Pod kubectl describe pod <pod-name> # وصف Deployment المالكة (يُظهر حالة الطرح والـ selector والاستراتيجية) kubectl describe deployment <deploy-name> # وصف Node — رؤية ضغط الموارد والـ taints والشروط والـ Pods المُخصَّصة kubectl describe node <node-name> # نمط سريع: استخلاص قسم Events فقط kubectl describe pod <pod-name> | grep -A 30 "^Events:"

kubectl logs — قراءة مخرجات الحاوية

يجلب kubectl logs stdout وstderr من وقت تشغيل الحاوية. حين تنهار Pod، مرّر --previous لقراءة سجلات الحاوية الميتة بدلًا من الحالية (الفارغة).

# السجلات الحالية للحاوية الأولى في Pod kubectl logs <pod-name> # سجلات حاوية بعينها في Pod متعددة الحاويات kubectl logs <pod-name> -c <container-name> # سجلات نسخة الحاوية السابقة (المنهارة) — ضرورية لـ CrashLoopBackOff kubectl logs <pod-name> --previous # بث السجلات لحظةً بلحظة (مكافئ لـ tail -f) kubectl logs -f <pod-name> # آخر 100 سطر مع الطوابع الزمنية kubectl logs --tail=100 --timestamps <pod-name> # سجلات جميع الـ Pods المطابقة لـ label selector (كل نسخ Deployment مثلًا) kubectl logs -l app=payment-service --prefix --tail=50
نصيحة للإنتاج — التسجيل المركزي: يقرأ kubectl logs من ملفات سجل الحاوية المحلية على الـ Node (/var/log/pods/). حين يُطرد Pod أو تُفرَّغ Node، قد تختفي تلك الملفات. في بيئة الإنتاج، دائمًا أرسل السجلات إلى مخزن مركزي (Loki أو OpenSearch أو Datadog) — kubectl logs للفرز السريع، ليس الأرشيف الأساسي للسجلات.

kubectl exec — Shell مباشر داخل الحاوية الجارية

يفتح kubectl exec عملية داخل حاوية تعمل فعلًا. استخدمه لفحص نظام الملفات، واختبار الوصول الشبكي من داخل namespace الشبكة الخاصة بالـ Pod، والتحقق من متغيرات البيئة والأسرار المُثبَّتة التي سيراها تطبيقك فعلًا.

# Shell تفاعلي (bash أو sh إن لم يكن bash موجودًا في الصورة) kubectl exec -it <pod-name> -- bash kubectl exec -it <pod-name> -- sh # تشغيل أمر واحد (لا يحتاج TTY) kubectl exec <pod-name> -- env | grep DATABASE kubectl exec <pod-name> -- cat /etc/config/app.yaml kubectl exec <pod-name> -- wget -qO- http://localhost:8080/healthz # اختبار DNS من داخل الـ Pod kubectl exec <pod-name> -- nslookup kubernetes.default.svc.cluster.local # اختبار الاتصال بـ Service أخرى kubectl exec <pod-name> -- curl -s http://payment-service.payments.svc.cluster.local/health # في حاوية بعينها من Pod متعددة الحاويات kubectl exec -it <pod-name> -c sidecar -- sh
خطر في الإنتاج — صور distroless / scratch: كثير من صور الإنتاج المُصلَّبة أمنيًا (distroless وصور scratch) لا تحتوي على shell أو curl أو wget. سيفشل kubectl exec -- bash بخطأ "OCI runtime exec failed". استخدم حاوية تشخيص مؤقتة بدلًا من ذلك: kubectl debug -it <pod-name> --image=busybox --target=<container-name>. هذا يحقن sidecar تشخيصيًا يشارك namespace العملية للحاوية المستهدفة دون تعديل مواصفة الـ Pod الجارية.

حالات فشل الـ Pod الأكثر شيوعًا

ImagePullBackOff (وErrImagePull)

لا يستطيع الـ kubelet سحب صورة الحاوية من السجل. ErrImagePull هي أول محاولة؛ بعد عدة محاولات مع تأخر أسي (5 ث، 10 ث، 20 ث... يصل أقصاه 5 دقائق)، تصبح الحالة ImagePullBackOff. الجذور الأكثر شيوعًا بالترتيب:

  1. الـ tag غير موجود — خطأ إملائي في tag الصورة، أو خط CI فشل في رفع الـ tag الجديد قبل تحديث الـ Deployment.
  2. imagePullSecret خاطئ أو مفقود — السجل يتطلب مصادقة (ECR أو GCR أو GHCR أو Docker Hub الخاص) ومواصفة الـ Pod لا تشير إلى سر صالح، أو السر في namespace خاطئة.
  3. حد معدل السجل — تجاوز حد السحب المجهول في Docker Hub (100 كل 6 ساعات) بسبب عدة nodes تسحب نفس الصورة بدون بيانات اعتماد.
  4. سياسة الشبكة أو الجدار الناري — الـ Node لا تستطيع الوصول إلى نقطة نهاية السجل (شائع في البيئات المعزولة أو المقيّدة بـ VPC).
# 1. تحقق من رسالة الخطأ الدقيقة في أحداث الـ Pod kubectl describe pod <pod-name> | grep -A 5 "Failed to pull\|ImagePull\|BackOff" # 2. تحقق من صحة مرجع الصورة kubectl get pod <pod-name> -o jsonpath='{.spec.containers[*].image}' # 3. تحقق من وجود imagePullSecret في الـ namespace الصحيحة kubectl get secret <pull-secret-name> -n <namespace> -o jsonpath='{.data.\.dockerconfigjson}' | base64 -d | python3 -m json.tool # 4. اختبر السحب يدويًا من Node (إن كان لديك صلاحية SSH للـ Node) crictl pull <image-reference>

CrashLoopBackOff

تبدأ الحاوية وتعمل لفترة وجيزة ثم تخرج بكود غير صفري (أو أحيانًا صفري). تُعيد Kubernetes تشغيلها. تستمر حلقة إعادة التشغيل مع تأخر أسي (10 ث، 20 ث، 40 ث... أقصاه 5 دقائق). بعد انهيارات كافية تستقر الحالة على CrashLoopBackOff، وهي رسالة Kubernetes: "أحاول باستمرار وتفشل باستمرار." الجذور الأكثر شيوعًا:

  1. خطأ في بدء تشغيل التطبيق — العملية لا تستطيع الاتصال بقاعدة بيانات، أو تقرأ متغير بيئة مطلوب مفقودًا أو بقيمة خاطئة، أو يفشل فحص التحقق عند البدء.
  2. إعداد خاطئ لمسبار liveness — عتبة المسبار عدوانية جدًا (مثلًا initialDelaySeconds: 0 لخدمة Java بطيئة البدء). تقتل Kubernetes الحاوية قبل انتهائها من البدء مُسبّبةً حلقة.
  3. قتل OOM عند البدء — حد الذاكرة منخفض جدًا لتهيئة العملية. تحقق من كود الخروج 137 في Last State.
  4. إعدادات مفقودة — ConfigMap أو Secret يعتمد عليها التطبيق غير مُثبَّت، أو مُثبَّت في مسار لا يتوقعه التطبيق.
# سير العمل الأساسي لـ CrashLoopBackOff: # الخطوة 1: تأكيد الحالة وفحص عدد إعادات التشغيل kubectl get pod <pod-name> # NAME READY STATUS RESTARTS AGE # api-7d9f4b 0/1 CrashLoopBackOff 8 12m # الخطوة 2: قراءة سجلات الانهيار من نسخة الحاوية السابقة kubectl logs <pod-name> --previous # الخطوة 3: فحص كود الخروج من آخر انهيار kubectl describe pod <pod-name> | grep -A 10 "Last State:" # Last State: Terminated # Reason: Error # Exit Code: 1 # الخطوة 4: إن كانت السجلات فارغة (العملية انهارت قبل الكتابة)، # تحقق من تثبيت متغيرات البيئة والأسرار بشكل صحيح kubectl get pod <pod-name> -o yaml | grep -A 20 "env:\|envFrom:\|volumeMounts:"

Pending — الـ Pod التي لا تبدأ أبدًا

الـ Pod العالقة في Pending قبلها API Server لكن المُجدوِل لم يضعها بعد. سبب الحدث يكون دائمًا تقريبًا FailedScheduling. الرسالة ستخبرك تحديدًا أي قيد فشل:

  • CPU / ذاكرة غير كافية — لا توجد Node بها موارد غير مُخصَّصة كافية. الحل: توسيع مجموعة الـ Nodes أو تقليل الطلبات أو التحقق من Pods منسية تستهلك موارد.
  • لا nodes تطابق nodeSelector أو الـ affinitynodeSelector يتطلب label لا تملكها أي Node.
  • PersistentVolumeClaim غير مرتبط — الـ Pod تتطلب PVC في حالة Pending (لا يوجد PV مطابق، أو StorageClass خاطئ).
  • Taint غير متحمَّل — جميع Nodes المتاحة لديها taint لا تتحمله الـ Pod.
# معرفة سبب توقف Pod في Pending kubectl describe pod <pending-pod-name> | grep -A 10 "Events:" # Events: # Warning FailedScheduling 2m default-scheduler # 0/3 nodes are available: 3 Insufficient cpu. # رؤية كمية CPU/الذاكرة المُخصَّصة عبر الـ Nodes kubectl describe nodes | grep -E "Name:| cpu| memory" | grep -v "^--" # أو استخدام أمر top (يتطلب metrics-server) kubectl top nodes
نصيحة إنتاجية — kubectl get pod -o wide: دائمًا مرّر -o wide عند مراجعة الـ Pods بالجملة. يُضيف عمود الـ Node وعنوان IP الـ Pod والـ Node المرشحة للـ Pods في حالة Pending. في مجموعة متعددة الـ Nodes، رؤية جميع نسخ خدمة ما على نفس الـ Node يُشير فورًا إلى قاعدة anti-affinity مفقودة.

قراءة الصورة الكاملة: دليل تشغيل منهجي

حين يُطلق تنبيه أو يُبلّغ مستخدم عن خطأ 503، اتبع هذا التسلسل في كل مرة — دون ارتجال:

  1. شغّل kubectl get pods -n <ns> — حدّد أي Pods لا تعمل بحالة Running 1/1.
  2. شغّل kubectl get events -n <ns> --sort-by='.lastTimestamp' | tail -30 — الإشارة على مستوى المجموعة.
  3. شغّل kubectl describe pod <name> — اقرأ Conditions وContainer State وLast State وEvents.
  4. شغّل kubectl logs <name> --previous إن انهارت الحاوية؛ أو kubectl logs -f <name> إن كانت تعمل لكن تتصرف بشكل خاطئ.
  5. شغّل kubectl exec -it <name> -- sh للتحقق من الاتصالات والإعدادات من داخل namespace الشبكة للـ Pod.
  6. إن كانت الـ Pod سليمة لكن الـ Service تفقد حمولتها، افحص الـ Endpoints: kubectl get endpoints <service-name> — endpoints فارغة تعني أن الـ label selector لا يطابق أي Pod جارية.

كل خطوة تكشف طبقة مختلفة. تخطّي أي خطوة منها يُخاطر بمطاردة الفرضية الخاطئة لساعة كاملة.