الـ Pod: الوحدة الأساسية في Kubernetes
الـ Pod: الوحدة الأساسية في Kubernetes
في Kubernetes، كل حمل عمل — سواء كان خادم API عديم الحالة، أو قاعدة بيانات، أو مهمة دفعية — يعمل في نهاية المطاف داخل Pod. الـ Pod ليس حاوية (container)؛ بل هو غلاف خفيف يجمع حاوية واحدة أو أكثر في وحدة واحدة قابلة للجدولة ضمن بيئة تشغيل مشتركة. فهم بنية الـ Pod بهذا العمق أمر ضروري: كل تجريد أعلى مستوى (Deployment، StatefulSet، Job) هو في جوهره مصنع يُنشئ الـ Pods ويديرها.
بنية الـ Pod
يصف قسم spec في مانيفيست الـ Pod كل ما يحتاجه المُجدوِل (scheduler) والـ kubelet لتشغيل حمل العمل:
- containers — مواصفات حاوية واحدة أو أكثر، لكل منها صورة، وأمر، ومنافذ، ومتغيرات بيئة، وطلبات موارد.
- volumes — وحدات تخزين يمكن لأي حاوية في الـ Pod تثبيتها. نطاق الـ volumes يقتصر على عمر الـ Pod.
- initContainers — حاويات تعمل حتى الاكتمال قبل بدء أي حاوية عادية. تُستخدم للإعداد المسبق: تشغيل migration قاعدة البيانات، جلب الأسرار، الانتظار حتى تصبح التبعيات جاهزة.
- restartPolicy —
Always(الافتراضي، للخدمات طويلة الأمد)،OnFailure(للمهام)، أوNever. - serviceAccountName — هوية RBAC التي يستخدمها الـ Pod لاستدعاء Kubernetes API.
- securityContext — إعدادات الأمان على مستوى الـ Pod: التشغيل كمستخدم غير جذر، نظام ملفات جذر للقراءة فقط، مرشحات syscall، وملفات تعريف AppArmor.
- affinity / tolerations / nodeSelector — قيود الجدولة التي تتحكم في أي الـ Nodes يمكن للـ Pod أن يُوضع عليها.
الحقيقة المعمارية الأهم في الـ Pod هي مشاركة شبكة IPC namespace. كل حاوية داخل نفس الـ Pod ترى تماماً واجهة loopback (localhost) نفسها، وعنوان IP نفسه، وـ hostname نفسه. إذا ربطت الحاوية A المنفذ 8080، يمكن للحاوية B الوصول إليه عبر localhost:8080. هذا تصميم مقصود — يتيح للعمليات المساعدة المترابطة (sidecars) التواصل دون الحاجة إلى service mesh لاتصالات داخل الـ Pod.
كتابة مانيفيست Pod حقيقي
نادراً ما تُنشئ Pods مجردة في بيئة الإنتاج (تفعل ذلك الـ Deployments نيابةً عنك)، لكن يجب أن تكون قادراً على قراءة المانيفيستات وكتابتها لتشخيص المشكلات وفهم ما تُولِّده الكائنات ذات المستوى الأعلى. فيما يلي مانيفيست Pod بمعايير الإنتاج لحاوية واحدة يتضمن الحقول التي ستصادفها في الـ clusters الحقيقية:
requests من قِبل المُجدوِل لتحديد Node ذات سعة احتياطية كافية. أما limits فهي الحد الأقصى الصارم المُطبَّق بواسطة cgroups أثناء التشغيل. ضبط الـ limits دون requests يجعل الـ requests تساوي الـ limits تلقائياً — وهو سلوك صحيح. لا تضبط الـ limits دون requests في بيئة الإنتاج؛ فذلك يمنع المُجدوِل من تحسين توزيع الأحمال على الـ cluster بكفاءة.
الـ Pods متعددة الحاويات والـ Sidecars
الـ Pod ذو الحاوية الواحدة هو الحالة الشائعة، لكن Kubernetes يدعم صراحةً حاويات متعددة لكل Pod. يُسمى هذا النمط sidecar. الـ sidecar هو حاوية تُعزز الحاوية الرئيسية دون تعديلها. هذا فعّال لأنه يحترم مبدأ المسؤولية الواحدة على مستوى الحاوية: صورة تطبيقك تفعل شيئاً واحداً، وصورة فريق منفصل تُضيف قدرة ما (تسجيل، مقاييس، mTLS) كاهتمام منفصل تماماً.
الأنماط الثلاثة الأساسية لـ sidecar المستخدمة في الشركات الكبرى:
- ناقل السجلات (Log shipper) — يكتب التطبيق سجلات منظمة إلى volume مشتركة من نوع
emptyDir. يقرأ sidecar من Fluentd أو Promtail هذا المجلد ويُعيد توجيهه إلى مجمّع مركزي (Loki, Elasticsearch, Splunk). فريق التطبيق يمتلك صورة التطبيق؛ فريق المنصة يمتلك صورة الناقل. لا أحد منهما يحتاج لمعرفة تفاصيل تنفيذ الآخر. - الوكيل / service mesh — يحقن Istio sidecar من Envoy (يُسمى data plane) في كل Pod تلقائياً عبر MutatingAdmissionWebhook. كل حركة المرور الواردة والصادرة تمر عبر Envoy، مما يمنحك mTLS، وإعادة المحاولات، وحماية الدوائر، والتتبع الموزع دون تغيير سطر واحد من كود التطبيق.
- مزامنة الأسرار (Secret sync) — يُصادق sidecar من Vault Agent على HashiCorp Vault، يسترجع الأسرار، ويكتبها إلى volume مشتركة من نوع
tmpfs. يقرأ التطبيق الأسرار من ملفات بدلاً من متغيرات البيئة — وهي أفضل ممارسة أمنية لأن متغيرات البيئة يمكن تسريبها عبر/proc/PID/environ.
initContainer يجعل الفشل واضحاً: سيُظهر kubectl describe pod <name> بوضوح أي init container فشل ولماذا. الحاوية الرئيسية لا تبدأ أبداً، فلا غموض.
دورة حياة الـ Pod
ينتقل الـ Pod عبر مجموعة محددة من المراحل خلال عمره. تُبلَّغ هذه المراحل في pod.status.phase وهي ما تراه في عمود STATUS عند تشغيل kubectl get pods:
- Pending — قبل API server الـ Pod لكنه لم يُجدوَل على Node بعد، أو جُدوِل لكن صوره لا تزال تُحمَّل.
- Running — الـ Pod مرتبط بـ Node، جميع الحاويات أُنشئت، ولا تزال حاوية واحدة على الأقل تعمل أو في طور البدء أو إعادة التشغيل.
- Succeeded — جميع الحاويات خرجت بكود الحالة 0 ولن تُعاد. هذه الحالة النهائية للـ Jobs.
- Failed — جميع الحاويات خرجت، وخرجت واحدة على الأقل بكود غير صفري أو قتلها النظام.
- Unknown — لا يمكن تحديد حالة الـ Pod، عادةً بسبب انقطاع التواصل مع kubelet الـ Node. هذا مؤشر على فشل Node أو انقسام شبكي.
ضمن مرحلة Running، للحاويات الفردية حالتها الخاصة: Waiting، Running، أو Terminated. حقل reason على حالة Waiting أو Terminated هو أول مكان تنظر فيه عند التشخيص — سيُخبرك بـ CrashLoopBackOff، OOMKilled، ImagePullBackOff، ContainerCreating، وغيرها.
reason على حالة حاوية من نوع Waiting. تعني أن الحاوية تعطلت مراراً وأن kubelet يطبق تأخيراً تراكمياً أسياً (يبدأ من 10 ثوانٍ، يصل حداً أقصى 5 دقائق) قبل محاولة إعادة التشغيل. شغّل دائماً kubectl logs <pod> --previous للحصول على سجلات نسخة الحاوية السابقة المتعطلة، ليس النسخة المنتظرة الحالية.
البروبات: Liveness و Readiness و Startup
لا يمكن لـ Kubernetes قراءة عقل تطبيقك — يحتاج لإشارات صريحة عن الصحة. ثلاثة أنواع من البروبات متاحة:
- livenessProbe — "هل هذه الحاوية حية؟" إذا فشلت
failureThresholdمرات، يقتل kubelet الحاوية ويعيد تشغيلها. استخدمها للكشف عن الأقفال الميتة: عملية تعمل لكنها عالقة إلى الأبد دون استجابة. - readinessProbe — "هل هذه الحاوية جاهزة لخدمة حركة المرور؟" إذا فشلت، يُزال IP الـ Pod من كائن Endpoints لكل Service تحدده. تتوقف حركة المرور نحو ذلك الـ Pod لكن الحاوية لا تُقتل. استخدمها للإشارة خلال الإحماء عند الإقلاع أو عندما تكون تبعية upstream معطلة مؤقتاً.
- startupProbe — للحاويات بطيئة الإقلاع (تطبيقات JVM، تحميل نماذج ML). بينما تعمل بروبة البدء، تُعطَّل بروبات liveness وreadiness. يمنع هذا إعادات التشغيل المبكرة خلال التهيئة.
فحص الـ Pods عملياً
الأوامر التي يشغّلها كل مهندس DevOps عشرات المرات يومياً: