SSD Nodes Learn 🎉 VPS เริ่ม $5.50/เดือน
คู่มือ Matt Connorโดย Matt Connor · อัปเดตเมื่อ 2026-08-13

วิธีเชื่อมต่อ Docker container ผ่าน VPN ด้วย Gluetun

แก้ไขปัญหาพอร์ตหายเมื่อทำ Docker network namespace ร่วมกับ VPN เรียนรู้วิธีตั้งค่า Gluetun sidecar ที่ถูกต้องเพื่อคงการเข้าถึงพอร์ต พร้อมตัวอย่างไฟล์ Docker Compose ที่ใช้งานได้จริง

เหตุใดพอร์ตจึงหายไปเมื่อกำหนดเส้นทาง Docker container ผ่าน VPN

ในการกำหนดเส้นทาง Docker container ผ่าน VPN คุณต้องให้ container หนึ่งตัวเป็นผู้จัดการอุโมงค์เชื่อมต่อ แล้วจึงเชื่อมต่อ container อื่นๆ เข้ากับ network namespace ของ container นั้นด้วย network_mode: "service:gluetun" การเชื่อมต่อในลักษณะนี้เป็นจุดที่ทำให้ผู้ใช้งานหลายคนแปลกใจ เนื่องจาก container ที่ถูกเชื่อมต่อจะไม่เหลือเครือข่ายเป็นของตัวเองอีกต่อไป พอร์ตที่ประกาศไว้ (published ports) และชื่อบริการ Docker ของมันจึงหายไปพร้อมกัน ให้คุณประกาศพอร์ตบน 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 และมีไฟร์วอลล์ในตัว เวอร์ชัน 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 ประเด็นดังนี้

  • แอปพลิเคชันจะไม่มี address เป็นของตัวเอง แต่จะใช้ address ของ gluetun แทน
  • แอปพลิเคชันไม่ได้เชื่อมต่อกับ Docker network ใดๆ ทำให้ชื่อบริการ (service name) ไม่ถูกลงทะเบียนและไม่สามารถ 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 ชี้ไปยังคอมมิตล่าสุดของสาขา 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 -30

docker 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 ใช้สำหรับเก็บข้อมูลรับรอง (credentials) และไม่ควรนำไปรวมใน git:

WIREGUARD_PRIVATE_KEY=wOEI9rqqbDwnN8/Bpp22sVz48T71vJ4fYmFWujulwUU=
WIREGUARD_ADDRESSES=10.64.222.21/32

ค่าทั้งสองได้มาจากไฟล์การตั้งค่า WireGuard ที่คุณสร้างขึ้นในส่วนบัญชีของผู้ให้บริการ ให้ตั้งค่าไฟล์เป็นโหมด 600 โปรดตระหนักถึงผลลัพธ์ที่แท้จริงของวิธีนี้: แม้คีย์จะไม่ได้อยู่ใน repository ของคุณ แต่ docker inspect gluetun ก็ยังคงแสดงค่าตัวแปรสภาพแวดล้อม (environment variable) ทั้งหมดให้ทุกคนที่สามารถเข้าถึง Docker socket เห็นได้ ไฟล์สภาพแวดล้อมและข้อมูลลับใน Docker Compose ครอบคลุมถึงตัวเลือกที่มีความปลอดภัยสูงกว่านี้

วิธีที่คอนเทนเนอร์ภายนอกอุโมงค์สื่อสารกับคอนเทนเนอร์ภายใน

การสื่อสารทำได้ทั้งสองทิศทางโดยใช้ชื่อเรียกที่แตกต่างกัน คอนเทนเนอร์ทั้งสองต้องใช้ Docker network ร่วมกัน ซึ่งในที่นี้คือเครือข่ายของ gluetun เนื่องจากคอนเทนเนอร์ที่แนบมานั้นไม่มีเครือข่ายเป็นของตัวเอง เนื้อหา วิธีการเชื่อมต่อเครือข่ายของ Docker Compose ได้อธิบายค่าเริ่มต้นไว้แล้ว

