رفتن به محتوا
LoopX

شبکه در داکر

توی این درس یاد می‌گیری کانتینرها چطور با هم و با دنیای بیرون حرف می‌زنند: داکر برای هر کانتینر یک شبکه‌ی مجازی می‌سازد. سه نوع اصلی را می‌شناسی: bridge (پیش‌فرض)، host و none، و یاد می‌گیری چطور با docker network create یک شبکه‌ی سفارشی بسازی، کانتینرها را به آن وصل کنی (docker run --network) و ایزوله‌سازی بین شبکه‌ها را ببینی. دستورهای اصلی: docker network create، docker network ls، docker network inspect و docker network connect.

تشبیه: ساختمان و واحدهای آن

Section titled “تشبیه: ساختمان و واحدهای آن”

شبکه‌ی داکر مثل راهروهای یک ساختمان است. کانتینرهایی که در یک راهرو (یک شبکه) باشند، به هم دسترسی دارند؛ کانتینرهای راهروهای مختلف نمی‌توانند هم‌دیگر را ببینند، مگر اینکه یک نفر در هر دو راهرو باشد. و بیرون ساختمان (اینترنت و میزبان) هم فقط از «دروازه‌هایی» که تو باز می‌کنی (-p) در دسترس است.

bridge: شبکه‌ی خصوصی روی میزبان (پیش‌فرض)؛ host: بدون ایزولاسیون شبکه؛ none: بدون شبکه.

مثال ۱: شبکه‌های موجود (docker network ls)

Section titled “مثال ۱: شبکه‌های موجود (docker network ls)”
Terminal window
docker network ls --format 'table {{.Name}}\t{{.Driver}}\t{{.Scope}}' | grep -E '^(NAME|bridge|host|none)'
خروجی
NAME DRIVER SCOPE
bridge bridge local
host host local
none null local

داکر همیشه سه شبکه‌ی آماده دارد: bridge (پیش‌فرض)، host و none. هر کانتینری که با docker run (بدون --network) بسازی روی شبکه‌ی bridge پیش‌فرض می‌رود. (اگر پروژه‌های Compose دیگری روی سیستم داشته باشی، شبکه‌هایشان هم در docker network ls دیده می‌شود؛ ما فقط سه شبکه‌ی پیش‌فرض را فیلتر کردیم.)

مثال ۲: شبکه‌ی پیش‌فرض و IP کانتینرها

Section titled “مثال ۲: شبکه‌ی پیش‌فرض و IP کانتینرها”
Terminal window
docker run -d --name lx-n1 alpine sleep 300 >/dev/null
docker run -d --name lx-n2 alpine sleep 300 >/dev/null
docker inspect lx-n1 lx-n2 --format '{{.Name}}: {{.NetworkSettings.Networks.bridge.IPAddress}}'
IP2=$(docker inspect lx-n2 --format '{{.NetworkSettings.Networks.bridge.IPAddress}}')
docker exec lx-n1 ping -c 1 -W 2 $IP2 | sed -n '1p;3p'
خروجی
/lx-n1: 172.17.0.4
/lx-n2: 172.17.0.5
PING 172.17.0.5 (172.17.0.5): 56 data bytes

هر کانتینر یک آدرس IP خصوصی گرفت و با IP می‌توانند به هم برسند. ولی با اسم چطور؟

Terminal window
docker exec lx-n1 ping -c 1 -W 2 lx-n2 2>&1 | head -2
خروجی
ping: bad address 'lx-n2'

روی شبکه‌ی bridge پیش‌فرض، resolve کردن اسم کانتینر کار نمی‌کند (bad address). دلیل و راه‌حلش در مثال بعد و درس بعدی است.

⚡ بررسی سریع

کانتینرها روی شبکه‌ی bridge پیش‌فرض می‌توانند همدیگر را با اسم پیدا کنند؟

مثال ۳: شبکه‌ی سفارشی (docker network create)

