کانتینرها در LAN دفتر پاسخ می‌دهند و همان لحظه‌ای که VPN شرکت وصل می‌شود پاسخ دادن را متوقف می‌کنند. هیچ چیز در کانتینر تغییر نکرده است: نه راه‌اندازی مجدد، نه به‌روزرسانی ایمیج، نه ویرایش تنظیمات. آنچه تغییر کرده جدول مسیریابی میزبان است، جایی که مسیری که تونل اضافه کرده اکنون آدرسی را که Docker داده بود می‌پوشاند. پیش از ویرایش یک زیرشبکه یا درخواست استثنا از VPN، دو طرح آدرس را کنار هم بگذارید، چون تعمیرها زمان‌های بسیار متفاوتی می‌گیرند.

پیش از تغییر تنظیمات، مسیر را بخوانید

با شبکه‌ای شروع کنید که Docker واقعاً ساخته است. بلوک IPAM همان طرح آدرس است و زیرشبکه همان خطی است که اهمیت دارد:

docker network inspect project_default --format '{{json .IPAM.Config}}'
# [{"Subnet":"172.18.0.0/16","Gateway":"172.18.0.1"}]

Compose به‌طور پیش‌فرض به هر پروژه جدید یک /16 اختصاص می‌دهد، بنابراین دو سرویس می‌توانند در محدوده‌ای با 65,536 آدرس جا بگیرند. همین پهنا باعث می‌شود هم‌پوشانی رایج باشد: یک /16 که تصادفی انتخاب شده به‌قدری بزرگ است که سایتی را که از طریق تونل به آن می‌رسید ببلعد، و کسی تا بالا آمدن تونل متوجه نمی‌شود.

سپس انتخاب مسیر را برای یک آدرس کانتینر مقایسه کنید، یک بار با تونل پایین و یک بار با تونل بالا:

ip route get 172.18.0.7
# 172.18.0.7 dev br-3f2a9c src 172.18.0.1

دو شکل پاسخ به جاهای متفاوتی می‌رسند. اگر خروجی tun0، wg0 یا دستگاه تونل دیگری را نام ببرد، پیشوند VPN دست‌کم به‌اندازه مسیر پل مشخص است و بسته هرگز به کانتینر نمی‌رسد. اگر هنوز پل را نام می‌برد، میزبان می‌تواند به کانتینر برسد و ایراد شما فیلترکردن، DNS یا مسیر بازگشتی است که فقط در یک جهت شکست می‌خورد.

چرا برخورد /16 آسان است

Docker شبکه‌های پل را از استخرهای خصوصی برمی‌دارد و استخر پیش‌فرض از بلوک 172.16.0.0/12 در برش‌های /16 می‌آید. شبکه‌های سازمانی اغلب از همان فضای خصوصی استفاده می‌کنند و VPN دسترسی از راه دور آن پیشوندها را به هر کلاینتی که وصل می‌شود هل می‌دهد. وقتی هر دو طرف باور دارند که مالک 172.18.0.0/16 هستند، یک جدول مسیریابی دو ادعای متناقض را نگه می‌دارد.

طول پیشوند برنده را تعیین می‌کند، نه ترتیبی که مسیرها ظاهر می‌شوند. تونلی که 10.0.0.0/8 را هل می‌دهد بر مسیر پل 172.18.0.0/16 پیروز نمی‌شود، چون /8 کم‌تر مشخص است. تونلی که 172.18.0.0/16، یا 172.18.5.0/24 را درون آن هل می‌دهد، پیروز می‌شود. به همین دلیل یک لپ‌تاپ در شبکه دفتر کار می‌کند و در Wi-Fi هتل با همان VPN شکست می‌خورد: کلاینت پیشوندهای متفاوتی دریافت می‌کند.

همان جدول جهت دیگر را هم اداره می‌کند. کانتینری که به میزبان داخلی روی 10.8.4.20 نیاز دارد بسته را به دروازه پل خود می‌فرستد و میزبان سپس آن را به تونل مسیریابی می‌کند. این مسیر معمولاً کار می‌کند چون میزبان NAT را انجام می‌دهد، اما وقتی طرف سازمانی فقط آدرس VPN لپ‌تاپ را می‌پذیرد یا وقتی فیلترکردن مسیر معکوس پاسخ نامتقارن را دور می‌ریزد، شکست می‌خورد.

سه تعمیر را مقایسه کنید

فقط سه راه خروج وجود دارد و تفاوتشان در این است که چه کسی باید تأییدشان کند و هنگام اعمال یکی چه چیزی می‌شکند.

RepairWho owns itWhat it costs
Renumber the container networkYouRecreates the network, restarts services, invalidates literal addresses in configs and scripts
Exclude your range from the tunnelNetwork teamChanges what every laptop sees, needs a change request and a client profile update
Reach the service through a published portYou and the clientOne port only, no direct container-to-container routing across the tunnel

تغییر شماره‌گذاری تنها تعمیری است که می‌توانید بدون تیکت تمام کنید، و به همین دلیل بیشتر نوشته‌ها همان‌جا متوقف می‌شوند. در پروژه‌ای جوان ارزان است و به‌محض اینکه ابزار مهاجرت یا رشته اتصال ذخیره‌شده به یک آدرس تحت‌اللفظی اشاره کند گران می‌شود. آن ارجاع‌ها را پیش از ویرایش YAML بشمارید، نه بعد از آن.