สำหรับการสื่อสารจากภายนอกเข้าสู่ภายใน ให้ใช้ชื่อของ gluetun และพอร์ตที่แอปพลิเคชันนั้นเปิดใช้งานอยู่ ตัวอย่างเช่น คอนเทนเนอร์ reverse proxy สามารถเข้าถึงหน้าเว็บอินเทอร์เฟซของ qBittorrent ได้ที่ gluetun:8080 โดยไม่จำเป็นต้องตั้งค่า ports: เนื่องจากทราฟฟิกแบบคอนเทนเนอร์ถึงคอนเทนเนอร์จะวิ่งอยู่ภายใน Docker network เท่านั้นและไม่ผ่านพอร์ตของโฮสต์

สำหรับการสื่อสารจากภายในออกสู่ภายนอก ให้ใช้ชื่อบริการของคอนเทนเนอร์อีกตัวหนึ่ง เช่น postgres:5432 ทั้งนี้ gluetun สามารถแก้ไขชื่อคอนเทนเนอร์อื่นจากภายในเนมสเปซของตัวเองได้ตั้งแต่เวอร์ชัน v3.41 เป็นต้นไป ดังนั้นหากพบปัญหาการแก้ไขชื่อ (name resolution) ให้ระบุเวอร์ชันดังกล่าวหรือใหม่กว่า

ไฟร์วอลล์ของ 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 ขัดข้อง

รูปแบบนี้มีความซับซ้อนเมื่อเกิดความล้มเหลว Container ที่เชื่อมต่ออยู่ไม่มีเส้นทางสำรอง เส้นทางเดียวที่จะออกไปนอกเครื่องได้คือ namespace ที่แชร์ร่วมกัน ดังนั้นเมื่อ tunnel ใช้งานไม่ได้ จึงไม่มีเส้นทางอื่นให้ใช้งานอีก ไฟร์วอลล์ของ Gluetun บังคับใช้กฎเดียวกันจากอีกฝั่ง คือ traffic ขาออกต้องผ่าน tunnel หรือไปยัง endpoint ของ VPN server เท่านั้น ส่วนข้อมูลอื่นทั้งหมดจะถูกทิ้ง (drop) จึงไม่มีช่วงเวลาที่แพ็กเก็ตจะรั่วไหลออกทางอินเทอร์เฟซปกติในระหว่างที่ไคลเอนต์กำลังเชื่อมต่อใหม่

Gluetun จะเฝ้าดูการเชื่อมต่อของตัวเอง ทุกๆ 1 นาที มันจะส่ง ICMP echo (ping) ไปยังที่อยู่ตาม HEALTH_ICMP_TARGET_IPS ซึ่งค่าเริ่มต้นคือ 1.1.1.1,8.8.8.8 ทุกๆ 5 นาที มันจะทำการ dial แบบเต็มรูปแบบผ่าน TCP และ TLS (transport layer security) ไปยัง HEALTH_TARGET_ADDRESSES ซึ่งค่าเริ่มต้นคือ cloudflare.com:443,github.com:443 เมื่อการตรวจสอบเหล่านี้ล้มเหลว มันจะรีสตาร์ท VPN ภายใน container และบันทึก log ไว้:

WARN [vpn] restarting VPN because it failed to pass the healthcheck: periodic check: dialing: dial tcp4: lookup cloudflare.com: i/o timeout

ให้อ่าน log ของ container ที่เชื่อมต่ออยู่โดยคำนึงถึงลำดับนี้ บรรทัดอย่างเช่น connection refused, operation not permitted และ i/o timeout ภายในแอปเป็นผลลัพธ์ที่เกิดจาก tunnel ที่ตายแล้ว ไม่ใช่สาเหตุ เอกสารของ Gluetun ระบุเรื่องนี้ไว้อย่างชัดเจน เพราะผู้ใช้มักรายงานผลลัพธ์ที่เกิดขึ้นและเสียเวลาไล่ตรวจสอบไปหลายชั่วโมง

HEALTH_RESTART_VPN=on เป็นค่าเริ่มต้นและควรเปิดไว้ ให้ปิดเฉพาะในขณะที่คุณกำลังดีบั๊กความล้มเหลวเฉพาะจุดเท่านั้น เพราะหากปิดไว้ tunnel ที่ตายแล้วจะยังคงอยู่ในสถานะตายต่อไป

การจัดลำดับ: หยุดไม่ให้ stack เริ่มทำงานก่อนที่ tunnel จะพร้อมใช้งาน

อิมเมจนี้มาพร้อมกับ Docker healthcheck:

