تشخيص أخطاء الـ Pods والأعباء
تشخيص أخطاء الـ Pods والأعباء
حين يحدث خلل في مجموعة Kubernetes، كثيرًا ما يبقى نطاق الضرر غير واضح في البداية. تظهر حالة CrashLoopBackOff على لوحة التحكم، أو يتساقط الحمل على Service بصمت، أو يتوقف Deployment عند مرحلة 2 من 3 نسخ متطلوبة دون أن يكتمل التقارب. على عكس العملية التي تعمل على خادم يمكنك الدخول إليه مباشرةً، تُخفي Kubernetes وقت التشغيل خلف طبقات متعددة — API Server وكيول kubelet ووقت تشغيل الحاويات وشبكة التراكب. التشخيص الفعّال يعني معرفة أي طبقة تستجوبها وأي أمر kubectl يكشف تلك الطبقة.
يمنحك هذا الدرس مجموعة الأدوات الكاملة التي يستخدمها مهندسو SRE في الشركات التي تُشغّل أسطول Kubernetes الضخمة: الأوامر الأربعة الأساسية للتشخيص، وكيفية قراءة مخرجاتها، والحالات الثلاث الأكثر شيوعًا لفشل الـ Pod مع جذور أسبابها، والنموذج الذهني المنهجي الذي ينقلك من التنبيه إلى الإصلاح في دقائق لا ساعات.
هرمية التشخيص
دائمًا اعمل من الأعم إلى الأخص: أحداث المجموعة ← وصف الكائن ← سجلات الحاوية ← Shell مباشر. القفز مباشرةً إلى السجلات قبل مراجعة الأحداث هو الخطأ الأكثر شيوعًا — ستفوت قرار المُجدوِل، أو فشل سحب الصورة، أو رفض المسبار الذي يُفسّر كل شيء.
kubectl get events — سجل التدقيق في المجموعة
الأحداث هي سجل Kubernetes المنظّم لكل ما حدث لكل كائن في namespace معيّنة. على عكس سجلات الحاوية التي لا تُوجد إلا أثناء تشغيل الحاوية، تُكتب الأحداث بواسطة مستوى التحكم — المُجدوِل والـ kubelet ومتحكم النشر — وتظل محفوظةً افتراضيًا لمدة ساعة. حين تفشل Pod قبل أن تتمكن من قراءة سجلاتها، تكون الأحداث في الغالب السجل الوحيد الذي يشرح السبب.
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 — تُظهر تقدم سحب الصورة وفشل المسابر وقرارات الجدولة.
kubectl logs — قراءة مخرجات الحاوية
يجلب kubectl logs stdout وstderr من وقت تشغيل الحاوية. حين تنهار Pod، مرّر --previous لقراءة سجلات الحاوية الميتة بدلًا من الحالية (الفارغة).
kubectl logs من ملفات سجل الحاوية المحلية على الـ Node (/var/log/pods/). حين يُطرد Pod أو تُفرَّغ Node، قد تختفي تلك الملفات. في بيئة الإنتاج، دائمًا أرسل السجلات إلى مخزن مركزي (Loki أو OpenSearch أو Datadog) — kubectl logs للفرز السريع، ليس الأرشيف الأساسي للسجلات.kubectl exec — Shell مباشر داخل الحاوية الجارية
يفتح kubectl exec عملية داخل حاوية تعمل فعلًا. استخدمه لفحص نظام الملفات، واختبار الوصول الشبكي من داخل namespace الشبكة الخاصة بالـ Pod، والتحقق من متغيرات البيئة والأسرار المُثبَّتة التي سيراها تطبيقك فعلًا.
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. الجذور الأكثر شيوعًا بالترتيب:
- الـ tag غير موجود — خطأ إملائي في tag الصورة، أو خط CI فشل في رفع الـ tag الجديد قبل تحديث الـ Deployment.
- imagePullSecret خاطئ أو مفقود — السجل يتطلب مصادقة (ECR أو GCR أو GHCR أو Docker Hub الخاص) ومواصفة الـ Pod لا تشير إلى سر صالح، أو السر في namespace خاطئة.
- حد معدل السجل — تجاوز حد السحب المجهول في Docker Hub (100 كل 6 ساعات) بسبب عدة nodes تسحب نفس الصورة بدون بيانات اعتماد.
- سياسة الشبكة أو الجدار الناري — الـ Node لا تستطيع الوصول إلى نقطة نهاية السجل (شائع في البيئات المعزولة أو المقيّدة بـ VPC).
CrashLoopBackOff
تبدأ الحاوية وتعمل لفترة وجيزة ثم تخرج بكود غير صفري (أو أحيانًا صفري). تُعيد Kubernetes تشغيلها. تستمر حلقة إعادة التشغيل مع تأخر أسي (10 ث، 20 ث، 40 ث... أقصاه 5 دقائق). بعد انهيارات كافية تستقر الحالة على CrashLoopBackOff، وهي رسالة Kubernetes: "أحاول باستمرار وتفشل باستمرار." الجذور الأكثر شيوعًا:
- خطأ في بدء تشغيل التطبيق — العملية لا تستطيع الاتصال بقاعدة بيانات، أو تقرأ متغير بيئة مطلوب مفقودًا أو بقيمة خاطئة، أو يفشل فحص التحقق عند البدء.
- إعداد خاطئ لمسبار liveness — عتبة المسبار عدوانية جدًا (مثلًا
initialDelaySeconds: 0لخدمة Java بطيئة البدء). تقتل Kubernetes الحاوية قبل انتهائها من البدء مُسبّبةً حلقة. - قتل OOM عند البدء — حد الذاكرة منخفض جدًا لتهيئة العملية. تحقق من كود الخروج 137 في
Last State. - إعدادات مفقودة — ConfigMap أو Secret يعتمد عليها التطبيق غير مُثبَّت، أو مُثبَّت في مسار لا يتوقعه التطبيق.
Pending — الـ Pod التي لا تبدأ أبدًا
الـ Pod العالقة في Pending قبلها API Server لكن المُجدوِل لم يضعها بعد. سبب الحدث يكون دائمًا تقريبًا FailedScheduling. الرسالة ستخبرك تحديدًا أي قيد فشل:
- CPU / ذاكرة غير كافية — لا توجد Node بها موارد غير مُخصَّصة كافية. الحل: توسيع مجموعة الـ Nodes أو تقليل الطلبات أو التحقق من Pods منسية تستهلك موارد.
- لا nodes تطابق nodeSelector أو الـ affinity —
nodeSelectorيتطلب label لا تملكها أي Node. - PersistentVolumeClaim غير مرتبط — الـ Pod تتطلب PVC في حالة
Pending(لا يوجد PV مطابق، أو StorageClass خاطئ). - Taint غير متحمَّل — جميع Nodes المتاحة لديها taint لا تتحمله الـ Pod.
kubectl get pod -o wide: دائمًا مرّر -o wide عند مراجعة الـ Pods بالجملة. يُضيف عمود الـ Node وعنوان IP الـ Pod والـ Node المرشحة للـ Pods في حالة Pending. في مجموعة متعددة الـ Nodes، رؤية جميع نسخ خدمة ما على نفس الـ Node يُشير فورًا إلى قاعدة anti-affinity مفقودة.قراءة الصورة الكاملة: دليل تشغيل منهجي
حين يُطلق تنبيه أو يُبلّغ مستخدم عن خطأ 503، اتبع هذا التسلسل في كل مرة — دون ارتجال:
- شغّل
kubectl get pods -n <ns>— حدّد أي Pods لا تعمل بحالةRunning 1/1. - شغّل
kubectl get events -n <ns> --sort-by='.lastTimestamp' | tail -30— الإشارة على مستوى المجموعة. - شغّل
kubectl describe pod <name>— اقرأ Conditions وContainer State وLast State وEvents. - شغّل
kubectl logs <name> --previousإن انهارت الحاوية؛ أوkubectl logs -f <name>إن كانت تعمل لكن تتصرف بشكل خاطئ. - شغّل
kubectl exec -it <name> -- shللتحقق من الاتصالات والإعدادات من داخل namespace الشبكة للـ Pod. - إن كانت الـ Pod سليمة لكن الـ Service تفقد حمولتها، افحص الـ Endpoints:
kubectl get endpoints <service-name>— endpoints فارغة تعني أن الـ label selector لا يطابق أي Pod جارية.
كل خطوة تكشف طبقة مختلفة. تخطّي أي خطوة منها يُخاطر بمطاردة الفرضية الخاطئة لساعة كاملة.