مدخل إلى شبكات الحاويات

عندما تشغّل تطبيقك في حاوية واحدة يكون كل شيء بسيطًا، لكن بمجرد أن يصبح لديك حاوية للتطبيق وأخرى لقاعدة البيانات وثالثة للكاش، يظهر السؤال الأهم: كيف تتواصل هذه الحاويات مع بعضها؟ شبكات الحاويات هي العمود الفقري لأي بنية حديثة، وفهمها يوفّر عليك ساعات من تتبّع أخطاء “Connection refused”. في هذا المقال نشرح كيف تتخاطب الحاويات في Docker وKubernetes بأوامر عملية للتشخيص.

مشكلة الشبكات في الحاويات

كل حاوية تحصل على فضاء شبكة معزول (network namespace) خاص بها، بعنوان IP داخلي مستقل. هذه العزلة ميزة أمنية، لكنها تعني أن الحاويات لا ترى بعضها افتراضيًا إلا إذا وضعناها على شبكة مشتركة. والأسوأ أن عناوين الـ IP الداخلية متغيّرة: عند إعادة تشغيل حاوية قد تأخذ IP مختلفًا، فالاعتماد عليه مباشرة وصفة للفشل. الحل هو الشبكات المعرّفة من المستخدم مع الـ DNS الداخلي الذي يحلّ الأسماء إلى عناوين متغيّرة تلقائيًا.

شبكات Docker

يوفّر Docker عدة أنواع من الشبكات (network drivers)، لكل منها استخدام:

  • bridge الافتراضية: الشبكة التلقائية لأي حاوية لا تحدد لها شبكة. الحاويات عليها تتواصل بالـ IP فقط، ولا يعمل عليها الـ DNS بالاسم.
  • user-defined bridge: شبكة جسر تنشئها بنفسك، وهي الخيار الموصى به لأنها تفعّل الـ DNS الداخلي فتتخاطب الحاويات بأسمائها.
  • host: تلغي العزل وتجعل الحاوية تشارك شبكة الخادم المضيف مباشرة، فلا حدود بينهما. أسرع لكن أقل أمانًا.
  • overlay: تربط حاويات تعمل على خوادم فيزيائية مختلفة عبر شبكة افتراضية واحدة، وتُستخدم في Docker Swarm والبيئات الموزّعة.

لإنشاء شبكة جسر معرّفة من المستخدم وعرض الشبكات الموجودة:

docker network create --driver bridge app-net
docker network ls

أمر ls يعرض قائمة بكل الشبكات مع معرّفها واسمها ونوع الـ driver ونطاقها. الآن شغّل حاويتين على هذه الشبكة، واحدة لقاعدة بيانات وأخرى للتطبيق:

docker run -d --name db --network app-net postgres:16
docker run -d --name web --network app-net nginx:latest

لفحص تفاصيل الشبكة ومعرفة أي حاويات متصلة بها وعناوينها:

docker network inspect app-net

المخرجات بصيغة JSON تعرض قسم Containers الذي يحتوي اسم كل حاوية وعنوان الـ IPv4 المخصص لها، وهذا أول ما تتحقق منه عند تشخيص مشكلة اتصال.

الـ DNS الداخلي والتخاطب بالاسم

أهم ميزة في الشبكات المعرّفة من المستخدم هي مزوّد DNS مدمج في Docker يحلّ اسم الحاوية إلى عنوانها الحالي تلقائيًا. هذا يعني أن حاوية web تستطيع الاتصال بقاعدة البيانات عبر الاسم db بدلًا من حفظ IP متغيّر. لنتأكد عمليًا، ندخل إلى حاوية web ونجرّب تحليل الاسم:

docker exec -it web ping -c 2 db

يجب أن يردّ الـ ping بعنوان الـ IP الخاص بحاوية db، مما يثبت أن الـ DNS يعمل. وبهذا يصبح إعداد التطبيق نظيفًا: تكتب في متغيّرات الاتصال DB_HOST=db فقط، ويبقى صحيحًا حتى لو تغيّر الـ IP بعد كل إعادة تشغيل.

الشبكات في Kubernetes