Section titled “مثال ۳: شبکه‌ی سفارشی (docker network create)”
Terminal window
docker rm -f lx-n1 lx-n2 >/dev/null
docker network create lx-net-a
docker network ls --filter name=lx-net-a --format 'table {{.Name}}\t{{.Driver}}'
docker run -d --name lx-n1 --network lx-net-a alpine sleep 300 >/dev/null
docker run -d --name lx-n2 --network lx-net-a alpine sleep 300 >/dev/null
docker exec lx-n1 ping -c 1 -W 2 lx-n2 | sed -n '1p;3p'
خروجی
5ec798beb39079e92543c9b8cd87da1ba7e3ad9c2985adbd1c01b6bfd42120f7
NAME DRIVER
lx-net-a bridge
PING lx-n2 (172.20.0.3): 56 data bytes

حالا دو کانتینر در شبکه‌ی lx-net-a هستند و ping lx-n2 با اسم کار کرد. چون شبکه‌ی سفارشی یک DNS داخلی دارد که اسم کانتینرها را به IP تبدیل می‌کند (درس بعد). docker network create به‌صورت پیش‌فرض یک شبکه‌ی bridge می‌سازد.

مثال ۴: ایزوله‌سازی بین شبکه‌ها

Section titled “مثال ۴: ایزوله‌سازی بین شبکه‌ها”
Terminal window
docker network create lx-net-b >/dev/null
docker run -d --name lx-n3 --network lx-net-b alpine sleep 300 >/dev/null
echo "از lx-n1 (شبکه a) به lx-n3 (شبکه b):"
docker exec lx-n1 ping -c 1 -W 2 lx-n3 2>&1 | head -1
IP3=$(docker inspect lx-n3 --format '{{(index .NetworkSettings.Networks "lx-net-b").IPAddress}}')
docker exec lx-n1 ping -c 1 -W 2 $IP3 2>&1 | grep -E 'packet loss'
خروجی
از lx-n1 (شبکه a) به lx-n3 (شبکه b):
ping: bad address 'lx-n3'
1 packets transmitted, 0 packets received, 100% packet loss

کانتینرها در دو شبکه‌ی مختلف نه با اسم و نه با IP همدیگر را نمی‌بینند (بسته‌ها ۱۰۰٪ گم می‌شوند). این ایزوله‌سازی پیش‌فرض است و پایه‌ی معماری امن.

مثال ۵: اتصال یک کانتینر به دو شبکه (docker network connect)

Section titled “مثال ۵: اتصال یک کانتینر به دو شبکه (docker network connect)”
Terminal window
docker network connect lx-net-b lx-n1
docker inspect lx-n1 --format '{{range $k, $v := .NetworkSettings.Networks}}{{$k}} {{end}}'
docker exec lx-n1 ping -c 1 -W 2 lx-n3 | sed -n '1p;3p'
خروجی
lx-net-a lx-net-b
PING lx-n3 (172.21.0.2): 56 data bytes

حالا lx-n1 در هر دو شبکه است و پلی بین‌شان شده؛ lx-n3 را با اسم می‌بیند. این همان الگوی «API در frontend و backend» از سناریو است: API هر دو را می‌بیند، ولی وب‌اپ و دیتابیس همدیگر را نه.

Terminal window
echo "none:"; docker run --rm --network none alpine sh -c 'ip -o addr | awk "{print \$2, \$4}"; ping -c 1 -W 2 8.8.8.8 2>&1 | tail -1'
echo "host (روی Docker Desktop یعنی شبکه‌ی ماشین مجازی داکر):"; docker run --rm --network host alpine sh -c 'ip link show docker0 >/dev/null 2>&1 && echo "رابط docker0 (مال میزبان) را می‌بینم"; echo "تعداد رابط‌های شبکه: $(ip -o link | wc -l)"'
خروجی
none:
lo 127.0.0.1/8
lo ::1/128
ping: sendto: Network unreachable
host (روی Docker Desktop یعنی شبکه‌ی ماشین مجازی داکر):
رابط docker0 (مال میزبان) را می‌بینم
تعداد رابط‌های شبکه: 32

