الشبكة: كيف تتكلّم المعزولات؟
في الإقليم I عزلنا الشبكة بسطرٍ واحد فصارت العملية وحيدةً بلا منفذ. قلنا «العزل سهل،
الوصل فن». هنا نتعلّم الفن: نبني شبكة Docker الافتراضية بأكملها بأيدينا. حين ننتهي،
docker0 لن يكون اسماً غامضاً بل شيئاً صنعتَ مثله بيدك.
النبذة
net namespace يعطي العملية مكدّس شبكةٍ خاصاً بالكامل، لكنه يولد معزولاً تماماً:
لا واجهة إلا lo. لنصل عالمين نحتاج ثلاث بدائيات: veth
(كابلٌ افتراضي يخترق حدود الـ namespaces)، bridge (مبدّلٌ افتراضي
يجمع كابلات)، وNAT (بوّابةٌ تترجم العناوين الخاصة للخروج). كل شبكات
Docker مشتقّةٌ من تركيب هذه الثلاث.
اللغز المستفزّ — صِل عالمين، ثم العالم كله
الجزء أ — صِل معزولتين:
ip netns add ns1; ip netns add ns2 — كلٌّ جزيرة. اجعل ns1 يـ ping لـ ns2 (عيّن 10.0.0.1/24 و10.0.0.2/24). السؤال: ما الذي له طرفان، طرفٌ في كل جزيرة، فيصير جسراً؟ ابحث في ip link help عن نوع واجهةٍ يُخلَق أزواجاً.
الجزء ب — أخرِج معزولةً للإنترنت:
اجعل ns1 يـ ping 8.8.8.8 عبر المضيف. عنوانه الخاص لا يعود إليه أحد — من يجب أن يبدّل عنوان المصدر بعنوان المضيف، ثم يتذكّر التعيين ليُرجع الردّ للجزيرة الصحيحة؟ اكتشف اسم العملية (تبدأ بـ N) وأي سلسلةٍ في الجدار تطبّقها.
الجزء ج — انشر منفذاً:
خدمةٌ تستمع داخل ns1 على :80، أتِحها على المضيف:8080. معكوس ب: ترجمة عنوان الوجهة (DNAT). هذا هو -p 8080:80 بالضبط.
ستكتشف أن docker run -p ليس ميزةً سحرية، بل سطرا iptables تكتبهما أنت في دقيقتين. الموارد: man ip-netns (قسم EXAMPLES)، man ip-link (نوع veth)، man iptables.
الدرس — البدائيات الثلاث، ومنها كل شبكات Docker
ليش veth: الكابل الذي يخترق الجدران
net namespace جدارٌ عازل. veth pair زوجٌ من الواجهات متّصلتان كطرفي كابل: ما يدخل أحدهما يخرج من الآخر. تضع طرفاً في namespace والآخر في آخر فيصير «سلكاً» رغم الجدار.
bash$ ip link add veth0 type veth peer name veth1 # كابل بطرفين $ ip link set veth1 netns ns1 # ادفع طرفاً داخل الجزيرة
في Docker لكل كونتينر veth pair: طرفٌ داخله (eth0)، وطرفٌ على المضيف (vethXXXX). شغّل كونتينراً ونفّذ ip link — سترى veth... تظهر؛ احذف الكونتينر تختفِ.
ليش bridge: المبدّل الذي يجمع الكابلات
عشرة كونتينرات = عشرة كابلاتٍ لا يرى بعضها بعضاً. تحتاج مبدّلاً. Linux bridge مبدّلٌ افتراضي L2: تربط به واجهاتٍ فيمرّر الإطارات بينها بـ MAC.
bash$ ip link add br0 type bridge # مبدّل افتراضي = docker0 بالضبط $ ip link set veth0 master br0 # وصّل طرف كابل ns1 $ ip link set veth2 master br0 # وطرف كابل ns2
عند تثبيت Docker يُنشأ جسرٌ افتراضي docker0 (مثل 172.17.0.1)، وكل كونتينر يُوصَل به بـ veth. الكونتينرات على نفس الجسر ترى بعضها.
على الجسر الافتراضي docker0 تتواصل الكونتينرات بالـ IP لكن لا تتحلّ أسماؤها؛ على شبكة docker network create تتحلّ بالاسم. لأن Docker يشغّل DNS مدمجاً (على 127.0.0.11) فقط على الشبكات المعرّفة من المستخدم. docker0 قديمٌ ولا يفعّله — اشتقاق سلوكٍ يحيّر كثيرين.
ليش NAT: جواز سفر العناوين الخاصة
الكونتينر يحمل عنواناً خاصاً (172.17.x.x) لا يُوجَّه على الإنترنت. ليخرج، يلعب المضيف دور البوّابة ويُجري NAT:
- خروجاً (SNAT/MASQUERADE): يبدّل عنوان المصدر بعنوان المضيف، ويسجّل التعيين في conntrack، فحين يعود الردّ يعرف لأي كونتينر يُعيده. قاعدة
MASQUERADEفيPOSTROUTING. - دخولاً (DNAT) للنشر:
-p 8080:80= قاعدةٌ فيPREROUTING: أي حزمةٍ تصل المضيف على :8080، بدّل وجهتها إلى172.17.x.x:80.
bash افحص بعينك$ sudo iptables -t nat -L -n -v # MASQUERADE و DNAT التي كتبها Docker $ sudo conntrack -L # جدول التتبّع يربط الجلسات
هذه نفس القواعد التي ستكتبها بيدك. Docker مجرد كاتبٍ آلي لها. (التوزيعات الحديثة تستبدل iptables بـ nftables؛ المفهوم واحد. وبدائل eBPF مثل Cilium تتجاوزه لأداء أعلى — الفكرة الجذرية ثابتة.)
اشتقاق كل شبكات Docker من الثلاث
| driver | التركيب | متى |
|---|---|---|
bridge | veth + docker0 + NAT | العزل الافتراضي مع خروج |
host | لا net ns — يشارك مكدّس المضيف | أقصى أداء، صفر عزل شبكي |
none | net ns بلا veth | عزلٌ تام — حالة الإقليم I |
container:X | يشارك net ns كونتينرٍ آخر | هذا هو الـ Pod |
macvlan | واجهةٌ بعنوان MAC خاص على الـ LAN | الكونتينر يبدو جهازاً مستقلاً، بلا NAT |
overlay | VXLAN يغلّف L2 فوق L3 | شبكةٌ واحدة عبر عنقود مضيفات |
لاحظ كيف host وnone وcontainer:X كلها مجرد قرارٍ حول net namespace — حصادٌ مباشر للإقليم I. وoverlay بذرةٌ للأوركستريشن.
اللغز — استنسخ شبكة Docker الافتراضية كاملة
- البنية: جسر
mybr0(10.10.0.1/24)، ثلاث namespaces كلٌّ بـ veth إلى الجسر وعنوانٍ في الشبكة. أثبت أن الثلاثة تـ ping بعضها والبوّابة. - الخروج: فعّل
ip_forward، أضفMASQUERADE، أثبتping 8.8.8.8ثمcurl. تتبّع حزمةً فيconntrack. - النشر:
python3 -m http.server 80داخل namespace، انشره على:8080بـ DNAT، أثبتcurl localhost:8080من المضيف. - المقارنة الكاشفة: شغّل كونتينر Docker بـ
-p 8080:80، نفّذiptables -t nat -Sوقارن قواعده بقواعدك. ستجدها نفس البنية. اكتب جدول تطابق: قاعدتك ↔ قاعدة Docker. لحظة اليقين.
man ip-netns (قسم EXAMPLES كامل)، man iptables-extensions لـ MASQUERADE/DNAT. لفهم DNS المدمج جرّبه على شبكتين (افتراضية vs user-defined) ولاحظ الفرق.
الخلاصة — الوصل أُتقن
كل شبكات Docker صارت تركيباتٍ مفهومةً من هذه الثلاث + قرار net namespace من الإقليم I. -p, --network, docker0, embedded DNS, وقواعد iptables -t nat الغامضة — كلها اشتُقّت بيدك.
في الإقليم III رأيت أن الصورة شجرة Merkle من طبقاتٍ معنونةٍ بالمحتوى. هذا التصميم لم
يكن للتخزين فقط — كان للـ توزيع. حين تدفع صورةً يملك الـ registry نصف
طبقاتها، فلا تُرسل إلا الناقص. وحين تبني، نفس العنونة تقرّر أي طبقةٍ تُعاد. الإقليم
التالي يكشف كيف أن Dockerfile وpull/push وجهان لفكرةٍ واحدة.