استثنا کردن از تونل پاک‌ترین پاسخ است وقتی هم‌پوشانی در محدوده‌ای قرار دارد که شرکت شما واقعاً مالک آن است. همچنین کندترین است و برای پیمانکاری که نمی‌تواند پروفایل کلاینت را تغییر دهد بی‌فایده است. یک پورت منتشرشده یا یک پراکسی معکوس کوچک از بحث دوری می‌کند: کلاینت با آدرسی صحبت می‌کند که تونل از قبل آن را درست مسیریابی می‌کند و فقط همان پورت باید در دسترس باشد.

یک محدوده را عمداً ثابت کنید

برای یک پروژه، زیرشبکه را در فایل compose اعلام کنید تا پروژه از استخر مشترک برندارد:

networks:
  default:
    ipam:
      config:
        - subnet: 10.201.7.0/24

آن محدوده را از ثبتی بگیرید که تیمتان نگه می‌دارد، نه از نمونه‌ای که هنگام جستجو پیدا کردید. آن را با پیشوندهایی که VPN هل می‌دهد، LANهای خانگی که مردم از آن کار می‌کنند و شبکه‌های کانتینر دیگر روی همان میزبان بسنجید. سپس بازسازی را برنامه‌ریزی کنید، چون شبکه موجود زیرشبکه قدیمی خود را نگه می‌دارد: docker compose down، سپس up -d. docker network rm تا وقتی نقاط پایانی متصل‌اند امتناع می‌کند و هر ipv4_address ورودی در فایل باید داخل محدوده جدید بیفتد.

برای شبکه‌هایی که Docker خودش می‌سازد، یک استخر دیمن در /etc/docker/daemon.json تعیین کنید:

{
  "default-address-pools": [
    { "base": "10.201.0.0/16", "size": 24 },
    { "base": "10.202.0.0/16", "size": 24 }
  ]
}

استخر برای شبکه‌هایی اعمال می‌شود که پس از راه‌اندازی مجدد دیمن ساخته می‌شوند و پل‌هایی که از قبل وجود دارند آدرس‌های خود را نگه می‌دارند. این راحت و آسان برای فراموش کردن است، پس با یک پروژه قدیمی تا زمانی که کسی شبکه‌اش را بازسازی کند مانند یک هم‌پوشانی رفتار کنید. مستندات Docker درباره شبکه‌سازی موتور قواعد تخصیص را توضیح می‌دهد اگر لازم است از استخر در بازبینی دفاع کنید.

پل Linux و Docker Desktop متفاوت شکست می‌خورند

روی میزبان Linux، کانتینرها روی پل‌هایی در فضای نام میزبان می‌نشینند. یک جدول همه چیز را تعیین می‌کند، پس مقایسه بالا کل ماجراست: مسیر پل را بخوانید، مسیر تونل را بخوانید و ببینید برای یک آدرس واقعی کانتینر کدام برنده است.

Docker Desktop کانتینرها را پشت VMی با NAT خودش می‌گذارد. میزبان هیچ مسیری به آدرس کانتینر ندارد؛ از طریق یک پورت منتشرشده که معمولاً به 127.0.0.1 بسته می‌شود به سرویس می‌رسد. بنابراین یک کلاینت VPN می‌تواند سرویس را بدون هیچ هم‌پوشانی زیرشبکه‌ای خراب کند، یا با نصب مسیری برای محدوده داخلی VM یا با مسدود کردن ترافیک loopback وقتی تونل وصل است. اگر ip route get روی میزبان گزارش دهد که مسیری برای آدرس کانتینر وجود ندارد، این انتظار می‌رود و نه خود ایراد.

پیامد عملی این است که اول کجا را نگاه کنید. در Linux از جدول مسیریابی شروع کنید. در Desktop از پورت منتشرشده و قواعد فایروال کلاینت VPN شروع کنید، سپس بررسی کنید آیا زیرشبکه داخلی VM با چیزی که تونل هل می‌دهد برخورد دارد یا نه.

با تونل بالا بررسی کنید

پیش از اینکه بگویید درست شد، VPN را دو بار دوباره وصل کنید، چون پروفایلی که هنگام اتصال مسیرها را هل می‌دهد و هنگام قطع کردن برمی‌گرداند ممکن است بعد از تلاش اول درست به نظر برسد. سپس بررسی‌هایی را اجرا کنید که کلاینت واقعاً اجرا می‌کند:

  1. ip route get از میزبان، با استفاده از آدرس واقعی کانتینر.
  2. درخواستی از کانتینر دیگر، برای اثبات اینکه خود سرویس سالم است.
  3. curl -sv http://127.0.0.1:8080/ از کلاینت با تونل بالا، سپس traceroute -n به آدرس کانتینر از ماشینی در LAN.
  4. sysctl net.ipv4.conf.all.rp_filter اگر درخواست به میزبان برسد اما پاسخ ناپدید شود.

در پایان، محدوده انتخاب‌شده را در همان ثبتی بنویسید که برای انتخابش استفاده کردید، با تاریخ و پروژه‌ای که مالک آن است. هم‌پوشانی بعدی از پروژه‌ای می‌آید که کسی مستندش نکرده است و تنها دفاع این است که محدوده عمداً رزرو شده باشد نه تصادفی کشیده.

مقالات مرتبط