با none فقط loopback (lo) هست و هیچ‌چیز بیرون نمی‌رود. با host کانتینر مستقیم از شبکه‌ی میزبان استفاده می‌کند (رابط‌هایی مثل docker0 که مال خود میزبان است در دسترسش هستند؛ در حالت عادی هیچ‌کدام از این‌ها را نمی‌دید) و دیگر -p معنی ندارد. نکته: روی مک و ویندوز «میزبان» یعنی همان VM لینوکسی داکر، نه خود سیستم تو؛ پس --network host برای دسترسی به localhost مک جواب نمی‌دهد. حالت host عملاً برای لینوکس است.

شبکه‌ی bridge روی لینوکس با یک پل مجازی (virtual bridge، مثلاً docker0 یا br-...) و جفت‌های veth ساخته می‌شود: هر کانتینر یک سر کابل مجازی دارد که داخل network namespace خودش است و سر دیگر به پل وصل است. خروج به اینترنت از طریق NAT (قانون‌های فایروال) انجام می‌شود؛ ورود از بیرون فقط به پورت‌هایی که با -p منتشر کرده‌ای.

ببین یک شبکه چه ساختاری دارد:

Terminal window
docker network inspect lx-net-a --format 'driver: {{.Driver}}
subnet: {{(index .IPAM.Config 0).Subnet}}
gateway: {{(index .IPAM.Config 0).Gateway}}
containers: {{range .Containers}}{{.Name}} {{end}}'
خروجی
driver: bridge
subnet: 172.20.0.0/16
gateway: 172.20.0.1
containers: lx-n2 lx-n1

هر شبکه یک subnet (بازه‌ی IP) و یک gateway دارد؛ و هر شبکه‌ی سفارشی بازه‌ی جدا می‌گیرد، به همین دلیل ایزوله‌اند.

درایور ایزولاسیون کاربرد
bridge شبکه‌ی خصوصی با NAT پیش‌فرض و بیشترین استفاده
host هیچ (شبکه‌ی میزبان) کارایی بالا، لینوکس
none کامل (فقط loopback) کار ایزوله و بدون شبکه
overlay چند میزبان (Swarm) خوشه‌های چندسرور
دستور کار
docker network ls فهرست شبکه‌ها
docker network create N ساخت شبکه‌ی bridge سفارشی
docker network inspect N جزئیات (subnet، کانتینرها)
docker run --network N IMG اجرا روی یک شبکه
docker network connect N C وصل کردن کانتینر در حال اجرا به شبکه
docker network disconnect N C جدا کردن
docker network rm N حذف (باید خالی باشد)

۱) انتظار resolve اسم روی bridge پیش‌فرض

Section titled “۱) انتظار resolve اسم روی bridge پیش‌فرض”

مثال ۲: bad address. راه‌حل: شبکه‌ی سفارشی بساز و کانتینرها را به آن وصل کن.

۲) حذف شبکه‌ی در حال استفاده

Section titled “۲) حذف شبکه‌ی در حال استفاده”
Terminal window
docker network rm lx-net-a
خروجی
Error response from daemon: error while removing network: network lx-net-a has active endpoints (name:"lx-n2" id:"ee3221d6ac63", name:"lx-n1" id:"cd3b20e4006b")
exit status 1

تا کانتینری به شبکه وصل است حذف نمی‌شود (active endpoints). راه‌حل: اول کانتینرها را حذف یا disconnect کن.

Terminal window
docker network create lx-net-b
خروجی
Error response from daemon: network with name lx-net-b already exists

اسم شبکه یکتاست (network with name ... already exists). راه‌حل: اسم دیگر یا حذف قبلی.

۴) فکر کردن «کانتینرها به هم می‌رسند چون روی یک میزبان‌اند»

Section titled “۴) فکر کردن «کانتینرها به هم می‌رسند چون روی یک میزبان‌اند»”

مثال ۴: نه؛ شبکه‌های مختلف ایزوله‌اند. راه‌حل: همه را در یک شبکه بگذار یا docker network connect.

۵) استفاده از --network host روی مک/ویندوز برای دسترسی به localhost

Section titled “۵) استفاده از --network host روی مک/ویندوز برای دسترسی به localhost”

فایده ندارد (میزبان = VM داکر). راه‌حل: -p یا host.docker.internal.

✎ تمرینآسان