تتبنّى Kubernetes نموذج شبكة مسطّح (flat network model): كل Pod يحصل على عنوان IP فريد، ويستطيع أي Pod الاتصال بأي Pod آخر مباشرة عبر هذا العنوان دون NAT، حتى لو كانا على عقدتين (nodes) مختلفتين. هذا يبسّط النموذج الذهني كثيرًا مقارنة بالـ overlay اليدوي.

لكن مثل Docker، عناوين الـ Pods متغيّرة وتختفي عند إعادة الجدولة. لذلك نضع أمامها Service من نوع ClusterIP، وهو عنوان IP ثابت واسم DNS دائم يوزّع الطلبات على الـ Pods الحيّة خلفه. لعرض الخدمات:

kubectl get svc

المخرجات تعرض اسم كل خدمة ونوعها وعنوان الـ CLUSTER-IP والمنافذ. هذا الـ ClusterIP ثابت طوال عمر الخدمة بغض النظر عن تبدّل الـ Pods خلفها.

يتولّى مكوّن CoreDNS (الذي حلّ محل kube-dns) تحليل أسماء الخدمات داخل الكلاستر. الاسم الكامل لأي خدمة يتبع الصيغة service-name.namespace.svc.cluster.local، لكن من داخل نفس الـ namespace يكفي استخدام اسم الخدمة المختصر.

التشخيص العملي داخل Kubernetes

لنفترض أن خدمة باسم backend تعمل، ونريد التأكد أن Pod آخر يستطيع الوصول إليها بالاسم. ندخل إلى Pod ونشغّل أداة تحليل DNS من داخله:

kubectl exec -it frontend-pod -- nslookup backend

الردّ الصحيح يُظهر اسم الخادم kube-dns وعنوان الـ ClusterIP الخاص بخدمة backend، مما يثبت أن الـ DNS داخل الكلاستر يعمل. إذا فشل التحليل، فالمشكلة غالبًا في CoreDNS أو في أن الخدمة في namespace مختلف. لاختبار الاتصال الفعلي بالمنفذ من داخل Pod:

kubectl exec -it frontend-pod -- wget -qO- http://backend:8080

وللتعمّق في تفاصيل خدمة معيّنة ومعرفة الـ Endpoints (عناوين الـ Pods الفعلية خلفها):

kubectl describe svc backend

إذا كان قسم Endpoints فارغًا، فهذا يعني أن الـ selector في الخدمة لا يطابق أي Pod، وهي من أكثر أسباب فشل الاتصال شيوعًا رغم أن كل شيء يبدو سليمًا ظاهريًا.

أخطاء شائعة

  • نشر حاويتين على شبكتين مختلفتين: في Docker، إذا كانت حاوية التطبيق على شبكة وحاوية قاعدة البيانات على شبكة أخرى، فلن تريا بعضهما أبدًا. تأكد أنهما على نفس الشبكة عبر docker network inspect، أو اربط حاوية بشبكة إضافية بـ docker network connect.
  • الاعتماد على IP بدل اسم الخدمة: كتابة عنوان IP مباشر في إعدادات الاتصال يعمل اليوم ويفشل غدًا بعد إعادة تشغيل الحاوية أو إعادة جدولة الـ Pod. استخدم دائمًا اسم الحاوية في Docker أو اسم الخدمة في Kubernetes.
  • نسيان نشر المنفذ في الخدمة: في Kubernetes قد تعمل الـ Pods لكن الخدمة لا تستهدف المنفذ الصحيح. تحقق أن targetPort في الخدمة يطابق المنفذ الذي يستمع عليه التطبيق فعلًا داخل الحاوية.
  • الخلط بين namespaces: محاولة الوصول لخدمة بالاسم المختصر من namespace آخر تفشل. استخدم الاسم الكامل backend.production.svc.cluster.local عند العبور بين الـ namespaces.

الخطوة التالية

الآن بعد أن فهمت كيف تتخاطب الحاويات بالأسماء، الخطوة التالية هي التحكم في من يُسمح له بالاتصال بمن. في Kubernetes يتم ذلك عبر الـ NetworkPolicy التي تعمل كجدار حماية بين الـ Pods. ابدأ بكتابة سياسة بسيطة تمنع كل الاتصالات افتراضيًا ثم تسمح فقط بما يحتاجه تطبيقك، فهذا أساس بناء بنية آمنة ومنظّمة.

اترك تعليقاً