توی این درس یاد میگیری چرا در تنظیمات برنامهات بهجای آدرس IP مینویسی db یا redis و همهچیز کار میکند: روی شبکههای سفارشی، داکر یک DNS داخلی (آدرس 127.0.0.11 داخل هر کانتینر) دارد که اسم کانتینرها و سرویسها را به IP تبدیل میکند. میفهمی چرا شبکهی پیشفرض این را ندارد، با alias چند اسم میدهی، دو کانتینر با یک alias را میبینی که DNS هر دو IP را برمیگرداند (round-robin)، و یک اپ را با اسم کانتینر به دیتابیس وصل میکنی. دستور آزمایش: docker exec app ping db.
تشبیه: دفترچهی تلفن شرکت
Section titled “تشبیه: دفترچهی تلفن شرکت”در یک شرکت، بهجای حفظ کردن شمارهی داخلی همه، میگویی «به مدیر فروش وصل کن». دفترچهی تلفن (DNS) اسم را به شماره تبدیل میکند. اگر کسی نقلمکان کند (IP عوض شود)، فقط دفترچه بهروز میشود و تو همچنان «مدیر فروش» را صدا میزنی. در داکر، IP کانتینرها با هر ساخت دوباره تغییر میکند؛ پس هیچوقت IP را در تنظیمات ننویس، اسم بنویس.
جریان resolve
Section titled “جریان resolve”مثالهای عملی
Section titled “مثالهای عملی”مثال ۱: DNS روی شبکهی پیشفرض و سفارشی
Section titled “مثال ۱: DNS روی شبکهی پیشفرض و سفارشی”docker run -d --name lx-dns1 alpine sleep 300 >/dev/nullecho "bridge پیشفرض، resolv.conf:"; docker exec lx-dns1 cat /etc/resolv.conf | grep nameserver | head -1docker network create lx-dnsnet >/dev/nulldocker run -d --name lx-dns2 --network lx-dnsnet alpine sleep 300 >/dev/nullecho "شبکهی سفارشی، resolv.conf:"; docker exec lx-dns2 cat /etc/resolv.conf | grep -E "nameserver|options" | head -2bridge پیشفرض، resolv.conf:nameserver 192.168.65.7شبکهی سفارشی، resolv.conf:nameserver 127.0.0.11options ndots:0روی شبکهی سفارشی nameserver برابر 127.0.0.11 است؛ همان DNS داخلی داکر. روی شبکهی پیشفرض، resolv.conf از میزبان کپی میشود و DNS داخلی داکر وجود ندارد. این تفاوت علت اینکه فقط شبکهی سفارشی resolve اسم دارد.
مثال ۲: resolve اسم کانتینر
Section titled “مثال ۲: resolve اسم کانتینر”docker run -d --name lx-dns3 --network lx-dnsnet alpine sleep 300 >/dev/nulldocker exec lx-dns2 nslookup lx-dns3 2>&1 | grep -E "^(Name|Address):" | tail -2docker exec lx-dns2 ping -c 1 -W 2 lx-dns3 | sed -n '1p;3p'echo "--- روی bridge پیشفرض:"docker exec lx-dns1 ping -c 1 -W 2 lx-dns3 2>&1 | head -1Name: lx-dns3Address: 172.20.0.3PING lx-dns3 (172.20.0.3): 56 data bytes
--- روی bridge پیشفرض:ping: bad address 'lx-dns3'داخل lx-dns2، اسم lx-dns3 به IP تبدیل شد و ping کار کرد. کانتینر lx-dns1 (روی شبکهی پیشفرض) نمیتواند lx-dns3 را resolve کند، حتی اگر روی یک میزبان باشند.
آدرس DNS داخلی داکر داخل کانتینر روی شبکهی سفارشی چیست؟
این آدرس loopback است که داکر به آن پاسخ میدهد؛ فقط روی شبکههای سفارشی.
مثال ۳: alias: چند اسم برای یک کانتینر
Section titled “مثال ۳: alias: چند اسم برای یک کانتینر”docker run -d --name lx-dnsdb --network lx-dnsnet --network-alias database --network-alias db alpine sleep 300 >/dev/nullfor n in lx-dnsdb database db; do echo "$n -> $(docker exec lx-dns2 getent hosts $n | awk '{print $1}')"; donelx-dnsdb -> 172.20.0.4database -> 172.20.0.4db -> 172.20.0.4با --network-alias یک کانتینر چند اسم دیگر در همان شبکه میگیرد. هر سه اسم به یک IP اشاره میکنند. برای اینکه اپ فقط اسم db را بداند و مستقل از اسم واقعی کانتینر باشد، alias عالی است (در Compose اسم هر سرویس خودکار یک alias است).
مثال ۴: round-robin: یک alias، چند کانتینر
Section titled “مثال ۴: round-robin: یک alias، چند کانتینر”docker run -d --name lx-w1 --network lx-dnsnet --network-alias web nginx:alpine >/dev/nulldocker run -d --name lx-w2 --network lx-dnsnet --network-alias web nginx:alpine >/dev/nullsleep 2docker exec lx-dns2 nslookup web 2>&1 | grep "^Address:" | tail -2Address: 172.20.0.6Address: 172.20.0.5وقتی دو کانتینر یک alias دارند، DNS هر دو IP را برمیگرداند و ترتیب را میچرخاند. یک load-balancing ساده در سطح DNS (یک راهکار کامل با Nginx یا Traefik در دورهی Nginx).
مثال ۵: اپ به دیتابیس با اسم
Section titled “مثال ۵: اپ به دیتابیس با اسم”یک «دیتابیس» (nginx که نقش یک سرویس را دارد) و یک «اپ» که با اسم به آن وصل میشود:
docker rm -f lx-w1 lx-w2 >/dev/nulldocker run -d --name lx-dnsapp --network lx-dnsnet alpine sleep 300 >/dev/nulldocker run -d --name lx-dns4 --network lx-dnsnet --network-alias backend nginx:alpine >/dev/nullsleep 2docker exec lx-dnsapp wget -qO- http://backend | head -3<!DOCTYPE html><html><head>اپ هیچ IP ای نمیدانست؛ فقط آدرس http://backend را زد و DNS داخلی آن را به کانتینر درست رساند. حالا ثابت کنیم IP عوض شود، اسم همچنان کار میکند:
echo "IP قبل: $(docker inspect lx-dns4 --format '{{(index .NetworkSettings.Networks "lx-dnsnet").IPAddress}}')"docker rm -f lx-dns4 >/dev/nulldocker run -d --name lx-dns8 --network lx-dnsnet alpine sleep 120 >/dev/null # IP آزادشده را یک کانتینر دیگر میگیردdocker run -d --name lx-dns4 --network lx-dnsnet --network-alias backend nginx:alpine >/dev/null; sleep 2echo "IP بعد: $(docker inspect lx-dns4 --format '{{(index .NetworkSettings.Networks "lx-dnsnet").IPAddress}}')"docker exec lx-dnsapp wget -qO- http://backend | head -1IP قبل: 172.20.0.6IP بعد: 172.20.0.7<!DOCTYPE html>حتی با IP جدید، http://backend بدون تغییر کار کرد؛ برای همین هرگز IP را در تنظیمات ننویس.
مثال ۶: DNS بیرونی هم کار میکند
Section titled “مثال ۶: DNS بیرونی هم کار میکند”docker exec lx-dnsapp nslookup example.com 2>&1 | grep -E "^Name:" | head -1Name: example.comبرای اسمهایی که بیرون از شبکهی داکرند (مثل example.com) DNS داخلی درخواست را به DNS میزبان forward میکند. پس هر دو نوع اسم کار میکنند.
پشت پرده
Section titled “پشت پرده”وقتی کانتینری روی یک شبکهی سفارشی شروع میشود، dockerd یک resolver داخلی کوچک را وارد network namespace آن میکند و /etc/resolv.conf را به 127.0.0.11 اشاره میدهد. این resolver جدول اسم کانتینر/alias → IP را برای همان شبکه نگه میدارد؛ اگر اسم در جدول نبود، درخواست را به DNS های میزبان میفرستد. شبکهی bridge پیشفرض قدیمیتر است و این resolver را ندارد (روش قدیمی --link برای آن بود که منسوخ شده).
دو نکتهی عملی:
- DNS داخلی محدود به همان شبکه است: کانتینری که روی شبکهی دیگر است، اسم را نمیبیند (مگر به هر دو شبکه وصل باشد).
- اسمها حساس به حروف بزرگ و کوچک نیستند (استاندارد DNS)، ولی بهتر است مثل
kebab-caseیکدست بنویسی.
جدولهای مرجع
Section titled “جدولهای مرجع”| ابزار | کاربرد |
|---|---|
docker exec app ping db |
آزمون ارتباط با اسم |
docker exec app nslookup db |
دیدن IP که DNS میدهد |
docker exec app getent hosts db |
resolve مثل خود برنامهها |
docker exec app cat /etc/resolv.conf |
دیدن nameserver |
docker run --network-alias A |
alias اضافه |
docker network inspect N |
کانتینرها و IP ها |
| شبکه | DNS اسم کانتینر؟ |
|---|---|
bridge پیشفرض |
خیر |
شبکهی سفارشی (docker network create) |
بله (127.0.0.11) |
host |
DNS میزبان (بدون resolve کانتینرها) |
| Compose | بله (اسم سرویسها) |
اشتباهات رایج
Section titled “اشتباهات رایج”۱) استفاده از IP بهجای اسم
Section titled “۱) استفاده از IP بهجای اسم”مثال ۵: IP با ساخت دوباره عوض میشود. راهحل: اسم کانتینر/سرویس/alias.
۲) انتظار DNS روی bridge پیشفرض
Section titled “۲) انتظار DNS روی bridge پیشفرض”docker exec lx-dns1 nslookup lx-dns3 2>&1 | grep -E "can't find|NXDOMAIN" | head -1** server can't find lx-dns3: NXDOMAINروی شبکهی پیشفرض اسم resolve نمیشود. راهحل: شبکهی سفارشی یا Compose.
۳) کانتینرها در شبکههای مختلف
Section titled “۳) کانتینرها در شبکههای مختلف”docker network create lx-dnsnet2 >/dev/nulldocker run -d --name lx-dns5 --network lx-dnsnet2 alpine sleep 120 >/dev/nulldocker exec lx-dns2 ping -c 1 -W 2 lx-dns5 2>&1 | head -1docker rm -f lx-dns5 >/dev/null; docker network rm lx-dnsnet2 >/dev/nullping: bad address 'lx-dns5'اسم در شبکهی دیگر دیده نمیشود. راهحل: هر دو را به یک شبکه وصل کن (docker network connect).
۴) اسم نامعتبر
Section titled “۴) اسم نامعتبر”اسم با زیرخط (_) یا کاراکتر عجیب در بعضی کتابخانهها مشکلساز است (hostname معتبر نیست). راهحل: از حروف، عدد و خط تیره استفاده کن.
۵) cache شدن DNS در برنامه
Section titled “۵) cache شدن DNS در برنامه”بعضی برنامهها (مثل JVM یا کتابخانههای قدیمی) IP را یک بار resolve و برای همیشه cache میکنند. اگر کانتینر مقصد دوباره ساخته شود، اتصال قدیمی خراب است. راهحل: TTL و retry در برنامه یا restart اپ.
آدرس nameserver داخل یک کانتینر روی شبکهی سفارشی را بخوان.
دیدن جواب
docker exec lx-dns2 cat /etc/resolv.conf | grep nameservernameserver 127.0.0.11تمرین اصلی: یک اپ را با اسم کانتینر به دیتابیس وصل کن. روی شبکهی lx-dnsnet یک nginx با alias database و یک کانتینر client بساز و از client با wget به http://database درخواست بزن.
دیدن جواب
docker run -d --name lx-dns6 --network lx-dnsnet --network-alias database nginx:alpine >/dev/nulldocker run -d --name lx-dns7 --network lx-dnsnet alpine sleep 120 >/dev/null; sleep 2docker exec lx-dns7 wget -qO- http://database | grep -o "<title>.*</title>"docker rm -f lx-dns6 lx-dns7 >/dev/null<title>Welcome to nginx!</title>ثابت کن با دو کانتینر همalias، nslookup هر دو IP را برمیگرداند. بعد یکی را حذف کن و ثابت کن حالا فقط یک IP مانده است.
دیدن جواب
docker run -d --name lx-r1 --network lx-dnsnet --network-alias svc nginx:alpine >/dev/nulldocker run -d --name lx-r2 --network lx-dnsnet --network-alias svc nginx:alpine >/dev/null; sleep 2echo "دو کانتینر: $(docker exec lx-dns2 nslookup svc 2>&1 | grep -c '^Address:.*172') آدرس"docker rm -f lx-r2 >/dev/null; sleep 1echo "یک کانتینر: $(docker exec lx-dns2 nslookup svc 2>&1 | grep -c '^Address:.*172') آدرس"docker rm -f lx-r1 >/dev/nullدو کانتینر: 2 آدرسیک کانتینر: 1 آدرسآزمونک
Section titled “آزمونک”DNS داخلی داکر فقط روی کدام شبکهها اسم کانتینر را resolve میکند؟
nameserver برابر 127.0.0.11.
چرا نباید IP کانتینر را در تنظیمات برنامه بنویسی؟
از اسم یا alias استفاده کن.
دو کانتینر با یک network-alias یکسان…
load-balancing ساده در سطح DNS.
کدام دستور resolve اسم را آزمایش میکند؟
یا getent hosts db یا ping db.
اگر اسم را کانتینری در شبکهی دیگر داشته باشد…
DNS داخلی به شبکه محدود است.
جمعبندی
Section titled “جمعبندی”- روی شبکهی سفارشی هر کانتینر
127.0.0.11را بهعنوان DNS دارد که اسم کانتینرها و alias ها را به IP تبدیل میکند. - bridge پیشفرض این resolver را ندارد؛ پس برای اسم، شبکهی سفارشی (یا Compose) بساز.
--network-aliasاسم اضافه میدهد؛ چند کانتینر با یک alias = round-robin DNS.- IP را در تنظیمات ننویس؛ از اسم استفاده کن (پایدار در برابر ساخت دوباره).
- اسمهای بیرونی به DNS میزبان forward میشوند.
| دستور | کاری که میکند |
|---|---|
docker exec app ping db | تست ارتباط با اسم |
docker exec app nslookup db | IP که DNS میدهد |
docker exec app getent hosts db | resolve مثل برنامهها |
docker exec app cat /etc/resolv.conf | nameserver (127.0.0.11) |
docker run --network N --network-alias A IMG | alias در شبکه |
docker network inspect N | کانتینرها و IP ها |