HEALTHCHECK --interval=5s --timeout=5s --start-period=10s --retries=1 CMD /gluetun-entrypoint healthcheck

คำสั่งดังกล่าวจะรัน gluetun อีกชุดหนึ่งขึ้นมาในช่วงเวลาสั้นๆ เพื่อสอบถามสถานะจาก health server ของตัวที่กำลังทำงานอยู่ที่ http://127.0.0.1:9999/ หาก tunnel ทำงานปกติจะตอบกลับมาเป็น 200 OK แต่หาก tunnel มีปัญหาจะตอบกลับมาเป็น 500 Internal server error พร้อมข้อความแจ้งข้อผิดพลาด และ container จะถูกทำเครื่องหมายว่าไม่พร้อมใช้งาน (unhealthy) ทันทีหลังจากล้มเหลวเพียงครั้งเดียว

condition: service_healthy คือสิ่งที่ใช้รอให้สถานะดังกล่าวพร้อมใช้งาน การใช้เพียง depends_on: [gluetun] จะรอแค่ให้ container เริ่มทำงานเท่านั้น ซึ่งเกิดขึ้นก่อนที่การทำ handshake จะเสร็จสมบูรณ์หลายวินาที ส่งผลให้แอปพลิเคชันเริ่มทำงานในขณะที่เครือข่ายยังไม่พร้อม และมักจะล้มเลิกความพยายามในการเชื่อมต่อตั้งแต่ครั้งแรก Healthchecks ใน Docker Compose ได้อธิบายรายละเอียดเกี่ยวกับไวยากรณ์และฟิลด์การกำหนดเวลาไว้

มีข้อจำกัดประการหนึ่งที่มักทำให้ผู้ใช้เกิดปัญหา คือ Compose จะประเมินเงื่อนไขนั้นเพียงครั้งเดียวในตอนที่สร้าง container เท่านั้น มันจะไม่หยุดหรือรีสตาร์ทแอปพลิเคชันในภายหลังหาก gluetun เปลี่ยนสถานะเป็น unhealthy ระบบ auto-healing ภายในของ gluetun จะทำหน้าที่จัดการในกรณีนี้แทน ซึ่งเป็นเหตุผลว่าทำไมมันถึงรีสตาร์ทกระบวนการ VPN แทนที่จะรีสตาร์ทตัว 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 ของผู้ให้บริการเพื่อรักษาช่องทางสำหรับผู้ดูแลระบบในการเข้าถึง 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 และติดตั้งเส้นทาง (routes) แต่เฉพาะสำหรับช่วงเครือข่าย tailnet 100.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 มีได้เพียงหนึ่งเดียวและต้องมีเจ้าของเพียงหนึ่งเดียวเท่านั้น

ผลข้างเคียงประการหนึ่งจะปรากฏให้เห็นเมื่อ 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: block ค้างอยู่ในบริการที่เชื่อมต่ออยู่ ให้ย้ายไปไว้ที่ gluetun

Compose ปฏิเสธไฟล์ทั้งหมด บริการหนึ่งไม่สามารถตั้งค่าทั้ง network_mode และ networks พร้อมกันได้ ให้ย้ายการตั้งค่า networks ไปไว้ที่ gluetun

container อื่นไม่สามารถ resolve ชื่อแอปได้ curl: (6) Could not resolve host: qbittorrent เป็นพฤติกรรมที่ถูกต้อง เพราะ container ที่เชื่อมต่อไม่ได้เข้าร่วม network ใดและไม่ได้ลงทะเบียนชื่อไว้ ให้ใช้ gluetun และระบุพอร์ตแทน

container ที่สองที่เชื่อมต่ออยู่ไม่ยอมเริ่มทำงาน สองกระบวนการใน namespace เดียวกันไม่สามารถ bind พอร์ตเดียวกันได้ และกระบวนการที่แพ้จะรายงานว่า address ถูกใช้งานอยู่ ให้เปลี่ยนพอร์ตภายในของแอป หรือรัน gluetun ตัวที่สอง

แอปไม่มี network หลังจากคุณแก้ไข gluetun การ restart หรือสร้าง gluetun ใหม่จะทำให้การเชื่อมต่อของทุกสิ่งที่เชื่อมต่ออยู่หลุดไป ให้ restart container เหล่านั้น

