Gluetun 컨테이너 네트워크 통신 및 포트 설정 방법
Gluetun 네트워크 네임스페이스를 사용하는 컨테이너의 포트 설정 오류를 해결합니다. 컨테이너가 아닌 Gluetun 서비스에 포트를 게시하고, 필요한 서브넷만 허용하여 외부 통신을 구성하는 정확한 방법을 단계별로 안내합니다.
컨테이너가 gluetun 네트워크에 참여하면 발생하는 일
network_mode: service:gluetun를 설정한 컨테이너는 자체 네트워크 인터페이스를 가지지 않습니다. 이 컨테이너는 gluetun의 네트워크 네임스페이스에 참여하게 되며, 따라서 포트 게시 및 방화벽 규칙은 해당 컨테이너의 속성이 아닌 gluetun 서비스의 속성이 됩니다. 아래의 모든 답변은 이 사실에서 비롯됩니다.
네트워크 네임스페이스는 커널이 관리하는 네트워크 스택의 독립적인 복사본으로, 고유한 인터페이스, 라우팅 테이블, 방화벽 규칙 및 리스닝 소켓을 포함합니다. Docker는 기본적으로 각 컨테이너에 하나씩 할당합니다. network_mode: service:gluetun를 작성하면 Docker는 이 단계를 건너뛰고 새 컨테이너를 gluetun이 이미 소유한 네임스페이스 안에 배치합니다. 컨테이너는 자체 파일 시스템을 유지하지만, 자체 /etc/hosts나 /etc/resolv.conf은 가지지 않습니다. Docker는 gluetun이 사용하는 것과 동일한 파일을 컨테이너에 제공하며, 이는 나중에 중요한 문제가 됩니다.
이를 직접 확인할 수 있습니다.
docker inspect -f '{{.HostConfig.NetworkMode}}' qbittorrent이 명령은 container:과 gluetun 컨테이너 ID를 출력합니다. 일반적인 컨테이너라면 bridge을 출력했을 것입니다. 이 가이드는 gluetun을 사용하여 Docker 트래픽을 VPN으로 라우팅하기에서 이어집니다. 터널은 정상적으로 작동하지만, 이제 컨테이너와 통신할 수 없는 상태가 됩니다.
포트를 애플리케이션이 아닌 gluetun에 게시하십시오
서비스에 ports: 블록을 남겨두고 network_mode을 설정하면 Docker는 컨테이너 생성을 거부합니다:
Error response from daemon: conflicting options: port publishing and the container type network mode이유는 간단합니다. 포트를 게시한다는 것은 호스트 포트를 컨테이너의 네트워크 네임스페이스로 전달하는 NAT(네트워크 주소 변환) 규칙을 추가하는 것인데, 이 컨테이너에는 해당 네임스페이스가 없기 때문입니다. 매핑을 gluetun 서비스로 옮기십시오. 애플리케이션은 여전히 공유 네임스페이스 내부의 해당 포트에서 수신 대기 중이므로 포트 번호는 변경되지 않습니다.
services:
gluetun:
ports:
- "8080:8080/tcp" # qBittorrent web UI
qbittorrent:
network_mode: "service:gluetun"
# no ports: block here종속 서비스의 expose: 블록도 동일한 방식으로 실패하며 conflicting options: port exposing and the container type network mode 오류가 발생합니다. 또한 해당 위치의 networks: 블록도 치명적인 오류를 일으킵니다. Compose는 해당 서비스가 상호 배타적인 network_mode와 networks를 선언했다고 보고하며 파일 로드를 완전히 거부합니다.
한 가지 결과는 나중에 나타납니다. 네임스페이스 내의 모든 컨테이너는 단일 포트 공간을 공유하므로, 기본값이 모두 8080인 두 애플리케이션이 충돌하게 되며, 두 번째로 시작되는 애플리케이션은 주소가 이미 사용 중이라는 오류와 함께 실패합니다. LinuxServer qBittorrent 이미지의 WEBUI_PORT 변수와 같이 각 애플리케이션의 자체 설정에서 포트 번호를 변경한 다음, gluetun에서 새 번호를 게시하십시오.
gluetun 뒤에 있는 컨테이너들은 서로 어떻게 통신합니까?
네임스페이스 내부에서 이들은 이미 루프백 인터페이스를 공유합니다. gluetun 뒤에 있는 컨테이너는 Docker 네트워크를 거치지 않고 127.0.0.1:<port>에서 형제 컨테이너에 도달합니다.
네임스페이스 외부에서 볼 때, 해당 컨테이너는 이름이 없습니다. Docker의 내장 DNS는 서비스 이름을 사용자 정의 네트워크상의 해당 서비스 주소로 해석하는데, 이 컨테이너는 어떤 네트워크에도 주소를 가지고 있지 않습니다. 따라서 Sonarr와 같은 일반 컨테이너는 http://qbittorrent:8080에서 토렌트 클라이언트에 도달할 수 없습니다. 대신 http://gluetun:8080를 통해 도달하는데, 이는 소켓이 gluetun의 네임스페이스 내에서 gluetun의 주소로 리스닝하고 있기 때문입니다. Docker Compose 네트워크와 서비스 이름의 작동 방식을 알고 일반적인 이름 지정 규칙이 적용될 것이라 예상하는 사용자들에게는 이 점이 의외로 다가올 수 있습니다. 두 컨테이너가 같은 Compose 네트워크에 있다면 호스트에 포트를 게시하지 않아도 이 방식은 작동합니다.
다른 항목을 디버깅하기 전에 DNS를 먼저 확인하십시오. gluetun은 네임스페이스 내부에서 자체 DNS 서버를 실행하며, Docker는 gluetun 뒤에 있는 모든 컨테이너에 gluetun과 동일한 /etc/resolv.conf 파일을 제공합니다. 따라서 애플리케이션의 조회는 호스트의 리졸버가 아닌 gluetun의 리졸버를 거치게 됩니다. 기본적으로 해당 리졸버는 사설 주소를 가리키는 응답을 거부하며, 이것이 호스트에서 해석되는 이름이 컨테이너 내부에서는 실패할 수 있는 이유입니다. gluetun 문서에 따르면 이러한 호스트 이름은 DNS_REBINDING_PROTECTION_EXEMPT_HOSTNAMES에 나열되어야 합니다.
docker exec qbittorrent cat /etc/resolv.confDocker 호스트에서 실행 중인 서비스에 어떻게 접근합니까?
host.docker.internal를 사용하십시오. 두 가지 설정이 모두 gluetun 서비스에 필요합니다. 두 가지 서로 다른 문제가 발생하기 때문입니다.
이름이 먼저 옵니다. gluetun 뒤에 있는 컨테이너는 gluetun의 /etc/hosts을 읽으며, Docker는 conflicting options: custom host-to-IP mapping and the network mode으로 network_mode를 설정한 서비스의 extra_hosts 항목을 거부합니다. 따라서 해당 항목은 gluetun에 있어야 하며, 네임스페이스 내의 모든 컨테이너가 이를 인식합니다.
gluetun:
extra_hosts:
- "host.docker.internal:host-gateway"host-gateway은 Docker가 호스트 자체의 내부 주소로 대체하는 특수 값입니다. 일반적인 Linux Docker 설치 환경에서는 이것이 172.17.0.1로 흔히 알려진 docker0 브리지의 주소입니다. VPS에서 ip -4 addr show docker0을 실행하여 본인의 주소를 확인하십시오. Docker Desktop은 이 이름을 자체적으로 해석하므로, 노트북에서 작성된 가이드들은 extra_hosts 줄을 생략하지만 동일한 파일을 서버에서 사용하면 실패하게 됩니다.
경로가 두 번째로 옵니다. 이름을 추가하는 것은 컨테이너에 어떤 주소를 사용할지 알려줄 뿐입니다. 패킷은 여전히 gluetun의 기본 경로인 터널을 통해 나가게 되며, gluetun의 방화벽이 이를 차단합니다. 이 경우 연결이 거부되는 것이 아니라, 응답을 기다리다가 시간이 초과되는 증상이 나타납니다. 거부(refused)는 패킷이 도착하여 응답을 받은 상태를 의미하고, 시간 초과(timeout)는 패킷이 도착하지 않았음을 의미합니다.
gluetun:
environment:
- FIREWALL_OUTBOUND_SUBNETS=172.17.0.1/32그다음 호스트 서비스가 해당 주소에서 실제로 수신 대기 중인지 확인하십시오. 127.0.0.1에만 바인딩된 PostgreSQL 서버는 자체 네트워크 네임스페이스를 가진 컨테이너에서 접근할 수 없습니다. 여기에는 터널 사용 여부와 관계없이 gluetun 뒤에 있는 모든 컨테이너가 포함됩니다. 네임스페이스 내부의 127.0.0.1은 해당 네임스페이스 자신의 루프백이기 때문입니다. 호스트 네트워킹을 사용하는 컨테이너만이 호스트의 루프백을 공유하며, gluetun 뒤에 있는 컨테이너는 이미 network_mode를 gluetun에 할당했습니다. 대신 PostgreSQL을 172.17.0.1에 바인딩하십시오. 그러면 공용 인터페이스를 노출하지 않으면서 컨테이너로부터의 연결을 수락합니다. 호스트에서 ss -lntp | grep 5432을 사용하여 확인하십시오.
FIREWALL_OUTBOUND_SUBNETS가 실제로 변경하는 내용
gluetun 문서에서는 이 설정을 gluetun과 해당 네트워크 스택을 공유하는 컨테이너가 접근할 수 있는 서브넷의 쉼표로 구분된 목록으로 설명하며, 방화벽과 라우팅 변경이 수반된다고 명시합니다. 두 가지 측면 모두 중요합니다. gluetun은 나열된 각 서브넷에 대해 Docker 브리지 게이트웨이를 통과하는 경로를 추가하므로, 해당 주소로 향하는 패킷은 터널이 아닌 eth0을 통해 나갑니다. 또한 gluetun은 VPN 서버로 향하지 않는 아웃바운드 트래픽을 기본적으로 차단하기 때문에, 이 설정을 통해 해당 서브넷에 대한 방화벽을 개방합니다.
값을 작성할 때는 쉼표 뒤에 공백을 넣지 마십시오.
- FIREWALL_OUTBOUND_SUBNETS=172.17.0.1/32,192.168.1.0/24,100.64.7.9/32놓치기 쉬운 두 가지 속성이 있습니다. 이 설정은 네임스페이스 수준에서 적용되므로, 의도했던 컨테이너뿐만 아니라 gluetun 뒤에 있는 모든 컨테이너에 영향을 미칩니다. 또한 이 설정은 아웃바운드에만 적용됩니다. 즉, 컨테이너가 시작하는 연결을 제어합니다. 게시된 포트로 들어오는 연결은 다른 경로를 통과하므로 여기에 입력할 필요가 없습니다.
Tailscale 피어에서 웹 UI 접속하기
Tailscale은 모든 머신에 100.64.0.0/10 범위의 주소를 할당하며, 이는 캐리어급 NAT를 위해 예약된 대역입니다. 개인용 tailnet에서는 이 모든 것이 무료이지만, 무료 플랜의 6인 사용자 제한에 따라 다른 사용자가 동일한 UI에 접근해야 할 때 비용 발생 여부가 결정됩니다. 모든 플랜에서 사용자 기기 수는 무제한입니다. 서버 및 기타 태그가 지정된 기기는 별도로 계산됩니다. Tailscale 가격 페이지에는 50개의 태그 지정 리소스가 포함되어 있으며, 추가 리소스는 개당 월 1달러입니다. 요금은 기기가 아닌 사용자를 기준으로 책정되므로, 유료 tailnet의 실제 비용은 컨테이너 노출 개수가 아니라 초대하는 사용자 수에 따라 결정됩니다. 인바운드와 아웃바운드 연결은 각각 다른 설정이 필요합니다.
인바운드는 설정이 간단합니다. gluetun에서 8080:8080을 게시하면 호스트의 모든 주소에서 해당 포트가 바인딩됩니다. 호스트의 tailscale0 인터페이스도 그중 하나이므로, 피어는 http://<machine-name>:8080으로 접속하여 컨테이너에 도달할 수 있습니다. Docker의 NAT 규칙이 네임스페이스 외부인 호스트에 위치하므로, 이 경로에서 gluetun은 아무런 역할을 하지 않습니다.
UI를 tailnet을 통해서만 접속할 수 있게 하려면, 게시된 포트를 모든 주소가 아닌 호스트의 Tailscale 주소에 바인딩하십시오.
ports:
- "100.101.102.103:8080:8080/tcp"호스트에서 tailscale ip -4 명령을 사용하여 해당 주소를 찾을 수 있습니다. 포트가 공용 인터페이스에서 전혀 열리지 않기 때문에, 바인딩은 방화벽 규칙보다 더 강력한 제어 수단입니다. 호스트와 포트 대신 HTTPS 도메인 이름으로 UI에 접속하려면 tailscale serve를 사용하여 게시된 포트 앞단에 배치할 수 있습니다. 단, serve와 funnel은 접근 가능한 대상이 다르며 둘 중 하나만 UI를 tailnet 내에 유지합니다. 또한 이 방법은 Docker가 ufw를 우회하여 포트를 게시하는 문제를 피할 수 있게 합니다. Compose를 위한 Tailscale 사이드카 패턴은 호스트 포트를 완전히 제거하는 더 나은 방법이지만, 이는 애플리케이션을 사이드카의 네트워크 네임스페이스에 배치하는 방식으로 작동합니다. gluetun 뒤에 있는 컨테이너는 이미 network_mode을 gluetun에 사용하고 있으므로 이 패턴을 적용할 수 없습니다.
아웃바운드는 FIREWALL_OUTBOUND_SUBNETS가 다시 문제가 되는 지점입니다. 컨테이너가 피어를 호출해야 하는 경우, 해당 피어의 주소를 추가하고 전체 /10 대신 피어별 /32를 사용하는 것이 좋습니다. 호출하려는 머신이 tailnet에 해당 서브넷을 광고하는 VPS를 통해 도달하는 사설 네트워크에 있다면, 라우터 자체의 100.x 주소 대신 광고된 대역을 나열하고 호스트가 해당 경로를 수락했는지 확인하십시오. 컨테이너는 호스트의 리졸버 대신 gluetun의 DNS 서버를 사용하므로, 기본적으로 컨테이너 내부에서 MagicDNS 이름이 해석되지 않습니다. 따라서 숫자 형태의 100.x 주소를 사용하거나 gluetun 서비스의 extra_hosts 라인에 이름을 고정하십시오. gluetun의 DNS_UPSTREAM_PLAIN_ADDRESSES 옵션을 사용하면 쿼리를 다른 리졸버로 전달할 수 있지만, 모든 쿼리가 평문으로 전송됩니다. gluetun 문서에서는 사설 주소에 대한 쿼리가 VPN 터널 밖으로 유출될 수 있으므로 이 방법을 권장하지 않습니다. Headscale을 사용하여 직접 Tailscale 제어 서버를 운영할 때도 동일하게 적용됩니다.
일반적인 구성을 위한 완성된 compose 파일
VPN 뒤에 위치한 다운로드 클라이언트, tailnet에서만 응답하는 두 개의 웹 UI, 그리고 호스트에서 실행 중인 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
extra_hosts:
- "host.docker.internal:host-gateway"
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"
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 외의 다른 곳에서는 응답하지 않습니다. extra_hosts 라인 또한 gluetun에 위치하는데, 이는 Prowlarr가 host.docker.internal을 해석할 때 gluetun의 /etc/hosts를 읽기 때문이며, Prowlarr 자체에 해당 라인을 작성하면 Docker가 거부하기 때문입니다. FIREWALL_OUTBOUND_SUBNETS는 두 개의 단일 주소를 지정합니다. 하나는 Prowlarr가 데이터베이스 연결을 열 수 있도록 하는 호스트의 Docker 브리지 주소이고, 다른 하나는 하나의 tailnet 피어 주소입니다.
PostgreSQL 서버는 의도적으로 이 파일에서 제외되었습니다. 이 서버는 172.17.0.1:5432에서 수신 대기하는 일반 시스템 서비스로 VPS에서 실행됩니다. 이는 Docker Compose의 arr 스택과 동일한 계층 구조이며, 데이터베이스만 Docker 외부로 이동한 형태입니다.
WireGuard 개인 키는 compose 파일에 포함하지 마십시오. ${WIREGUARD_PRIVATE_KEY}은 옆에 있는 .env 파일에서 읽어오며, 이 패턴은 Docker Compose를 위한 env 파일 및 보안 설정에서 다룹니다. condition: service_healthy 절은 gluetun 이미지가 기본적으로 제공하는 healthcheck를 사용하므로, 터널이 활성화되었다고 보고하기 전까지는 아무것도 시작되지 않습니다. Compose healthcheck에서 일반적인 형식을 설명합니다.
tailnet뿐만 아니라 모든 주소에 게시하기
VPS 공인 IP를 포함하는 0.0.0.0에서 주소 접두사와 포트 바인딩을 제거하십시오. 이 작업은 본인이 제어하는 방화벽 뒤에서만 수행해야 하며, 먼저 위의 ufw 관련 주의 사항을 읽어 보십시오.
ports:
- "8080:8080/tcp"터널이 트래픽을 정상적으로 전송하는지 확인
네임스페이스 내부에서 한 번, 호스트에서 한 번, 총 두 번 동일한 요청을 실행하여 결과를 비교합니다.
docker run --rm --network=container:gluetun curlimages/curl:latest -s https://api.ipify.org
curl -s https://api.ipify.org첫 번째 요청은 VPN 제공업체의 출구 주소를 출력해야 합니다. 두 번째 요청은 VPS 주소를 출력해야 합니다. 두 주소가 일치한다면 컨테이너의 트래픽이 터널을 통하지 않는 상태입니다. 이 문제가 해결되기 전까지는 이 가이드의 다른 모든 수정 사항은 의미가 없습니다.
라우팅 규칙과 테이블을 통해 터널을 통하는 트래픽과 그렇지 않은 트래픽을 확인할 수 있습니다.
docker run --rm --network=container:gluetun alpine:3.22 sh -c 'ip rule show; ip route show table 51820; ip route show table 199'이 가이드의 WireGuard 설정에서 gluetun은 터널 인터페이스를 wg0로 명명합니다. tun0라는 이름은 OpenVPN의 기본값일 뿐입니다. 또한 gluetun은 터널의 기본 경로를 메인 라우팅 테이블에 포함하지 않습니다. ip rule show에는 WireGuard 자체가 표시하지 않은 모든 트래픽을 테이블 51820으로 보내는 규칙이 포함되어야 하며, 해당 테이블은 wg0에 기본 경로를 유지해야 합니다. 테이블 199는 FIREWALL_OUTBOUND_SUBNETS의 각 항목당 하나의 경로를 보유하며, 각 경로는 eth0의 Docker 브리지 게이트웨이를 가리킵니다. 이 테이블의 규칙은 터널 규칙 직전에 확인됩니다. 일반적인 ip route show 명령은 여전히 eth0에 Docker 자체의 기본 경로를 나열합니다. 이는 예상된 동작이며 유출이 아닙니다. gluetun의 두 규칙 모두 메인 테이블보다 먼저 확인되기 때문입니다. 테이블 199에 나열된 경로 중 사용자가 직접 추가하지 않은 경로가 있다면, 해당 트래픽은 VPN을 우회하는 것입니다.
gluetun의 제어 서버는 /v1/publicip/ip의 포트 8000에서 동일한 공인 IP를 보고할 수 있지만, 기본적으로는 인증을 설정한 후에만 가능합니다. gluetun 문서에 따르면 모든 제어 서버 경로는 이제 기본적으로 비공개이므로, 해당 경로로 보내는 일반적인 요청은 실패합니다. /gluetun/auth/config.toml에 바인드 마운트된 인증 파일을 basic 또는 apikey 방식으로 구성한 다음 gluetun을 재시작하십시오. 위의 compose 파일은 이미 ./gluetun를 /gluetun에 마운트하므로, 호스트의 ./gluetun/auth/config.toml 위치에 파일을 배치하면 됩니다. HTTP_CONTROL_SERVER_AUTH_DEFAULT_ROLE 변수를 사용하는 것이 더 빠른 방법입니다. 이 변수를 {"auth":"none"}로 설정하면 인증이 완전히 비활성화되지만, 문서에서는 이를 강력히 권장하지 않습니다. v3.39.1 이전 릴리스에서는 서버가 이런 방식으로 잠겨 있지 않으며, 문서에서는 서버에 접근할 수 있는 모든 대상을 신뢰해야 한다고 경고합니다.
잘못된 서브넷 설정으로 발생하는 유출
FIREWALL_OUTBOUND_SUBNETS은 의도적으로 방화벽에 뚫는 구멍이므로, 구멍의 크기가 곧 위험의 크기입니다. 이 구멍이 너무 커지는 네 가지 경우는 다음과 같습니다.
0.0.0.0/0은 모든 트래픽을 터널 외부로 보냅니다. 위에서 언급한 두 가지 IP 확인 절차를 거치면 첫 실행 시 이를 잡아낼 수 있습니다. 두 확인 결과가 동일한 주소를 반환하기 때문입니다.- 대상보다 넓은 범위를 설정하는 경우입니다.
10.0.1.7에 있는 한 대의 머신에 접근하려고10.0.0.0/8범위를 열면, 해당 범위 내에서 토렌트 피어가 광고할 수 있는 모든 주소도 함께 열리게 됩니다.10.0.1.7/32와 같이 명시적으로 작성하십시오. - 터널 자체 주소와 겹치는 범위를 설정하는 경우입니다. gluetun 문서에서는 이 경우 VPN 트래픽이 브리지를 통해 외부로 나가게 되어 포트 포워딩이 중단된다고 경고합니다. 사설 대역을 열기 전에
WIREGUARD_ADDRESSES값을 먼저 확인하십시오. - Tailscale에
100.64.0.0/10을 사용하는 경우입니다. 이는 단 하나의 피어에 접근하기 위해 약 400만 개의 주소를 여는 것과 같습니다. 필요한 피어는/32항목으로 나열하십시오.
이 설정은 전체 네임스페이스에 적용된다는 점을 기억하십시오. 인덱서가 호스트 서비스에 접근할 수 있도록 서브넷을 열면, 같은 네임스페이스를 공유하는 토렌트 클라이언트에도 동일한 서브넷이 열립니다. 이 변수를 변경할 때마다 공인 IP 확인 절차를 다시 수행하십시오. 의도한 대로 변경되었는지 확인할 수 있는 유일한 방법이기 때문입니다.
gluetun을 재시작할 때 발생하는 문제
gluetun은 네트워크 네임스페이스를 소유하므로, gluetun의 생명 주기는 곧 네임스페이스의 생명 주기와 같습니다. gluetun이 중단된 상태에서 의존 컨테이너를 시작하면 즉시 실패합니다.
Error response from daemon: cannot join network namespace of a non running container: container <id> is exitedgluetun을 그 자리에서 재시작하는 것은 더 조용한 실패를 유발합니다. 의존 컨테이너들은 계속 실행 중이지만, 이들이 연결된 네임스페이스는 그 아래에서 재구축됩니다. 이로 인해 docker ps는 모든 것이 정상이라고 보고하지만, 실제로는 아무런 응답도 하지 않게 됩니다. gluetun 서비스에 변경 사항이 발생하면, 일부만 재시작하지 말고 그룹 전체를 다시 생성하십시오.
docker compose up -d --force-recreate이미지 업데이트 시에도 동일하게 적용됩니다. 새로운 gluetun 이미지를 내려받고 해당 서비스만 다시 생성하면, 다른 컨테이너들은 더 이상 존재하지 않는 네임스페이스를 참조하게 됩니다.
FAQ
Docker에서 "port publishing and the container type network mode"라는 오류가 발생하는 이유는 무엇입니까?
network_mode: service:gluetun를 설정한 서비스에 ports: 블록이 남아 있기 때문입니다. 포트를 게시하면 호스트 포트를 컨테이너의 네트워크 네임스페이스로 전달하는 NAT 규칙이 추가되는데, 해당 모드의 컨테이너는 별도의 네임스페이스를 가지지 않습니다. 해당 서비스에서 ports: 블록을 삭제하고 동일한 매핑을 gluetun 서비스에 추가하십시오. 애플리케이션은 여전히 공유 네임스페이스 내부에서 해당 포트를 리스닝하므로 포트 번호는 동일하게 유지됩니다.
gluetun 뒤에 있는 서비스에 다른 컨테이너가 어떻게 접근합니까?
동일한 네임스페이스 내의 컨테이너들은 127.0.0.1을 통해 서로 통신합니다. 외부 컨테이너는 gluetun 서비스 이름을 사용하므로 http://qbittorrent:8080 대신 http://gluetun:8080을 사용해야 합니다. 애플리케이션 컨테이너는 Docker 네트워크상에 주소를 가지지 않으므로, 내장 DNS 서버가 해당 컨테이너의 이름을 해석할 수 없습니다. 두 컨테이너가 동일한 Compose 네트워크를 공유한다면 포트 게시 없이도 통신이 가능합니다.
FIREWALL_OUTBOUND_SUBNETS에는 무엇을 입력해야 합니까?
gluetun 뒤에 있는 컨테이너가 연결을 시작해야 하는 주소만 입력하며, 최대한 좁은 범위로 작성하십시오. 단일 머신은 /32로 표기합니다. 일반적으로 172.17.0.1/32의 Docker 호스트와 연결하려는 각 Tailscale 피어에 대한 /32을 입력합니다. 0.0.0.0/0를 추가하지 마십시오. 또한 VPN 자체 터널 주소와 겹치는 범위를 추가해서도 안 됩니다. 게시된 포트로 들어오는 인바운드 연결은 여기에 항목을 추가할 필요가 없습니다.
컨테이너에서 Tailscale MagicDNS 이름을 해석할 수 없는 이유는 무엇입니까?
MagicDNS는 호스트의 리졸버를 Tailscale의 DNS 서버로 지정하여 작동하는데, 컨테이너는 호스트의 리졸버를 사용하지 않기 때문입니다. 컨테이너는 gluetun과 공유하는 /etc/resolv.conf을 사용하며, 이는 gluetun의 자체 DNS 서버를 가리킵니다. docker exec <container> cat /etc/resolv.conf로 이를 확인하십시오. 피어의 숫자형 100.x 주소를 사용하거나, gluetun 서비스에 extra_hosts 항목을 추가하여 이름을 고정하십시오. Gluetun은 DNS_UPSTREAM_PLAIN_ADDRESSES을 통해 다른 리졸버로 쿼리를 전달할 수 있지만, 이는 평문으로 전송되므로 문서에서는 권장하지 않습니다.
트래픽이 VPN을 통해 전달되고 있는지 어떻게 확인합니까?
네임스페이스 내부에서 요청을 하나 실행하고 호스트에서도 동일한 요청을 실행한 뒤 결과를 비교하십시오. docker run --rm --network=container:gluetun curlimages/curl:latest -s https://api.ipify.org은 VPN 제공업체의 출구 주소를 반환해야 하며, VPS에서 실행한 curl -s https://api.ipify.org는 VPS의 주소를 반환해야 합니다. 두 결과가 일치한다면 터널이 컨테이너의 트래픽을 전달하지 않는 것입니다. FIREWALL_OUTBOUND_SUBNETS을 변경할 때마다 이 확인 절차를 다시 수행하십시오.