วิธีตั้งค่า Docker container ให้เชื่อมต่อผ่าน VPN
แก้ไขปัญหาพอร์ตหายเมื่อใช้ Gluetun ร่วมกับ Docker container ผ่าน network namespace เรียนรู้วิธีประกาศพอร์ตที่ถูกต้องและตัวอย่างไฟล์ Compose ที่ใช้งานได้จริงสำหรับ VPN
เหตุใดพอร์ตจึงหายไปเมื่อกำหนดเส้นทาง Docker container ผ่าน VPN
ในการกำหนดเส้นทาง Docker container ผ่าน VPN คุณต้องให้ container หนึ่งตัวเป็นผู้รับผิดชอบอุโมงค์เชื่อมต่อ (tunnel) จากนั้นจึงเชื่อมต่อ container อื่นๆ เข้ากับ network namespace ของ container นั้นด้วย network_mode: "service:gluetun" การเชื่อมต่อในลักษณะนี้เป็นจุดที่ทำให้ผู้ใช้งานหลายคนประหลาดใจ เนื่องจาก container ที่ถูกเชื่อมต่อจะไม่เหลือเครือข่ายเป็นของตัวเองอีกต่อไป พอร์ตที่ประกาศไว้ (published ports) และชื่อบริการ Docker ของ container นั้นจึงหายไปพร้อมกัน ให้คุณประกาศพอร์ตที่ตัว container ของ VPN แทน แล้ว container อื่นๆ จะสามารถเข้าถึงแอปพลิเคชันได้ผ่านชื่อของ container VPN นั้น
หากคุณทิ้งบล็อก ports: ไว้ใน container ที่ถูกเชื่อมต่อ Docker จะปฏิเสธการสร้าง container นั้นทันที:
Error response from daemon: conflicting options: port publishing and the container type network modeเครื่องมือที่ใช้ในที่นี้คือ Gluetun ซึ่งเป็น container ที่เชื่อมต่อกับผู้ให้บริการ VPN (virtual private network) เชิงพาณิชย์ผ่าน WireGuard หรือ OpenVPN และมี firewall ในตัว Release v3.41.3 เป็นเวอร์ชันปัจจุบัน ณ เดือนสิงหาคม 2026 ตัวอย่างเหล่านี้ใช้ Mullvad ร่วมกับ WireGuard ดังนั้นคุณจำเป็นต้องมีบัญชีและคีย์จากผู้ให้บริการของคุณ หากคุณต้องการจัดการการเชื่อมต่ออุโมงค์บนฮาร์ดแวร์ของคุณเอง การรัน WireGuard server ของคุณเองบน VPS จะช่วยสร้างปลายทางอีกด้านหนึ่ง และ wg-easy ใน Docker จะช่วยครอบการทำงานนั้นด้วยอินเทอร์เฟซบนเว็บ
การทำงานจริงของ network_mode: "service:gluetun"
โดยปกติ Docker container แต่ละตัวจะมี network namespace เป็นของตนเอง ทั้งอินเทอร์เฟซ ตาราง routing กฎ firewall และ socket ที่เปิดรอรับการเชื่อมต่อ โหมด service: จะข้ามขั้นตอนดังกล่าวและเริ่ม container ภายใน namespace ของ gluetun แทน เมื่อใช้ namespace เดียวกันจึงเท่ากับใช้ IP address เดียวกัน ซึ่งส่งผลต่อสิ่งต่างๆ 6 ประการดังนี้:
- แอปพลิเคชันจะไม่มีที่อยู่เป็นของตนเอง แต่จะใช้ที่อยู่ของ gluetun แทน
- แอปพลิเคชันไม่ได้เชื่อมต่อกับ Docker network ใดๆ ทำให้ชื่อบริการไม่ถูกลงทะเบียนและไม่สามารถ resolve ได้ container อื่นๆ จึงต้องใช้
gluetunในการติดต่อ - container ที่อยู่ภายใน namespace เดียวกันสามารถติดต่อกันผ่าน
localhostได้ - container สองตัวใน namespace เดียวกันไม่สามารถเปิดรอรับการเชื่อมต่อ (listen) บนพอร์ตเดียวกันได้ เอกสารของ gluetun ระบุเรื่องนี้ไว้อย่างชัดเจนว่าไม่มีวิธีแก้ไข
- สิทธิ์ (capabilities) เป็นของ container ไม่ใช่ของ namespace โดย gluetun จะถือสิทธิ์
NET_ADMINและ/dev/net/tunไว้เนื่องจากเป็นผู้สร้าง tunnel interface ส่วน container ที่แนบเข้ามาจะไม่ได้รับสิทธิ์เหล่านี้ - Docker Compose จะปฏิเสธไฟล์ใดก็ตามที่มีการตั้งค่าทั้ง
network_modeและnetworksในบริการเดียวกัน ให้คุณแนบ gluetun เข้ากับ network ของคุณแทน แล้วแอปพลิเคชันจะเชื่อมต่อตามไปด้วย
การรีสตาร์ท gluetun จะทำให้ทุกสิ่งที่แนบอยู่กับมันหลุดจากการเชื่อมต่อ นี่เป็นพฤติกรรมที่ระบุไว้ตามเอกสาร และเป็นเหตุผลว่าทำไม gluetun ถึงเลือกที่จะรีสตาร์ทกระบวนการ VPN ภายใน container แทนที่จะปิดตัวลงเมื่อการเชื่อมต่อล้มเหลว หลังจากที่คุณรีสตาร์ทหรือสร้าง gluetun ขึ้นมาใหม่ด้วยตัวเองแล้ว ให้ทำการรีสตาร์ท container ที่แนบอยู่กับมันด้วย
ไฟล์ compose ที่ใช้งานได้
services:
gluetun:
image: qmcgaw/gluetun:v3
container_name: gluetun
cap_add:
- NET_ADMIN
devices:
- /dev/net/tun:/dev/net/tun
environment:
- VPN_SERVICE_PROVIDER=mullvad
- VPN_TYPE=wireguard
- SERVER_CITIES=Amsterdam
- TZ=Europe/Amsterdam
env_file:
- ./gluetun.env
volumes:
- ./gluetun:/gluetun
ports:
- 127.0.0.1:8080:8080/tcp
restart: unless-stopped
qbittorrent:
image: lscr.io/linuxserver/qbittorrent:latest
container_name: qbittorrent
network_mode: "service:gluetun"
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/Amsterdam
- WEBUI_PORT=8080
volumes:
- ./qbittorrent:/config
- ./downloads:/downloads
depends_on:
gluetun:
condition: service_healthy
restart: unless-stoppedแท็ก :v3 คือรุ่นเสถียรล่าสุดในซีรีส์ v3 ส่วนแท็ก :latest จะชี้ไปยัง commit ล่าสุดของ branch master ซึ่งเป็นรุ่นสำหรับการพัฒนา ดังนั้นให้ระบุเวอร์ชัน :v3 บนเครื่องที่คุณไม่ต้องการเสียเวลาแก้ไขปัญหาในวันอังคาร
WEBUI_PORT=8080 จะต้องตรงกับพอร์ตที่เปิดใช้งาน (published port) เนื่องจาก qBittorrent ทำการ bind อยู่ภายใน namespace ของ gluetun และกฎการ publish จะส่ง traffic จากโฮสต์ไปยังพอร์ต 8080 ที่นั่น หากคุณเปลี่ยนตัวเลขตัวหนึ่งโดยไม่เปลี่ยนอีกตัว พอร์ตนั้นจะไม่ตอบสนองใดๆ 127.0.0.1:8080:8080 จะคงหน้าเว็บอินเทอร์เฟซไว้ที่ loopback address ของโฮสต์ การใช้ 8080:8080 เปล่าๆ จะเป็นการเปิดใช้งานบนทุกอินเทอร์เฟซและเขียนกฎ firewall ของตัวเอง ซึ่งเป็นสาเหตุที่ทำให้ พอร์ตที่เปิดผ่าน Docker หลุดรอดการตรวจสอบของ ufw
เริ่มการทำงานของบริการ จากนั้นตรวจสอบตามลำดับดังนี้:
docker compose up -d
docker compose ps
docker compose logs gluetun | tail -30docker compose ps ควรแสดงสถานะ gluetun เป็น healthy และ qbittorrent เป็น running จากนั้นยืนยันที่อยู่ IP ขาออก (exit address) จากภายใน namespace ซึ่งเป็นการตรวจสอบที่ชี้ขาดทุกอย่าง:
docker run --rm --network=container:gluetun alpine:3.22 sh -c "apk add wget && wget -qO- https://ipinfo.io"ฟิลด์ ip ใน JSON นั้นควรเป็นที่อยู่ IP ของผู้ให้บริการ VPN ของคุณ หากเป็นที่อยู่ IP ของเซิร์ฟเวอร์คุณเอง แสดงว่าแอปไม่ได้อยู่ใน tunnel และทุกอย่างหลังจากนี้จะไม่ทำงานตามที่อธิบายไว้
เก็บคีย์ไว้ภายนอกไฟล์ compose
gluetun.env เป็นที่เก็บข้อมูลรับรองและต้องไม่ถูกรวมอยู่ใน git:
WIREGUARD_PRIVATE_KEY=wOEI9rqqbDwnN8/Bpp22sVz48T71vJ4fYmFWujulwUU=
WIREGUARD_ADDRESSES=10.64.222.21/32ค่าทั้งสองได้มาจากไฟล์การตั้งค่า WireGuard ที่คุณสร้างขึ้นในพื้นที่บัญชีของผู้ให้บริการ ให้ตั้งค่าไฟล์เป็นโหมด 600 จงตระหนักถึงสิ่งที่วิธีนี้ช่วยคุณได้จริง: คีย์จะไม่ถูกเก็บไว้ใน repository ของคุณ แต่ docker inspect gluetun ยังคงแสดง environment variable ทุกตัวให้ทุกคนที่เข้าถึง Docker socket ได้เห็น ไฟล์ environment และ secret ใน Docker Compose ครอบคลุมถึงทางเลือกที่มีความปลอดภัยสูงกว่า
วิธีที่คอนเทนเนอร์ภายนอกอุโมงค์สื่อสารกับคอนเทนเนอร์ภายใน
การสื่อสารทำได้ทั้งสองทิศทางโดยใช้ชื่อที่แตกต่างกัน คอนเทนเนอร์ทั้งสองต้องใช้ Docker network ร่วมกัน ซึ่งในกรณีนี้คือเครือข่ายของ gluetun เนื่องจากคอนเทนเนอร์ที่แนบมาไม่มีเครือข่ายเป็นของตนเอง เนื้อหาใน วิธีการเชื่อมต่อเครือข่ายของ Docker Compose ได้อธิบายค่าเริ่มต้นไว้แล้ว
สำหรับการสื่อสารจากภายนอกเข้าไปภายใน ให้ใช้ชื่อของ gluetun และพอร์ตที่แอปพลิเคชันนั้นเปิดใช้งานอยู่ ตัวอย่างเช่น คอนเทนเนอร์ reverse proxy สามารถเข้าถึงหน้าเว็บอินเทอร์เฟซของ qBittorrent ได้ที่ gluetun:8080 โดยไม่จำเป็นต้องมีการตั้งค่า ports: เพิ่มเติม เนื่องจากทราฟฟิกแบบคอนเทนเนอร์ต่อคอนเทนเนอร์จะอยู่ภายใน Docker network และไม่ผ่านพอร์ตของโฮสต์
สำหรับการสื่อสารจากภายในออกสู่ภายนอก ให้ใช้ชื่อบริการของคอนเทนเนอร์อีกตัวหนึ่ง เช่น postgres:5432 เป็นต้น gluetun สามารถแก้ไขชื่อคอนเทนเนอร์อื่นจากภายใน namespace ของตนเองได้ตั้งแต่เวอร์ชัน v3.41 เป็นต้นไป ดังนั้นหากชื่อไม่สามารถแก้ไขได้ ให้ระบุเวอร์ชันดังกล่าวหรือใหม่กว่า
ไฟร์วอลล์ของ gluetun จะเป็นตัวกำหนดว่าใครสามารถเปิดการเชื่อมต่อเข้ามาได้ ทราฟฟิกจาก Docker network ของ gluetun เองจะได้รับอนุญาต แต่ไคลเอนต์ที่อยู่บน subnet อื่น เช่น แล็ปท็อปบน LAN ของคุณ หรือคอนเทนเนอร์บน bridge network แยกต่างหาก จะถูกปฏิเสธการเชื่อมต่อจนกว่าคุณจะระบุชื่อ subnet นั้น:
FIREWALL_OUTBOUND_SUBNETS=192.168.1.0/24ความหมายที่ระบุไว้ในเอกสารนั้นชัดเจน คือการระบุ subnet โดยคั่นด้วยเครื่องหมายจุลภาค เพื่อให้ gluetun และคอนเทนเนอร์ที่ใช้ network stack ร่วมกันสามารถเข้าถึงได้
การเชื่อมต่อขาเข้าจากอินเทอร์เน็ตเป็นปัญหาที่แยกต่างหาก เนื่องจาก peer ของโปรแกรม torrent จะเข้ามาทางฝั่ง VPN ดังนั้นการเปิดพอร์ต 6881 บนโฮสต์จึงไม่มีผลกับ peer เหล่านั้น คุณจำเป็นต้องใช้พอร์ตที่ส่งต่อ (forwarded port) จากผู้ให้บริการของคุณ และระบุพอร์ตนั้นไว้ใน FIREWALL_VPN_INPUT_PORTS ซึ่งจะอนุญาตให้พอร์ตจากฝั่งเซิร์ฟเวอร์ VPN สามารถใช้งานได้ นี่คือส่วนที่ media stacks ที่สร้างด้วย Docker Compose ส่วนใหญ่มักตั้งค่าไม่ถูกต้อง
Kill switch: สิ่งที่จะเกิดขึ้นเมื่ออุโมงค์เชื่อมต่อหลุด
รูปแบบนี้มีความซับซ้อนที่คุ้มค่าเมื่อเกิดความล้มเหลว คอนเทนเนอร์ที่แนบอยู่ไม่มีเส้นทางที่สอง เส้นทางเดียวที่จะออกไปนอกเครื่องได้คือเนมสเปซที่แชร์ร่วมกัน ดังนั้นเมื่ออุโมงค์เชื่อมต่อ (tunnel) ล่ม จึงไม่มีเส้นทางสำรองให้ใช้งาน ไฟร์วอลล์ของ Gluetun บังคับใช้กฎเดียวกันจากอีกฝั่ง: ทราฟฟิกขาออกต้องผ่านอุโมงค์หรือไปยังปลายทางของเซิร์ฟเวอร์ VPN เท่านั้น ส่วนทราฟฟิกอื่นทั้งหมดจะถูกดรอปทิ้ง ไม่มีช่วงเวลาที่แพ็กเก็ตจะรั่วไหลออกทางอินเทอร์เฟซปกติในขณะที่ไคลเอนต์กำลังเชื่อมต่อใหม่
Gluetun จะเฝ้าดูการเชื่อมต่อของตัวเอง ทุกๆ หนึ่งนาทีมันจะส่ง ICMP echo (ping) ไปยังที่อยู่ตามที่ระบุใน HEALTH_ICMP_TARGET_IPS ซึ่งค่าเริ่มต้นคือ 1.1.1.1,8.8.8.8 ทุกๆ ห้านาทีมันจะทำการ dial แบบเต็มรูปแบบผ่าน TCP และ TLS (transport layer security) ไปยัง HEALTH_TARGET_ADDRESSES ซึ่งค่าเริ่มต้นคือ cloudflare.com:443,github.com:443 เมื่อการตรวจสอบเหล่านี้ล้มเหลว มันจะรีสตาร์ท VPN ภายในคอนเทนเนอร์และบันทึกลงใน log:
WARN [vpn] restarting VPN because it failed to pass the healthcheck: periodic check: dialing: dial tcp4: lookup cloudflare.com: i/o timeoutให้อ่าน log ของคอนเทนเนอร์ที่แนบอยู่โดยคำนึงถึงลำดับนี้ บรรทัดอย่างเช่น connection refused, operation not permitted และ i/o timeout ภายในแอปพลิเคชันเป็นผลลัพธ์ที่เกิดจากอุโมงค์เชื่อมต่อที่ตายไปแล้ว ไม่ใช่สาเหตุ เอกสารของ Gluetun ระบุเรื่องนี้ไว้อย่างชัดเจน เพราะผู้ใช้มักรายงานผลลัพธ์ที่เกิดขึ้นและเสียเวลาไล่ตามหาสาเหตุเป็นชั่วโมง
HEALTH_RESTART_VPN=on เป็นค่าเริ่มต้นและควรเปิดไว้ ให้ปิดเฉพาะในขณะที่คุณกำลังดีบั๊กความล้มเหลวที่เฉพาะเจาะจงเท่านั้น เพราะหากปิดไว้ อุโมงค์เชื่อมต่อที่ตายไปแล้วจะยังคงตายอยู่อย่างนั้นโดยไม่มีการกู้คืน
Ordering: stop the stack starting before the tunnel is up
The image ships a Docker healthcheck:
HEALTHCHECK --interval=5s --timeout=5s --start-period=10s --retries=1 CMD /gluetun-entrypoint healthcheckThat command runs a second, short lived copy of gluetun which queries the health server of the running one at http://127.0.0.1:9999/. A working tunnel answers 200 OK. A broken one answers 500 Internal server error with an error string, and the container is marked unhealthy after a single failure.
condition: service_healthy is what waits for that. Plain depends_on: [gluetun] waits only for the container to start, which happens several seconds before the handshake completes, so the app starts into a dead network and often gives up on its first connection attempt. Healthchecks in Docker Compose goes through the syntax and the timing fields.
One limit catches people out. Compose evaluates that condition once, when it creates the container. It does not stop or restart the app later if gluetun turns unhealthy. Gluetun's internal auto-healing covers that case instead, which is why it restarts the VPN process rather than the container.
ตรวจสอบการรั่วไหลของ DNS ก่อนที่คุณจะเชื่อมั่นในการตั้งค่า
DNS (domain name system) คือช่องโหว่ที่อาจเกิดขึ้นได้แม้จะทำ tunnel อย่างถูกต้องแล้วก็ตาม Gluetun จะรัน resolver ของตัวเองไว้ภายใน namespace และส่งต่อคำขอผ่าน DoT (DNS over TLS) ไปยัง Cloudflare เป็นค่าเริ่มต้น: DNS_UPSTREAM_RESOLVER_TYPE=dot และ DNS_UPSTREAM_RESOLVERS=cloudflare หากคุณปล่อยให้ค่าทั้งสองเป็นไปตามเดิม การค้นหาข้อมูลของคุณจะถูกเข้ารหัสและเดินทางผ่าน tunnel ไป
การตั้งค่าที่ทำให้เกิดปัญหานี้คือ DNS_UPSTREAM_PLAIN_ADDRESSES ผู้ใช้งานมักเลือกใช้เมื่อชื่อโดเมนไม่สามารถ resolve ได้ และต้องการให้ router หรือ resolver ของผู้ให้บริการเป็นผู้ตอบแทน เอกสารของ Gluetun ระบุถึงผลกระทบไว้อย่างชัดเจนว่า: ทราฟฟิก DNS ทั้งหมดจะไม่ผ่าน VPN tunnel และจะรั่วไหลออกไปภายนอก แม้ทราฟฟิกของคุณจะยังคงเป็นส่วนตัว แต่รายการชื่อโฮสต์ที่คุณเข้าถึงจะไม่เป็นเช่นนั้น ข้อผิดพลาดในลักษณะเดียวกันสำหรับ WireGuard ได้รับการอธิบายไว้ใน DNS ที่หยุดทำงานผ่าน WireGuard tunnel
ในการทดสอบ ให้ตั้งค่า HTTPPROXY=on บน gluetun และเปิดใช้งาน 8888:8888/tcp จากนั้นชี้เบราว์เซอร์ไปที่ proxy ดังกล่าวแล้วโหลดหน้าทดสอบ DNS leak ผลลัพธ์ที่ได้ควรระบุชื่อผู้ให้บริการของคุณหรือ Cloudflare เท่านั้น ห้ามปรากฏชื่อ router ที่บ้านของคุณ เอกสารของ Gluetun เองได้เตือนไว้ว่าการทดสอบบางอย่างอาจให้ผลลัพธ์ที่แปลกไป เนื่องจาก resolver ภายใน namespace เป็นเพียงตัวกลางในการแคชข้อมูลในเครื่อง ไม่ใช่เซิร์ฟเวอร์ที่ตอบกลับคำขอจริง หากพบชื่อประเทศที่ไม่ถูกต้องหรือพบ resolver ของ ISP ของคุณเอง ให้ถือว่านั่นคือสัญญาณเตือนที่แท้จริง
การเพิ่ม Tailscale ควบคู่ไปกับ VPN sidecar และการตัดสินว่าตัวใดจะเป็นผู้ควบคุมเส้นทาง
Tailscale คือ overlay network ที่สร้างบน WireGuard สำหรับเข้าถึงเครื่องของคุณเอง โดยผู้ใช้งานมักรันควบคู่ไปกับ VPN ของผู้ให้บริการเพื่อรักษาช่องทางสำหรับผู้ดูแลระบบ (admin path) เข้าสู่ stack ทั้งสองบริการมักไม่ขัดแย้งกันด้วยเหตุผลที่ควรทำความเข้าใจ เอกสารของ Tailscale ระบุค่าเริ่มต้นไว้ว่า: มันทำหน้าที่เป็น overlay network ซึ่งจะกำหนดเส้นทางเฉพาะข้อมูลระหว่างอุปกรณ์ที่รัน Tailscale เท่านั้น และจะไม่ยุ่งเกี่ยวกับข้อมูลอินเทอร์เน็ตสาธารณะของคุณ
ดังนั้นคำตอบจึงขึ้นอยู่กับการตั้งค่าเพียงจุดเดียว
- Tailscale ในคอนเทนเนอร์ของตัวเองด้วยการตั้งค่าเริ่มต้น: มันจะไม่เห็นข้อมูลขาออกของแอปพลิเคชัน โดย Gluetun จะเป็นผู้จัดการข้อมูลทั้งหมด Tailscale จะเข้าถึงแอปที่
gluetun:8080เหมือนกับคอนเทนเนอร์ภายนอกอื่นๆ ทุกประการ - Tailscale ที่เชื่อมต่อกับ namespace ของ gluetun ด้วย
network_mode: "service:gluetun": จำเป็นต้องมีcap_addเป็นของตัวเองในส่วนของnet_adminและnet_rawเนื่องจากสิทธิ์ (capabilities) จะไม่ถูกส่งผ่านมาพร้อมกับ namespace ในโหมดเครือข่ายแบบ userspace เริ่มต้นที่TS_USERSPACEเปิดใช้งานอยู่ tailscaled จะไม่สร้างอินเทอร์เฟซใดๆ ขึ้นมา แต่จะทำงานเป็น SOCKS5 หรือ HTTP proxy จึงไม่สามารถเปลี่ยนเส้นทางการรับส่งข้อมูลได้ Gluetun จึงยังคงเป็นผู้จัดการข้อมูลทั้งหมด - กรณีเดียวกันแต่ใช้
TS_USERSPACE=false: tailscaled จะสร้าง tunnel device และติดตั้งเส้นทาง (routes) เฉพาะสำหรับช่วงเครือข่าย tailnet100.64.0.0/10รวมถึง subnet route ใดๆ ที่คุณประกาศด้วยTS_ROUTESส่วนข้อมูลสาธารณะยังคงออกผ่าน gluetun - กรณีใดก็ตามข้างต้นหากมีการเลือก exit node ด้วย
sudo tailscale set --exit-node=<exit-node-ip>: Tailscale จะเข้ายึด default route และเป็นผู้ควบคุมเส้นทาง ห้ามใช้งานร่วมกับ gluetun เพราะ default route มีได้เพียงหนึ่งเดียวและมีเจ้าของได้เพียงหนึ่งเดียวเท่านั้น
หากเป้าหมายคือการประกาศเส้นทาง (advertised routes) และคุณต้องการเข้าถึงเครือข่ายส่วนตัวทั้งหมดที่อยู่หลังกล่องแทนที่จะเข้าถึงแค่ตัวกล่องเพียงอย่างเดียว การรัน Tailscale subnet router บน VPS จะครอบคลุมถึงการอนุมัติเส้นทาง, การทำ IP forwarding และ flag ฝั่งไคลเอนต์ที่ TS_ROUTES ไม่ได้จัดการให้โดยอัตโนมัติ
หาก Tailscale มีไว้เพื่อให้คุณเข้าถึง admin URL แทนที่จะเป็นเรื่องของเส้นทาง tailscale serve และ tailscale funnel จะช่วยวาง HTTPS ไว้หน้า gluetun:8080 สำหรับ tailnet ของคุณ โดยมีเพียงฟีเจอร์ funnel เท่านั้นที่เปิดให้เข้าถึงผ่านอินเทอร์เน็ตสาธารณะ
ผลข้างเคียงประการหนึ่งจะปรากฏให้เห็นเมื่อ Tailscale รันอยู่ภายใน tunnel โดย peer จะมองเห็นเป็นที่อยู่ IP ของผู้ให้บริการ VPN ดังนั้นจึงมีโอกาสสูงที่จะต้องเปลี่ยนไปใช้ relay บ่อยขึ้น tailscale status จะแสดง relay "..." ข้างชื่อ peer แทนที่จะเป็น direct เมื่อเกิดเหตุการณ์ดังกล่าว การเชื่อมต่อยังคงใช้งานได้แต่จะมีความเร็วลดลง หากสิ่งที่ต้องการจริงๆ คือเพียงแค่ overlay network ความแตกต่างระหว่าง WireGuard แบบปกติกับ Tailscale คือจุดเริ่มต้นที่ดีกว่าในการศึกษา
สิ่งที่มักจะเกิดข้อผิดพลาดและข้อความที่คุณจะพบ
Docker ปฏิเสธที่จะสร้าง container ของแอป Error response from daemon: conflicting options: port publishing and the container type network mode หมายความว่ายังมีบล็อก ports: ค้างอยู่ในบริการที่เชื่อมต่ออยู่ ให้ย้ายบล็อกดังกล่าวไปไว้ที่ gluetun
Compose ปฏิเสธไฟล์ทั้งหมด บริการหนึ่งไม่สามารถตั้งค่าทั้ง network_mode และ networks พร้อมกันได้ ให้ย้ายการตั้งค่า networks ไปไว้ที่ gluetun แทน
container อื่นไม่สามารถ resolve ชื่อแอปได้ curl: (6) Could not resolve host: qbittorrent เป็นพฤติกรรมที่ถูกต้อง เพราะ container ที่เชื่อมต่อไม่ได้เข้าร่วม network ใดและไม่ได้ลงทะเบียนชื่อไว้ ให้ใช้ gluetun และระบุพอร์ตแทน
container ที่สองที่เชื่อมต่ออยู่ไม่ยอมเริ่มทำงาน กระบวนการสองอย่างใน namespace เดียวกันไม่สามารถ bind พอร์ตเดียวกันได้ และกระบวนการที่แพ้จะรายงานว่า address ดังกล่าวถูกใช้งานอยู่ ให้เปลี่ยนพอร์ตภายในของแอป หรือรัน gluetun ตัวที่สอง
แอปไม่มีเครือข่ายหลังจากที่คุณแก้ไข gluetun การรีสตาร์ทหรือสร้าง gluetun ใหม่จะทำให้การเชื่อมต่อของทุกสิ่งที่เชื่อมต่ออยู่หลุดไป ให้รีสตาร์ท container เหล่านั้น
หน้าเว็บขนาดเล็กโหลดได้ แต่หน้าเว็บขนาดใหญ่ค้าง นั่นคือปัญหาเรื่อง MTU (maximum transmission unit) อุโมงค์เชื่อมต่อจะเพิ่ม overhead และมีบางอย่างในเส้นทางที่ทิ้งแพ็กเก็ตขนาดใหญ่เกินไปโดยไม่ส่งข้อความแจ้งเตือนกลับมา ให้ลดค่า WIREGUARD_MTU, ลองใช้ 1400, แล้วตามด้วย 1320
gluetun ไม่ยอมเปลี่ยนสถานะเป็น healthy การตรวจสอบตอนเริ่มต้นจะระบุผู้ต้องสงสัยกลุ่มแรกไว้คือ WARN [vpn] restarting VPN because it failed to pass the healthcheck: startup check: dialing: dial tcp4: lookup cloudflare.com: i/o timeout ให้ตรวจสอบว่า key หมดอายุหรือไม่ จากนั้นตรวจสอบว่ารายการเซิร์ฟเวอร์ล้าสมัยหรือไม่ และสุดท้ายตรวจสอบว่า firewall ของโฮสต์บล็อกการส่งข้อมูล UDP ขาออกหรือไม่
FAQ
ทำไมพอร์ตที่ publish ไว้ของคอนเทนเนอร์ถึงใช้งานไม่ได้เมื่ออยู่หลัง Gluetun?
เพราะ network_mode: "service:gluetun" นำคอนเทนเนอร์เข้าไปอยู่ใน network namespace ของ Gluetun ซึ่ง namespace หนึ่งจะมีเพียงหนึ่ง IP address และหนึ่งชุดของพอร์ตที่เปิดใช้งาน แอปพลิเคชันยังคงทำงานอยู่ แต่กฎการ publish พอร์ตต้องกำหนดไว้ที่คอนเทนเนอร์ที่เป็นเจ้าของ namespace นั้น ให้ย้ายรายการ ports: ไปไว้ที่บริการของ Gluetun หากคุณยังคงทิ้งไว้ที่บริการที่เชื่อมต่ออยู่ Docker จะไม่สร้างพอร์ตเหล่านั้นให้: Error response from daemon: conflicting options: port publishing and the container type network mode
ฉันจะเข้าถึงคอนเทนเนอร์ที่อยู่ภายใน VPN tunnel จากคอนเทนเนอร์ที่อยู่นอก tunnel ได้อย่างไร?
ให้ใช้ชื่อบริการของ Gluetun และพอร์ตที่แอปพลิเคชันนั้นเปิดใช้งาน เช่น gluetun:8080 คอนเทนเนอร์ที่เชื่อมต่ออยู่จะไม่มี Docker network เป็นของตัวเอง ดังนั้นชื่อของมันจึงไม่สามารถ resolve ได้ ไม่จำเป็นต้อง publish พอร์ตใดๆ สำหรับการรับส่งข้อมูลระหว่างคอนเทนเนอร์ ในทางกลับกัน คอนเทนเนอร์ที่อยู่ภายใน namespace สามารถเข้าถึงคอนเทนเนอร์ภายนอกได้ผ่านชื่อบริการ เช่น postgres:5432 บน Gluetun v3.41 ขึ้นไป สำหรับไคลเอนต์ที่อยู่บน subnet อื่น เช่น แล็ปท็อปใน LAN ของคุณ ข้อมูลจะถูก firewall ของ Gluetun ปฏิเสธจนกว่าคุณจะเพิ่ม subnet นั้นลงใน FIREWALL_OUTBOUND_SUBNETS
Gluetun ทำหน้าที่เป็น kill switch เมื่อ VPN หลุดหรือไม่?
ใช่ โดยมีเหตุผลสองประการพร้อมกัน ประการแรก คอนเทนเนอร์ที่เชื่อมต่ออยู่ไม่มีเส้นทางเครือข่ายอื่นนอกจากเส้นทางใน shared namespace ดังนั้นเมื่อ tunnel หยุดทำงาน คอนเทนเนอร์เหล่านั้นจึงไม่มีเส้นทางออกสู่ภายนอก ประการที่สอง firewall ของ Gluetun อนุญาตให้รับส่งข้อมูลขาออกผ่าน tunnel และไปยัง endpoint ของเซิร์ฟเวอร์ VPN เท่านั้น จากนั้น Gluetun จะรีสตาร์ท VPN ภายในตัวมันเองพร้อมบันทึก log WARN [vpn] restarting VPN because it failed to pass the healthcheck แทนที่จะปิดการทำงาน เพราะหาก Gluetun รีสตาร์ท คอนเทนเนอร์ที่เชื่อมต่ออยู่ทั้งหมดจะสูญเสียการเชื่อมต่อเครือข่ายทันที
หากใช้ Tailscale และ Gluetun ใน stack เดียวกัน บริการใดจะเป็นผู้จัดการ traffic ขาออก?
Gluetun จะเป็นผู้จัดการในทุกการตั้งค่า ยกเว้นกรณีเดียว โดยปกติแล้ว Tailscale จะทำหน้าที่เพียง routing ข้อมูลระหว่างอุปกรณ์ใน tailnet ของคุณเท่านั้น และไม่ยุ่งกับ traffic สาธารณะ ในโหมด userspace เริ่มต้นของ container image นั้น Tailscale จะไม่สร้าง interface ใดๆ ขึ้นมา จึงไม่ส่งผลต่อการ routing หากใช้ TS_USERSPACE=false ระบบจะติดตั้งเส้นทางสำหรับ 100.64.0.0/10 และ subnet ที่คุณประกาศไว้เท่านั้น ข้อยกเว้นคือการใช้ exit node: sudo tailscale set --exit-node=<exit-node-ip> จะทำให้ Tailscale กลายเป็น default route และเป็นผู้จัดการ traffic ทั้งหมด ควรเลือกผลิตภัณฑ์เพียงตัวเดียวให้เป็นผู้จัดการ default route แทนการใช้งานร่วมกันทั้งสองตัว