หน้าเว็บขนาดเล็กโหลดได้ แต่หน้าขนาดใหญ่ค้าง นั่นคือปัญหา MTU (maximum transmission unit) tunnel จะเพิ่ม overhead เข้าไป และมีบางอย่างในเส้นทางที่ทิ้ง packet ที่มีขนาดใหญ่เกินไปโดยไม่ส่งข้อความแจ้งเตือนกลับมา ให้ลดค่า 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 หมดอายุหรือไม่ จากนั้นตรวจสอบว่ารายการ server ล้าสมัยหรือไม่ และสุดท้ายตรวจสอบว่า firewall ของ host บล็อกการส่งข้อมูล UDP ขาออกหรือไม่

FAQ

ทำไมพอร์ตที่เผยแพร่ (published ports) ของคอนเทนเนอร์ถึงใช้งานไม่ได้เมื่ออยู่หลัง Gluetun?

เพราะ network_mode: "service:gluetun" นำคอนเทนเนอร์ไปไว้ใน network namespace ของ Gluetun ซึ่งในหนึ่ง namespace จะมีเพียงหนึ่ง IP address และหนึ่งชุดของพอร์ตที่เปิดใช้งาน แอปพลิเคชันยังคงทำงานอยู่ แต่กฎการเผยแพร่พอร์ตต้องกำหนดไว้ที่คอนเทนเนอร์ที่เป็นเจ้าของ 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 ได้ การสื่อสารระหว่างคอนเทนเนอร์ไม่จำเป็นต้องเผยแพร่พอร์ตใดๆ ส่วนในทิศทางตรงกันข้าม คอนเทนเนอร์ที่อยู่ภายใน namespace สามารถเข้าถึงคอนเทนเนอร์ภายนอกได้โดยใช้ชื่อบริการ เช่น postgres:5432 (สำหรับ Gluetun v3.41 ขึ้นไป) สำหรับไคลเอนต์ที่อยู่บน subnet อื่น เช่น แล็ปท็อปใน LAN ของคุณ ไฟร์วอลล์ของ Gluetun จะบล็อกการเชื่อมต่อจนกว่าคุณจะเพิ่ม subnet นั้นลงใน FIREWALL_OUTBOUND_SUBNETS

Gluetun ทำหน้าที่เป็น kill switch เมื่อการเชื่อมต่อ VPN หลุดหรือไม่?

ใช่ โดยมีเหตุผลสองประการพร้อมกัน ประการแรก คอนเทนเนอร์ที่เชื่อมต่ออยู่ไม่มีเส้นทางเครือข่ายอื่นนอกจากเส้นทางใน shared namespace ดังนั้นเมื่อ tunnel หยุดทำงาน คอนเทนเนอร์เหล่านั้นจึงไม่มีเส้นทางออกสู่ภายนอก ประการที่สอง ไฟร์วอลล์ของ Gluetun อนุญาตให้ส่งข้อมูลออกได้เฉพาะผ่าน tunnel และไปยังปลายทางของเซิร์ฟเวอร์ VPN เท่านั้น จากนั้น Gluetun จะรีสตาร์ท VPN ภายในโดยบันทึก log ว่า WARN [vpn] restarting VPN because it failed to pass the healthcheck แทนที่จะปิดตัวลง เพราะหาก Gluetun รีสตาร์ท คอนเทนเนอร์ที่เชื่อมต่ออยู่ทั้งหมดจะสูญเสียการเชื่อมต่อเครือข่ายทันที

หากใช้ Tailscale และ Gluetun ใน stack เดียวกัน บริการใดจะเป็นผู้รับผิดชอบการส่งข้อมูลออก?

ในทุกการตั้งค่า ยกเว้นกรณีเดียว คือ Gluetun โดยปกติแล้ว Tailscale จะทำหน้าที่เพียงกำหนดเส้นทางข้อมูลระหว่างอุปกรณ์ใน tailnet ของคุณเท่านั้น และจะไม่ยุ่งกับข้อมูลสาธารณะ ในโหมด 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) และเป็นผู้ควบคุมการส่งข้อมูล คุณควรเลือกผลิตภัณฑ์เพียงตัวเดียวให้เป็นผู้รับผิดชอบ default route แทนการใช้งานร่วมกันทั้งสองตัว