วิธีตั้งค่า Gluetun ให้เข้าถึง Host และคอนเทนเนอร์อื่น
เมื่อคอนเทนเนอร์ใช้ network namespace ของ Gluetun จะไม่มีอินเทอร์เฟซเป็นของตัวเอง เรียนรู้วิธีเปิดพอร์ตผ่าน Gluetun และกำหนดค่า Subnet เพื่อให้สื่อสารกับเครือข่ายภายนอกได้
เกิดอะไรขึ้นเมื่อคอนเทนเนอร์เข้าร่วมเครือข่ายของ gluetun
คอนเทนเนอร์ที่ตั้งค่า network_mode: service:gluetun จะไม่มีอินเทอร์เฟซเครือข่ายเป็นของตัวเอง คอนเทนเนอร์นั้นจะเข้าไปอยู่ใน network namespace ของ gluetun ส่งผลให้การเปิดพอร์ต (port publishing) และกฎของไฟร์วอลล์ไม่ได้ขึ้นอยู่กับคอนเทนเนอร์นั้นอีกต่อไป แต่จะกลายเป็นคุณสมบัติของบริการ gluetun แทน คำตอบทุกข้อด้านล่างนี้ล้วนเป็นผลสืบเนื่องมาจากข้อเท็จจริงดังกล่าว
network namespace คือสำเนาส่วนตัวของ stack เครือข่ายที่ kernel จัดเตรียมไว้ให้ ซึ่งประกอบด้วยอินเทอร์เฟซ ตาราง routing กฎของไฟร์วอลล์ และ listening socket ของตัวเอง โดยปกติ Docker จะสร้าง namespace ให้คอนเทนเนอร์ละหนึ่งชุด แต่เมื่อคุณระบุ network_mode: service:gluetun ตัว Docker จะข้ามขั้นตอนนี้ไปและนำคอนเทนเนอร์ใหม่เข้าไปไว้ใน namespace ที่ gluetun ใช้งานอยู่แล้ว คอนเทนเนอร์ยังคงมีระบบไฟล์และไฟล์ /etc/hosts เป็นของตัวเอง ซึ่งไฟล์หลังนี้จะมีความสำคัญในภายหลัง
คุณสามารถตรวจสอบเรื่องนี้ได้โดยตรง
docker inspect -f '{{.HostConfig.NetworkMode}}' qbittorrentคำสั่งดังกล่าวจะแสดงผล container: ตามด้วย ID ของคอนเทนเนอร์ gluetun ในขณะที่คอนเทนเนอร์ทั่วไปจะแสดงผลเป็น bridge คู่มือนี้จะอธิบายต่อจากส่วน การส่งผ่านทราฟฟิก Docker ผ่าน VPN ด้วย gluetun ซึ่งในขณะนี้อุโมงค์เครือข่ายทำงานได้แล้ว แต่ยังไม่มีสิ่งใดสามารถสื่อสารกับคอนเทนเนอร์ได้
เผยแพร่พอร์ตบน gluetun ไม่ใช่บนแอปพลิเคชัน
การทิ้งบล็อก ports: ไว้บนเซอร์วิสที่ตั้งค่า network_mode จะทำให้ Docker ปฏิเสธการสร้างคอนเทนเนอร์:
Error response from daemon: conflicting options: port publishing and the container type network modeสาเหตุนั้นชัดเจน การเผยแพร่พอร์ตหมายถึงการเพิ่มกฎ NAT (network address translation) เพื่อส่งต่อพอร์ตของโฮสต์เข้าไปยัง network namespace ของคอนเทนเนอร์ แต่คอนเทนเนอร์นี้ไม่มี namespace เป็นของตนเอง ให้ย้ายการแมปพอร์ตไปไว้ที่เซอร์วิส gluetun แทน หมายเลขพอร์ตไม่จำเป็นต้องเปลี่ยนแปลง เนื่องจากแอปพลิเคชันยังคงฟังคำขอที่พอร์ตนั้นภายใน shared namespace เดิม
services:
gluetun:
ports:
- "8080:8080/tcp" # qBittorrent web UI
qbittorrent:
network_mode: "service:gluetun"
# no ports: block hereบล็อก expose: บนเซอร์วิสที่เป็น dependency ก็ไม่มีประโยชน์เช่นกัน และบล็อก networks: ในตำแหน่งนั้นจะทำให้เกิดการหยุดทำงานทันที: Compose จะรายงานว่าเซอร์วิสมีการประกาศ network_mode และ networks ที่ขัดแย้งกันเอง และปฏิเสธที่จะโหลดไฟล์ดังกล่าว
ผลกระทบประการหนึ่งจะปรากฏในภายหลัง คอนเทนเนอร์ทุกตัวใน namespace เดียวกันจะใช้พื้นที่พอร์ตชุดเดียวกัน ดังนั้นหากแอปพลิเคชันสองตัวมีค่าเริ่มต้นเป็น 8080 ทั้งคู่ จะเกิดการชนกัน และตัวที่เริ่มทำงานทีหลังจะล้มเหลวพร้อมข้อความแจ้งว่า address already in use ให้เปลี่ยนค่าพอร์ตของแอปพลิเคชันตัวใดตัวหนึ่งในการตั้งค่าของแอปนั้นเอง ตัวอย่างเช่น ตัวแปร WEBUI_PORT ในอิมเมจ qBittorrent ของ LinuxServer จากนั้นจึงเผยแพร่หมายเลขพอร์ตใหม่บน gluetun
คอนเทนเนอร์ที่อยู่หลัง gluetun สื่อสารกันเองได้อย่างไร
ภายในเนมสเปซเดียวกัน คอนเทนเนอร์เหล่านี้ใช้ loopback interface ร่วมกันอยู่แล้ว คอนเทนเนอร์ที่อยู่หลัง gluetun จึงสามารถเข้าถึงคอนเทนเนอร์พี่น้องได้ที่ 127.0.0.1:<port> โดยไม่ต้องผ่าน Docker network ใดๆ
จากภายนอกเนมสเปซ คอนเทนเนอร์นี้จะไม่มีชื่อเรียก ระบบ DNS ฝังตัวของ Docker จะแปลงชื่อเซอร์วิสให้เป็นที่อยู่บนเครือข่ายที่ผู้ใช้กำหนด แต่คอนเทนเนอร์นี้ไม่มีที่อยู่บนเครือข่ายใดเลย ดังนั้นคอนเทนเนอร์ทั่วไปอย่าง Sonarr จึงไม่สามารถเข้าถึงไคลเอนต์ torrent ได้ที่ http://qbittorrent:8080 แต่จะเข้าถึงได้ที่ http://gluetun:8080 เนื่องจากซ็อกเก็ตกำลังรอรับการเชื่อมต่ออยู่ในเนมสเปซของ gluetun และใช้ที่อยู่ของ gluetun สิ่งนี้มักทำให้ผู้ที่เข้าใจ วิธีการทำงานของ Docker Compose network และชื่อเซอร์วิส และคาดหวังว่าจะใช้การตั้งชื่อแบบปกติเกิดความสับสน นอกจากนี้ยังสามารถทำงานได้โดยไม่ต้องเปิดพอร์ตใดๆ ออกสู่โฮสต์ เนื่องจากคอนเทนเนอร์ทั้งสองอยู่บน Compose network เดียวกัน
ให้ตรวจสอบ DNS ก่อนเริ่มแก้ไขปัญหาอื่นใด gluetun รันตัวแก้ไขชื่อ (resolver) ของตัวเองและเขียนทับไฟล์ /etc/resolv.conf ภายในคอนเทนเนอร์ของมันเอง แต่ไฟล์ /etc/resolv.conf เป็นไฟล์เฉพาะของแต่ละคอนเทนเนอร์ ดังนั้นไฟล์ที่ gluetun เขียนขึ้นจึงไม่ใช่ไฟล์ที่แอปพลิเคชันของคุณอ่าน
docker exec qbittorrent cat /etc/resolv.confฉันจะเข้าถึงบริการที่ทำงานอยู่บน Docker host ได้อย่างไร
ให้ใช้ host.docker.internal โดยต้องตั้งค่าสองส่วนในตำแหน่งที่ต่างกัน เนื่องจากมีปัญหาที่ต้องแก้ไขสองจุด
ชื่อต้องมาก่อน /etc/hosts เป็นการตั้งค่าราย container ดังนั้นรายการ extra_hosts จึงต้องอยู่ใน container ของแอปพลิเคชัน ไม่ใช่ใน gluetun
prowlarr:
network_mode: "service:gluetun"
extra_hosts:
- "host.docker.internal:host-gateway"host-gateway เป็นค่าพิเศษที่ Docker จะแทนที่ด้วยที่อยู่ภายในของตัว host เอง บนการติดตั้ง Docker แบบปกติบน Linux ค่านี้คือที่อยู่ของ bridge docker0 ซึ่งโดยทั่วไปคือ 172.17.0.1 ให้ตรวจสอบค่าของคุณด้วยคำสั่ง ip -4 addr show docker0 บน VPS สำหรับ Docker Desktop นั้นจะแก้ไขชื่อนี้ได้ด้วยตัวเอง ซึ่งเป็นเหตุผลว่าทำไมคู่มือที่เขียนบนแล็ปท็อปจึงข้ามบรรทัด extra_hosts ไป และทำให้ไฟล์เดียวกันใช้งานไม่ได้บนเซิร์ฟเวอร์
เส้นทาง (route) ต้องมาเป็นลำดับที่สอง การเพิ่มชื่อเพียงอย่างเดียวเป็นการบอก container ว่าให้ใช้ที่อยู่ใด แต่แพ็กเก็ตยังคงออกผ่านเส้นทางเริ่มต้นของ gluetun ซึ่งก็คือ tunnel และ firewall ของ gluetun จะทิ้งแพ็กเก็ตนั้นไป อาการที่พบคือการเชื่อมต่อค้างแล้วหมดเวลา (timeout) แทนที่จะถูกปฏิเสธ (refused) การถูกปฏิเสธหมายความว่าแพ็กเก็ตไปถึงแล้วและมีบางอย่างตอบกลับมาว่าไม่ได้ แต่การหมดเวลาหมายความว่าแพ็กเก็ตไปไม่ถึง
gluetun:
environment:
- FIREWALL_OUTBOUND_SUBNETS=172.17.0.1/32จากนั้นให้ตรวจสอบว่าบริการบน host กำลังฟัง (listen) อยู่ที่ที่อยู่นั้นจริงๆ หรือไม่ PostgreSQL server ที่ผูก (bind) ไว้กับ 127.0.0.1 เท่านั้น จะไม่สามารถเข้าถึงได้จาก container ใดๆ ไม่ว่าจะผ่าน tunnel หรือไม่ก็ตาม เพราะ 127.0.0.1 ภายใน namespace คือ loopback ของตัว namespace เอง ให้ผูกไว้กับ 172.17.0.1 แทน ซึ่งจะยอมรับการเชื่อมต่อจาก container โดยที่ยังคงไม่เปิดเผยบน public interface ให้ยืนยันด้วยคำสั่ง ss -lntp | grep 5432 บน host
สิ่งที่ FIREWALL_OUTBOUND_SUBNETS เปลี่ยนแปลงจริง
เอกสารประกอบของ gluetun อธิบายว่าค่านี้คือรายการ subnet ที่คั่นด้วยเครื่องหมายจุลภาค ซึ่งอนุญาตให้ gluetun และคอนเทนเนอร์ที่ใช้ network stack ร่วมกันสามารถเข้าถึงได้ โดยระบุว่าการตั้งค่านี้เกี่ยวข้องกับการเปลี่ยนแปลงทั้งในส่วนของ firewall และ routing ซึ่งทั้งสองส่วนมีความสำคัญเท่ากัน gluetun จะเพิ่มเส้นทาง (route) สำหรับแต่ละ subnet ที่ระบุผ่าน Docker bridge gateway เพื่อให้แพ็กเก็ตสำหรับที่อยู่เหล่านั้นออกทาง eth0 แทนที่จะผ่านอุโมงค์ VPN นอกจากนี้ยังเป็นการเปิด firewall สำหรับ subnet เหล่านั้นด้วย เนื่องจากโดยปกติแล้ว gluetun จะทิ้ง (drop) ทราฟฟิกขาออกทั้งหมดที่ไม่ได้มุ่งหน้าไปยังเซิร์ฟเวอร์ VPN
ให้ระบุค่าโดยไม่มีช่องว่างหลังเครื่องหมายจุลภาค
- FIREWALL_OUTBOUND_SUBNETS=172.17.0.1/32,192.168.1.0/24,100.64.7.9/32มีคุณสมบัติสองประการที่มักถูกมองข้าม ประการแรก การตั้งค่านี้เป็นระดับ namespace ดังนั้นจึงมีผลกับทุกคอนเทนเนอร์ที่อยู่เบื้องหลัง gluetun ไม่ใช่แค่คอนเทนเนอร์ที่คุณตั้งใจไว้เท่านั้น ประการที่สอง การตั้งค่านี้มีผลเฉพาะขาออก (outbound) เท่านั้น โดยจะควบคุมเฉพาะการเชื่อมต่อที่คอนเทนเนอร์เป็นผู้เริ่ม ส่วนการเชื่อมต่อที่เข้ามายังพอร์ตที่เปิดไว้ (published port) จะใช้เส้นทางที่แตกต่างออกไปและไม่จำเป็นต้องระบุในส่วนนี้
การเข้าถึงเว็บ UI จาก Tailscale peer
Tailscale กำหนด address ให้แต่ละเครื่องใน 100.64.0.0/10 ซึ่งเป็นช่วง address ที่สงวนไว้สำหรับ carrier grade NAT การใช้งานทั้งหมดนี้ไม่มีค่าใช้จ่ายบน tailnet ส่วนบุคคล แต่ ข้อจำกัดด้านผู้ใช้และอุปกรณ์ของ free plan จะเป็นตัวกำหนดว่ายังคงไม่มีค่าใช้จ่ายหรือไม่ เมื่อมีบุคคลอื่นต้องเข้าถึง UI เดียวกัน เนื่องจากการคิดค่าบริการนับตามผู้ใช้ ไม่ใช่จำนวนเครื่อง ค่าใช้จ่ายจริงของ paid tailnet จึงขึ้นอยู่กับจำนวนบุคคลที่คุณเชิญ ไม่ใช่จำนวน container ที่คุณเปิดให้เข้าถึง การเชื่อมต่อทั้งสองทิศทางต้องดำเนินการต่างกัน.
การเชื่อมต่อขาเข้า (Inbound) เป็นส่วนที่ง่ายที่สุด การเผยแพร่ 8080:8080 บน gluetun จะทำการ bind พอร์ตนั้นบนทุกที่อยู่ของโฮสต์ และอินเทอร์เฟซ tailscale0 ของโฮสต์ก็เป็นหนึ่งในนั้น ดังนั้น peer จึงสามารถเปิด http://<machine-name>:8080 เพื่อเข้าถึงคอนเทนเนอร์ได้ Gluetun ไม่ได้มีส่วนเกี่ยวข้องในเส้นทางนี้ เนื่องจากกฎ NAT ของ Docker อยู่ที่ตัวโฮสต์ ซึ่งอยู่นอก namespace
หากต้องการให้เข้าถึง UI ได้เฉพาะผ่านทาง tailnet ให้ bind พอร์ตที่เผยแพร่ไว้เข้ากับที่อยู่ Tailscale ของโฮสต์แทนที่จะ bind กับทุกที่อยู่
ports:
- "100.101.102.103:8080:8080/tcp"ค้นหาที่อยู่นั้นด้วยคำสั่ง tailscale ip -4 บนโฮสต์ การ bind เป็นการควบคุมที่เข้มงวดกว่ากฎ firewall ในกรณีนี้ เนื่องจากพอร์ตจะไม่ถูกเปิดบนอินเทอร์เฟซสาธารณะเลย หากคุณต้องการเข้าถึง UI ผ่านชื่อ HTTPS แทนการใช้โฮสต์และพอร์ต tailscale serve สามารถทำหน้าที่เป็นหน้าด่านให้กับพอร์ตที่เผยแพร่นั้นได้ แม้ว่า serve และ funnel จะมีความแตกต่างกันในแง่ของผู้ที่สามารถเข้าถึงได้ และมีเพียงวิธีเดียวเท่านั้นที่รักษาให้ UI อยู่ภายใน tailnet นอกจากนี้ยังช่วยเลี่ยงปัญหาใน การที่ Docker เผยแพร่พอร์ตโดยข้าม ufw ไปโดยตรง
การเชื่อมต่อขาออก (Outbound) คือจุดที่ FIREWALL_OUTBOUND_SUBNETS กลับมามีบทบาท หากคอนเทนเนอร์จำเป็นต้องเรียกใช้งาน peer ให้เพิ่มที่อยู่ของ peer นั้น และควรเลือกใช้ /32 แยกตามราย peer แทนการใช้ /10 ทั้งหมด หากเครื่องที่คุณกำลังเรียกใช้งานอยู่ในเครือข่ายส่วนตัวที่เข้าถึงได้ผ่าน VPS ที่ประกาศ subnet นั้นไปยัง tailnet ของคุณ ให้ระบุช่วงที่ประกาศไว้แทนที่อยู่ 100.x ของตัว router เอง และตรวจสอบว่าโฮสต์ได้ยอมรับเส้นทางเหล่านั้นแล้ว ชื่อ MagicDNS จะไม่สามารถ resolve ภายในคอนเทนเนอร์ได้ เนื่องจากคอนเทนเนอร์ไม่ได้ใช้ตัว resolver ของโฮสต์ ดังนั้นให้ใช้ที่อยู่ตัวเลข 100.x หรือกำหนดค่าตายตัวด้วยบรรทัด extra_hosts หลักการเดียวกันนี้ยังใช้กับการรัน Tailscale control server ของคุณเองด้วย Headscale
ไฟล์ compose ที่สมบูรณ์สำหรับรูปแบบทั่วไป
ไคลเอนต์ดาวน์โหลดที่อยู่หลัง VPN, เว็บ UI สองรายการที่ตอบสนองเฉพาะบน tailnet และคอนเทนเนอร์หนึ่งรายการที่อ่านฐานข้อมูล PostgreSQL ที่รันอยู่บนโฮสต์
services:
gluetun:
image: qmcgaw/gluetun:latest
container_name: gluetun
cap_add:
- NET_ADMIN
devices:
- /dev/net/tun:/dev/net/tun
ports:
- "127.0.0.1:8000:8000/tcp" # gluetun control server, host only
- "100.101.102.103:8080:8080/tcp" # qBittorrent UI, tailnet only
- "100.101.102.103:9696:9696/tcp" # Prowlarr UI, tailnet only
volumes:
- ./gluetun:/gluetun
environment:
- VPN_SERVICE_PROVIDER=mullvad
- VPN_TYPE=wireguard
- WIREGUARD_PRIVATE_KEY=${WIREGUARD_PRIVATE_KEY}
- WIREGUARD_ADDRESSES=${WIREGUARD_ADDRESSES}
- SERVER_CITIES=Amsterdam
- FIREWALL_OUTBOUND_SUBNETS=172.17.0.1/32,100.64.7.9/32
- TZ=Europe/Amsterdam
restart: unless-stopped
qbittorrent:
image: lscr.io/linuxserver/qbittorrent:latest
network_mode: "service:gluetun"
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/Amsterdam
- WEBUI_PORT=8080
volumes:
- ./qbittorrent:/config
- /srv/downloads:/downloads
depends_on:
gluetun:
condition: service_healthy
restart: unless-stopped
prowlarr:
image: lscr.io/linuxserver/prowlarr:latest
network_mode: "service:gluetun"
extra_hosts:
- "host.docker.internal:host-gateway"
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/Amsterdam
- PROWLARR__POSTGRES__HOST=host.docker.internal
- PROWLARR__POSTGRES__PORT=5432
- PROWLARR__POSTGRES__USER=prowlarr
- PROWLARR__POSTGRES__PASSWORD=${PROWLARR_DB_PASSWORD}
- PROWLARR__POSTGRES__MAINDB=prowlarr-main
- PROWLARR__POSTGRES__LOGDB=prowlarr-log
volumes:
- ./prowlarr:/config
depends_on:
gluetun:
condition: service_healthy
restart: unless-stoppedให้ศึกษาไฟล์นี้เพื่อดูรูปแบบการตั้งค่ามากกว่าชื่อผลิตภัณฑ์ เว็บ UI ทั้งสองรายการถูกเผยแพร่ผ่าน gluetun และผูกไว้กับที่อยู่ tailnet ของโฮสต์ ดังนั้นจึงตอบสนองผ่าน Tailscale เท่านั้นและไม่ตอบสนองจากที่อื่น มีเพียง Prowlarr เท่านั้นที่มีบรรทัด extra_hosts เนื่องจาก Prowlarr เป็นคอนเทนเนอร์ที่ทำหน้าที่แก้ไข host.docker.internal ส่วน FIREWALL_OUTBOUND_SUBNETS ระบุที่อยู่เดี่ยวสองรายการ ได้แก่ ที่อยู่ Docker bridge ของโฮสต์เพื่อให้ Prowlarr สามารถเปิดการเชื่อมต่อฐานข้อมูลได้ และที่อยู่ของ peer ใน tailnet อีกหนึ่งรายการ
เซิร์ฟเวอร์ PostgreSQL ถูกละไว้จากไฟล์นี้โดยเจตนา โดยรันอยู่บน VPS ในฐานะบริการระบบทั่วไปที่ฟังบน 172.17.0.1:5432 ซึ่งเป็นการจัดเลเยอร์แบบเดียวกับ ชุดแอป arr บน Docker Compose โดยย้ายฐานข้อมูลออกมาไว้นอก Docker
เก็บ WireGuard private key ไว้นอกไฟล์ compose โดยให้ ${WIREGUARD_PRIVATE_KEY} อ่านจากไฟล์ .env ที่วางอยู่ข้างกัน ซึ่งเป็นรูปแบบที่อธิบายไว้ใน ไฟล์ env และ secret สำหรับ Docker Compose ส่วนคำสั่ง condition: service_healthy ใช้ healthcheck ที่มาพร้อมกับอิมเมจ gluetun เพื่อให้มั่นใจว่าไม่มีบริการใดเริ่มทำงานจนกว่า tunnel จะรายงานสถานะว่าพร้อมใช้งาน Compose healthchecks อธิบายรูปแบบการใช้งานทั่วไปไว้
การเผยแพร่บนทุกที่อยู่แทนที่จะเป็นเฉพาะ tailnet
ให้ลบ prefix ของที่อยู่และการผูกพอร์ตบน 0.0.0.0 ออก ซึ่งรวมถึง IP สาธารณะของ VPS ด้วย ให้ดำเนินการนี้เฉพาะเมื่ออยู่หลังไฟร์วอลล์ที่คุณควบคุมได้เท่านั้น และโปรดอ่านหมายเหตุเกี่ยวกับ ufw ด้านบนก่อน
ports:
- "8080:8080/tcp"ยืนยันว่า tunnel ยังคงรับส่งข้อมูลอยู่
ให้รันคำขอเดิมสองครั้ง ครั้งแรกจากภายใน namespace และครั้งที่สองจาก host จากนั้นนำมาเปรียบเทียบกัน
docker run --rm --network=container:gluetun curlimages/curl:latest -s https://api.ipify.org
curl -s https://api.ipify.orgคำสั่งแรกควรแสดง exit address ของผู้ให้บริการ VPN ของคุณ ส่วนคำสั่งที่สองควรแสดง address ของ VPS หากทั้งสองค่าตรงกัน แสดงว่าทราฟฟิกของ container ไม่ได้วิ่งผ่าน tunnel และการแก้ไขใดๆ ในคู่มือนี้จะไม่มีผลจนกว่าจะแก้ไขปัญหานี้ได้
ตาราง routing จะแสดงให้เห็นว่าข้อมูลใดที่วิ่งผ่าน tunnel และข้อมูลใดที่ไม่ผ่าน
docker run --rm --network=container:gluetun alpine:3.22 ip route showdefault route ควรชี้ไปยัง interface ของ tunnel คือ tun0 ด้านล่างนั้น คุณควรเห็น route หนึ่งรายการต่อหนึ่ง entry ใน FIREWALL_OUTBOUND_SUBNETS ซึ่งชี้ไปยัง gateway ของ Docker bridge route อื่นใดที่ออกผ่าน eth0 คือทราฟฟิกที่ข้าม VPN ไป
control server ของ Gluetun จะรายงาน public IP เดียวกันบนพอร์ต 8000 ที่ /v1/publicip/ip เวอร์ชันล่าสุดกำหนดให้คุณต้องตั้งค่าการยืนยันตัวตนสำหรับ route ของ control server ดังนั้นควรตั้งค่าส่วนนี้ให้เรียบร้อยก่อนที่จะใช้งาน
ผลกระทบจากการกำหนด subnet ที่ผิดพลาด
FIREWALL_OUTBOUND_SUBNETS คือช่องโหว่ที่คุณเจาะผ่านไฟร์วอลล์ด้วยความตั้งใจ ดังนั้นขนาดของช่องโหว่จึงเท่ากับระดับความเสี่ยง มี 4 วิธีที่ทำให้ช่องโหว่นี้ใหญ่เกินความจำเป็น:
0.0.0.0/0ส่งทุกอย่างออกไปนอกอุโมงค์ การตรวจสอบ IP สองรายการข้างต้นจะตรวจพบปัญหานี้ในการรันครั้งแรก เพราะจะส่งคืนที่อยู่เดียวกัน- ช่วงที่กว้างกว่าเป้าหมาย การเปิด
10.0.0.0/8เพื่อเข้าถึงเครื่องเดียวที่10.0.1.7จะเป็นการเปิดที่อยู่ทุกหมายเลขที่ peer ของ torrent อาจประกาศไว้ในช่วงนั้น ให้ระบุเป็น10.0.1.7/32แทน - ช่วงที่ทับซ้อนกับที่อยู่ของอุโมงค์เอง เอกสารของ gluetun เตือนว่าการทำเช่นนี้จะทำให้ gluetun ส่งทราฟฟิก VPN ออกไปทาง bridge แทน ซึ่งจะทำให้การทำ port forwarding ใช้งานไม่ได้ ตรวจสอบค่า
WIREGUARD_ADDRESSESของคุณก่อนเปิดช่วงเครือข่ายส่วนตัวใดๆ 100.64.0.0/10สำหรับ Tailscale ซึ่งเป็นการเปิดที่อยู่ประมาณสี่ล้านหมายเลขเพื่อให้เข้าถึง peer ได้เพียงเครื่องเดียว ให้ระบุรายชื่อ peer ที่คุณต้องการเป็นรายการ/32แทน
โปรดจำไว้ว่าการตั้งค่านี้ครอบคลุมทั้ง namespace การเปิด subnet เพื่อให้ indexer เข้าถึง host service ได้ จะเป็นการเปิด subnet เดียวกันนั้นให้กับ torrent client ที่แชร์ namespace เดียวกันด้วย ให้รันการตรวจสอบ public IP ซ้ำทุกครั้งหลังเปลี่ยนค่าตัวแปรนี้ เพราะเป็นวิธีทดสอบเดียวที่จะแสดงให้เห็นว่าการเปลี่ยนแปลงนั้นส่งผลตามที่คุณต้องการหรือไม่
สิ่งที่เกิดขึ้นเมื่อรีสตาร์ท gluetun
gluetun เป็นผู้ควบคุม namespace ดังนั้นวงจรชีวิตของ gluetun จึงเป็นวงจรชีวิตของ namespace นั้น การเริ่มคอนเทนเนอร์ที่ขึ้นต่อกันในขณะที่ gluetun หยุดทำงานจะล้มเหลวทันที:
Error response from daemon: cannot join network of a non running containerการรีสตาร์ท gluetun โดยตรงเป็นความล้มเหลวที่ตรวจพบได้ยากกว่า คอนเทนเนอร์ที่ขึ้นต่อกันจะยังคงทำงานอยู่ ในขณะที่ namespace ที่พวกมันเชื่อมต่ออยู่ถูกสร้างขึ้นใหม่ภายใต้ตัวมันเอง ส่งผลให้ docker ps รายงานว่าทุกอย่างปกติ แต่ไม่มีบริการใดตอบสนอง หลังจากมีการเปลี่ยนแปลงใดๆ กับบริการ gluetun ให้สร้างกลุ่มบริการทั้งหมดขึ้นใหม่แทนการรีสตาร์ทเพียงส่วนใดส่วนหนึ่ง
docker compose up -d --force-recreateหลักการเดียวกันนี้ใช้กับการอัปเดต image การดึง image ของ gluetun เวอร์ชันใหม่มาและสร้างใหม่เฉพาะบริการนั้น จะทำให้บริการอื่นชี้ไปยัง namespace ที่ไม่มีอยู่จริงอีกต่อไป
FAQ
ทำไม Docker ถึงแจ้งเตือนเรื่อง "port publishing and the container type network mode"?
เพราะมีการระบุบล็อก ports: ไว้ในบริการที่ตั้งค่า network_mode: service:gluetun เอาไว้ด้วย การเผยแพร่พอร์ต (port publishing) จะเพิ่มกฎ NAT เพื่อส่งต่อพอร์ตจากโฮสต์เข้าไปยัง network namespace ของคอนเทนเนอร์ แต่คอนเทนเนอร์ที่อยู่ในโหมดนี้ไม่มี namespace เป็นของตนเอง ให้ลบบล็อก ports: ออกจากบริการนั้น แล้วย้ายการแมปพอร์ตที่เหมือนกันไปไว้ที่บริการ gluetun แทน โดยใช้หมายเลขพอร์ตเดิมได้เลย เนื่องจากแอปพลิเคชันยังคงฟังพอร์ตนั้นอยู่ภายใน namespace ที่ใช้ร่วมกัน
คอนเทนเนอร์อื่นจะเข้าถึงบริการที่อยู่หลัง gluetun ได้อย่างไร?
คอนเทนเนอร์ที่อยู่ใน namespace เดียวกันสามารถเข้าถึงกันได้ผ่าน 127.0.0.1 ส่วนคอนเทนเนอร์ที่อยู่ภายนอกให้ใช้ชื่อบริการ gluetun ดังนั้น http://gluetun:8080 จึงใช้งานได้ในขณะที่ http://qbittorrent:8080 ใช้งานไม่ได้ เนื่องจากคอนเทนเนอร์ของแอปพลิเคชันไม่มีที่อยู่บนเครือข่าย Docker ใดๆ เซิร์ฟเวอร์ DNS ภายในจึงไม่มีข้อมูลให้ resolve สำหรับชื่อของมัน ไม่จำเป็นต้องเผยแพร่พอร์ตหากคอนเทนเนอร์ทั้งสองอยู่ใน Compose network เดียวกัน
ควรใส่ค่าอะไรใน FIREWALL_OUTBOUND_SUBNETS?
ให้ใส่เฉพาะที่อยู่ที่คอนเทนเนอร์หลัง gluetun จำเป็นต้องใช้เพื่อเริ่มต้นการเชื่อมต่อ โดยระบุให้แคบที่สุดเท่าที่จะทำได้ เครื่องเดียวให้ระบุเป็น /32 รายการที่พบบ่อยคือ Docker host ที่ 172.17.0.1/32 และ /32 หนึ่งรายการสำหรับ Tailscale peer แต่ละตัวที่คุณเรียกใช้งาน ห้ามใส่ 0.0.0.0/0 และห้ามใส่ช่วง IP ที่ทับซ้อนกับที่อยู่ tunnel ของ VPN ของคุณเอง การเชื่อมต่อขาเข้า (inbound) ไปยังพอร์ตที่เผยแพร่อยู่ไม่จำเป็นต้องระบุในส่วนนี้
ทำไมคอนเทนเนอร์ถึงไม่สามารถ resolve ชื่อ Tailscale MagicDNS ได้?
MagicDNS ทำงานโดยชี้ตัวแก้ไข (resolver) ของโฮสต์ไปที่เซิร์ฟเวอร์ DNS ของ Tailscale แต่คอนเทนเนอร์ไม่ได้ใช้ตัวแก้ไขของโฮสต์ มันจะใช้ค่าที่ระบุใน /etc/resolv.conf ของตัวเอง ซึ่งเมื่ออยู่หลัง gluetun จะเป็นการตั้งค่า DNS ของ gluetun ให้ตรวจสอบด้วย docker exec <container> cat /etc/resolv.conf โดยให้ใช้ที่อยู่ 100.x แบบตัวเลขของ peer นั้น หรือกำหนดชื่อด้วยรายการ extra_hosts ในคอนเทนเนอร์นั้นแทน
จะยืนยันได้อย่างไรว่าทราฟฟิกยังคงผ่าน VPN อยู่?
ให้รันคำขอหนึ่งรายการจากภายใน namespace และรันคำขอเดียวกันจากโฮสต์ จากนั้นเปรียบเทียบผลลัพธ์ docker run --rm --network=container:gluetun curlimages/curl:latest -s https://api.ipify.org ควรแสดงที่อยู่ขาออกของผู้ให้บริการ VPN ของคุณ ในขณะที่ curl -s https://api.ipify.org บน VPS จะแสดงที่อยู่ของ VPS หากผลลัพธ์ทั้งสองเหมือนกัน แสดงว่า tunnel ไม่ได้นำส่งทราฟฟิกของคอนเทนเนอร์ ให้ตรวจสอบซ้ำอีกครั้งหลังจากแก้ไข FIREWALL_OUTBOUND_SUBNETS ทุกครั้ง