یک شبکه به اسم lx-net-a را فهرست کن و subnet اش را با docker network inspect بخوان.

دیدن جواب
Terminal window
docker network inspect lx-net-a --format '{{(index .IPAM.Config 0).Subnet}}'
خروجی
172.20.0.0/16
✎ تمرینمتوسط

تمرین اصلی: دو کانتینر را در یک شبکه‌ی سفارشی به هم وصل کن. شبکه‌ی lx-net-c را بساز، دو کانتینر alpine (lx-n4 و lx-n5) در آن بگذار و از یکی به دیگری با اسم ping بزن.

دیدن جواب
Terminal window
docker network create lx-net-c >/dev/null
docker run -d --name lx-n4 --network lx-net-c alpine sleep 120 >/dev/null
docker run -d --name lx-n5 --network lx-net-c alpine sleep 120 >/dev/null
docker exec lx-n4 ping -c 1 -W 2 lx-n5 | sed -n '1p;3p'
docker rm -f lx-n4 lx-n5 >/dev/null; docker network rm lx-net-c
خروجی
PING lx-n5 (172.22.0.3): 56 data bytes
lx-net-c
✎ تمرینسخت

سناریوی درس را بساز: شبکه‌های lx-frontend و lx-backend؛ کانتینر web فقط در frontend، db فقط در backend و api در هر دو. ثابت کن web می‌تواند api را ببیند ولی نمی‌تواند db را ببیند، و api می‌تواند db را ببیند.

دیدن جواب
Terminal window
docker network create lx-frontend >/dev/null; docker network create lx-backend >/dev/null
docker run -d --name web --network lx-frontend alpine sleep 120 >/dev/null
docker run -d --name db --network lx-backend alpine sleep 120 >/dev/null
docker run -d --name api --network lx-frontend alpine sleep 120 >/dev/null
docker network connect lx-backend api
echo "web -> api: $(docker exec web ping -c 1 -W 2 api >/dev/null 2>&1 && echo OK || echo FAIL)"
echo "web -> db: $(docker exec web ping -c 1 -W 2 db >/dev/null 2>&1 && echo OK || echo FAIL)"
echo "api -> db: $(docker exec api ping -c 1 -W 2 db >/dev/null 2>&1 && echo OK || echo FAIL)"
docker rm -f web db api >/dev/null; docker network rm lx-frontend lx-backend >/dev/null
خروجی
web -> api: OK
web -> db: FAIL
api -> db: OK
؟ آزمونک
  1. کانتینری که بدون --network اجرا شود، روی کدام شبکه می‌رود؟

  2. کدام درایور کانتینر را کاملاً بی‌شبکه می‌کند؟

  3. چرا کانتینرهای دو شبکه‌ی سفارشی مختلف همدیگر را نمی‌بینند؟

  4. docker network connect چه می‌کند؟

  5. روی مک --network host به چه شبکه‌ای وصل می‌کند؟

  • سه شبکه‌ی آماده: bridge (پیش‌فرض)، host، none؛ هر docker run معمولاً روی bridge پیش‌فرض می‌رود.
  • شبکه‌ی bridge پیش‌فرض DNS اسم ندارد؛ شبکه‌ی سفارشی (docker network create) دارد.
  • کانتینر روی شبکه‌ی سفارشی با --network N اجرا می‌شود؛ شبکه‌های مختلف ایزوله‌اند.
  • docker network connect یک کانتینر را به چند شبکه می‌برد (الگوی پل بین frontend و backend).
  • host برای کارایی روی لینوکس؛ none برای ایزولاسیون کامل.
برگه‌ی تقلب این درس
دستورکاری که می‌کند
docker network lsفهرست شبکه‌ها
docker network create NAMEساخت شبکه‌ی سفارشی
docker network inspect NAMEجزئیات شبکه
docker run --network NAME IMGاجرا روی یک شبکه
docker network connect NAME CONTAINERوصل کردن کانتینر
docker network disconnect NAME CONTAINERجدا کردن
docker network rm NAMEحذف شبکه (باید خالی باشد)
docker run --network none IMGبدون شبکه
docker run --network host IMGشبکه‌ی میزبان