איך לנתב תעבורת מארח דרך מכולת Docker עם VPN
מכולת VPN היא מרחב שמות רשת, לא פרוקסי. בחרו netns משותף, מאזין SOCKS אמיתי או שער מנותב, ואז אמתו את נתיב היציאה.
מכולת המנהרה שלכם עובדת, docker compose exec vpn curl -s https://ifconfig.me עונה בכתובת הספק, ובכל זאת הדפדפן על שולחן העבודה שלכם עדיין יוצא דרך ספק האינטרנט שלכם. הפער הזה צפוי. מכולה שמריצה לקוח VPN מחזיקה מרחב שמות רשת, נתיבים וממשק מנהרה, וכלום שמדבר HTTP או SOCKS; פרסום פורט עליה לא יכול ליצור מאזין שמעולם לא היה שם.
היכן הלקוח רץ קובע את התכנון. מכולה אחות יכולה לעבור למרחב השמות של המנהרה, יישום שולחן עבודה לא יכול, ונתב צריך נתיבים ולא שדה פרוקסי. אם המיפוי שגוי, התסמין זהה: חיבור נדחה, או תעבורה שלוקחת בשקט את הנתיב הישן.
התאימו את הסידור למקום שבו הלקוח רץ
שאלו שאלה אחת לפני עריכת Compose: האם אפשר להפעיל את הלקוח בתוך מרחב השמות של מכולת ה-VPN? כל מה שכבר רץ כמכולת Linux יכול. דפדפן, IDE או CLI על השולחן שלכם לא יכול, כי הגדרת הפרוקסי שלו מצפה לשירות פרוטוקול, לא למרחב שמות.
| Client | Arrangement | What must already exist |
|---|---|---|
| Another Compose service | Shares the vpn namespace via network_mode: service:vpn | A working tunnel and correct start order |
| Host browser, IDE or CLI | Proxy listener inside the namespace, published to host loopback | A configured SOCKS or HTTP proxy and its access rules |
| Router, VM or phone | The container as a layer-3 gateway | Forwarding, NAT and a return route |
השלושה האלה לא מתערבבים. שדה פרוקסי HTTP שמכוון לכתובת הגשר של המכולה לא מגיע לשום דבר אלא אם פרוקסי מאזין שם, ומאזין פרוקסי לא מנתב תעבורה של נתב. החליטו קודם, ואז עקבו אחר הקטע המתאים.
שתפו את מרחב השמות של המנהרה עם מכולה אחות
זו האפשרות הזולה ביותר כי אין מעורב פרוטוקול פרוקסי. היישום מצטרף למרחב השמות של שירות vpn ויורש את הממשקים, הנתיבים ומרחב הפורטים שלו:
services:
vpn:
image: your-vpn-client
cap_add:
- NET_ADMIN
healthcheck:
test: ["CMD-SHELL", "ip link show tun0 >/dev/null"]
interval: 10s
retries: 5
app:
image: your-app
network_mode: service:vpn
depends_on:
vpn:
condition: service_healthyשלוש מגבלות נובעות מכך שמרחב השמות שייך לשירות אחר. היישום לא מקבל מפתח networks משלו, כי אין לו מרחב שמות להיצמד אליו. פרסום פורטים שייך ל-vpn, כי שם חי מרחב השמות. ושחזור vpn הורס את מרחב השמות שאליו הצטרף היישום, לכן docker compose up -d --force-recreate vpn משאיר לכם לשחזר גם את app; הפעלה מחדש רגילה לא תמיד מספיקה. הפניה לשירותי Compose מכסה את הסידור הזה ואת מגבלת פרסום הפורטים שלו.
אל תניחו שהפותר מגיע איתו. /etc/resolv.conf הוא קובץ לכל מכולה ולא חלק ממרחב שמות הרשת, לכן היישום עדיין יכול לשאול את הפותר של המארח בזמן שהחבילות שלו יוצאות דרך המנהרה. קראו אותו מתוך המכולה והגדירו dns: ביישום כששמות פרטיים חייבים להיפתר דרך המנהרה.
docker compose exec app ip route
docker compose exec app cat /etc/resolv.conf
docker compose exec app curl -s https://ifconfig.meאם רשימת הנתיבים מכילה את ממשק המנהרה והכתובת היא של הספק, היישום בתוך המנהרה. אם הממשק חסר, app התחיל לפני של-vpn הייתה מנהרה, אז שחזרו את app לאחר ש-vpn מדווח שהוא בריא.
הציבו מאזין פרוקסי אמיתי במרחב השמות עבור לקוחות המארח
דפדפן שולחן עבודה לא יכול להצטרף למרחב שמות, לכן המנהרה חייבת להיחשף כשירות. הריצו פרוקסי SOCKS או HTTP אמיתי בתוך מרחב השמות של vpn ופרסמו רק את הפורט שלו:
services:
vpn:
ports:
- "127.0.0.1:1080:1080"
proxy:
image: your-proxy
network_mode: service:vpn
command: ["--socks", "0.0.0.0:1080"]הכתובת בצד המארח היא החצי החשוב של המיפוי הזה. 127.0.0.1:1080 משמאל מרחיק את המאזין מה-LAN שלכם; "1080:1080" חשוף מפרסם פרוקסי פתוח לכל מכשיר שיכול להגיע למכונה שלכם.
לצד המכולה יש מלכודת משלו. פורטים שפורסמו נמסרים לכתובת המכולה בגשר, לא ל-loopback שלה, לכן פרוקסי שהופעל עם --socks 127.0.0.1:1080 בתוך מרחב השמות מקבל חיבורים מאחיות ומסרב לכל מה שמגיע דרך הפורט שפורסם. קשרו אותו ל-0.0.0.0 והסתמכו על קישור ה-loopback בצד המארח לבידוד. אותו כלל חל על כתובת ההאזנה בקובץ תצורת פרוקסי.
אחר כך החליטו היכן נפתרים השמות. ב-curl --socks5 פותר את שם המארח מקומית ו---socks5-hostname שולח את השם לפרוקסי; דפדפנים חושפים את אותו מתג תחת תווית אחרת. פתרון מקומי מדליף את השאילתה גם כשהדף נטען דרך המנהרה.
התייחסו למכולה כשער רק עם נתיב חזרה
נתבים, מכונות וירטואליות וטלפונים לא ישתמשו בשדה פרוקסי HTTP, וכמה יישומים מתעלמים לגמרי ממשתני פרוקסי. ניתוב התעבורה שלהם פירושו שכבה 3: המכולה מעבירה חבילות ומתרגמת כתובות בממשק המנהרה.
sysctl -w net.ipv4.ip_forward=1
iptables -t nat -A POSTROUTING -o tun0 -j MASQUERADEשני דברים קלים לפספס. הלקוח צריך נתיב שמפנה את שער ברירת המחדל שלו למארח הזה, והתשובות חייבות למצוא את הדרך חזרה, או בנתיב סטטי בנתב שמעל או ב-NAT בממשק הפונה ללקוחות. בלי אחד מאלה, המנהרה מוציאה את הבקשה והתשובה נעלמת.
יש לו גם רדיוס ההשפעה הרחב ביותר. מארח שמשמש כשער לכל ה-LAN שולח כל לקוח שהוגדר שגוי דרך חשבון ה-VPN שלכם, ונפילת מנהרה או מחסלת את ה-LAN או נופלת חזרה לספק בהתאם ל-kill switch.
אמתו את נתיב היציאה ואז חזרו אחורה לפי הסדר
בדקו לפי הסדר הזה ועצרו בכשל הראשון: כתובת יציאה בתוך מרחב השמות, כתובת יציאה מהמארח דרך הפרוקסי, ואז פתרון שמות עבור מארח פרטי.
docker compose exec vpn curl -s https://ifconfig.me
curl -s -x socks5h://127.0.0.1:1080 https://ifconfig.me
curl -s -x socks5h://127.0.0.1:1080 http://internal.example/healthשתי הכתובות הראשונות אמורות להתאים לספק, והשלישית אמורה להיפתר דרך הפרוקסי במקום להיכשל בשגיאת שם. התאמה בשורה הראשונה וכשל בשורה השנייה פירושם שהמאזין או הפורט שפורסם שגויים, לא המנהרה.
חזרה לאחור היא ההיפך מההתקנה. הסירו את משתני הסביבה של הפרוקסי מפרופיל ה-shell ומכל יחידת systemd, ואז הורידו את המחסנית כדי שמרחב השמות המשותף ייעלם עם הבעלים שלו. מחקו את הפורט שפורסם לפני מחיקת שירות הפרוקסי, אחרת המארח משאיר מאזין loopback שאיש לא קורא.
לפני שתמשיכו, אשרו ארבעה דברים: הלקוח שחשוב לכם באמת מגיע למנהרה, רק בעל מרחב השמות מפרסם פורטים, שמות פרטיים נפתרים במקום שהתכוונתם, ושום דבר לא מאזין בכתובת מארח שאינה loopback. כל אחד מאלה נכשל בשקט בייצור, וכל אחד הוא בדיקה של שורה אחת למעלה.