컨테이너는 사무실 LAN에서는 응답하지만, 회사 VPN이 연결되는 순간 응답을 멈춥니다. 컨테이너 안에서는 아무것도 바뀌지 않았습니다. 재시작도, 이미지 업데이트도, 설정 편집도 없었습니다. 바뀐 것은 호스트 라우팅 테이블이고, 터널이 추가한 경로가 이제 Docker가 내준 주소를 덮습니다. 서브넷을 고치거나 VPN 예외를 요청하기 전에 두 주소 계획을 나란히 놓으세요. 복구 방법마다 드는 시간이 크게 다르기 때문입니다.

설정을 바꾸기 전에 경로를 읽으세요

Docker가 실제로 만든 네트워크부터 시작합니다. IPAM 블록이 주소 계획이고, 서브넷이 중요한 줄입니다:

docker network inspect project_default --format '{{json .IPAM.Config}}'
# [{"Subnet":"172.18.0.0/16","Gateway":"172.18.0.1"}]

Compose는 기본적으로 새 프로젝트마다 /16을 할당하므로, 두 서비스가 65,536개 주소 범위 안에 들어갈 수 있습니다. 바로 그 넓이가 겹침을 흔하게 만듭니다. 무작위로 고른 /16은 터널로 접근하는 사이트를 삼키기에 충분히 크고, 터널이 올라오기 전까지는 아무도 알아채지 못합니다.

그다음 컨테이너 주소 하나에 대한 경로 선택을 비교합니다. 터널을 내린 상태에서 한 번, 올린 상태에서 한 번:

ip route get 172.18.0.7
# 172.18.0.7 dev br-3f2a9c src 172.18.0.1

답의 모양이 두 가지면 결과도 다릅니다. 출력이 tun0, wg0 또는 다른 터널 장치를 가리키면 VPN 프리픽스가 최소한 브리지 경로만큼 구체적이며 패킷은 컨테이너에 도달하지 않습니다. 그래도 브리지를 가리키면 호스트는 컨테이너에 도달할 수 있고, 문제는 필터링, DNS, 또는 한 방향에서만 실패하는 반환 경로입니다.

/16 충돌이 일어나기 쉬운 이유

Docker는 브리지 네트워크를 사설 풀에서 가져오고, 기본 풀은 172.16.0.0/12 블록에서 /16 조각으로 나옵니다. 기업 네트워크는 같은 사설 공간을 자주 쓰고, 원격 접속 VPN은 그 프리픽스를 접속하는 모든 클라이언트에 밀어 넣습니다. 양쪽이 172.18.0.0/16 를 자기 것이라 믿으면 한 라우팅 테이블에 상충하는 주장 두 개가 들어갑니다.

승자를 정하는 것은 프리픽스 길이이고, 경로가 나열된 순서가 아닙니다. 10.0.0.0/8 를 밀어 넣는 터널은 172.18.0.0/16 의 브리지 경로를 이기지 못합니다. /8이 덜 구체적이기 때문입니다. 172.18.0.0/16, 또는 172.18.5.0/24 그 안에 있는 것을 밀어 넣는 터널은 이깁니다. 같은 VPN인데 한 노트북은 사무실 네트워크에서 되고 호텔 Wi-Fi에서 안 되는 이유가 여기 있습니다. 클라이언트가 받는 프리픽스가 다르기 때문입니다.

같은 테이블이 반대 방향도 지배합니다. 10.8.4.20 에 있는 내부 호스트가 필요한 컨테이너는 패킷을 자기 브리지 게이트웨이로 보내고, 호스트가 그것을 터널로 라우팅합니다. 이 경로는 보통 호스트가 NAT를 하기 때문에 동작하지만, 기업 쪽이 노트북의 VPN 주소만 받아들이거나 역경로 필터링이 비대칭 응답을 버리면 실패합니다.

세 가지 복구 방법을 비교하세요

빠져나갈 길은 세 가지뿐이고, 누가 승인해야 하는지와 적용하는 동안 무엇이 깨지는지가 다릅니다.

RepairWho owns itWhat it costs
Renumber the container networkYouRecreates the network, restarts services, invalidates literal addresses in configs and scripts
Exclude your range from the tunnelNetwork teamChanges what every laptop sees, needs a change request and a client profile update
Reach the service through a published portYou and the clientOne port only, no direct container-to-container routing across the tunnel

재번호 부여는 티켓 없이 끝낼 수 있는 유일한 복구 방법이라 대부분의 글이 거기서 멈춥니다. 젊은 프로젝트에서는 싸고, 마이그레이션 도구나 저장된 연결 문자열이 리터럴 주소를 가리키면 비쌉니다. YAML을 고치기 전에 그 참조를 세세요. 후가 아니라 전입니다.

터널에서 제외하는 것은 겹침이 회사가 실제로 소유한 범위에 있을 때 가장 깔끔한 답입니다. 가장 느리기도 하고, 클라이언트 프로필을 바꿀 수 없는 외주 인력에게는 쓸모가 없습니다. 게시된 포트나 작은 리버스 프록시는 논쟁을 피합니다. 클라이언트는 터널이 이미 올바르게 라우팅하는 주소와 이야기하고, 그 포트만 도달 가능하면 됩니다.

범위를 의도적으로 고정하세요

프로젝트 하나라면 compose 파일에 서브넷을 선언해 프로젝트가 공유 풀에서 더 이상 가져오지 않게 하세요:

networks:
  default:
    ipam:
      config:
        - subnet: 10.201.7.0/24

그 범위는 검색하다 찾은 예제가 아니라 팀이 관리하는 등록부에서 가져오세요. VPN이 밀어 넣는 프리픽스, 사람들이 일하는 집 LAN, 같은 호스트의 다른 컨테이너 네트워크와 대조하세요. 그다음 재생성을 계획하세요. 기존 네트워크는 옛 서브넷을 유지하기 때문입니다: docker compose down, 그다음 up -d. docker network rm 는 엔드포인트가 붙어 있는 동안 거부하고, 파일의 어떤 ipv4_address 항목이든 새 범위 안에 들어와야 합니다.

Docker가 스스로 만드는 네트워크라면 데몬 풀을 /etc/docker/daemon.json:

{
  "default-address-pools": [
    { "base": "10.201.0.0/16", "size": 24 },
    { "base": "10.202.0.0/16", "size": 24 }
  ]
}

풀은 데몬이 재시작된 뒤 만들어지는 네트워크에 적용되고, 이미 있는 브리지는 주소를 그대로 유지합니다. 편리하고 잊기 쉬우므로, 누군가 그 네트워크를 다시 만들기 전까지 옛 프로젝트는 여전히 겹친다고 보세요. Docker의 엔진 네트워킹 문서 가 할당 규칙을 설명하니, 리뷰에서 풀을 변호해야 할 때 참고하세요.

Linux 브리지와 Docker Desktop은 다르게 실패합니다

Linux 호스트에서 컨테이너는 호스트 네임스페이스의 브리지에 있습니다. 테이블 하나가 모든 것을 정하므로 위 비교가 전부입니다. 브리지 경로를 읽고, 터널 경로를 읽고, 실제 컨테이너 주소에 대해 어느 쪽이 이기는지 보세요.

Docker Desktop은 컨테이너를 자체 NAT를 가진 VM 뒤에 둡니다. 호스트에는 컨테이너 주소로 가는 경로가 아예 없고, 보통 127.0.0.1 에 바인딩된 게시 포트로 서비스에 도달합니다. 그래서 VPN 클라이언트는 서브넷 겹침 없이도 서비스를 깨뜨릴 수 있습니다. VM의 내부 범위에 경로를 설치하거나, 터널이 연결된 동안 loopback 트래픽을 막으면 됩니다. 호스트에서 ip route get 가 컨테이너 주소에 대한 경로를 보고하지 않는다면 그것은 버그가 아니라 예상된 결과입니다.

실질적인 결론은 어디를 먼저 보느냐입니다. Linux에서는 라우팅 테이블에서 시작하세요. Desktop에서는 게시 포트와 VPN 클라이언트의 방화벽 규칙에서 시작하고, 그다음 VM의 내부 서브넷이 터널이 밀어 넣는 것과 충돌하는지 확인하세요.

터널을 올린 상태로 검증하세요

고쳤다고 말하기 전에 VPN을 두 번 다시 연결하세요. 연결할 때 경로를 밀어 넣고 끊을 때 회수하는 프로필은 첫 시도 뒤에는 멀쩡해 보일 수 있습니다. 그다음 클라이언트가 실제로 하는 점검을 실행하세요:

  1. ip route get 호스트에서, 컨테이너의 실제 주소를 사용해 실행합니다.
  2. 다른 컨테이너에서 보내는 요청으로 서비스 자체가 건강한지 증명합니다.
  3. curl -sv http://127.0.0.1:8080/ 터널을 올린 상태로 클라이언트에서, 그다음 traceroute -n LAN의 머신에서 컨테이너 주소로 보냅니다.
  4. sysctl net.ipv4.conf.all.rp_filter 요청이 호스트에 도달하는데 응답이 사라진다면 실행하세요.

마지막으로 고른 범위를 그것을 고를 때 쓴 같은 등록부에 날짜와 그 범위를 소유한 프로젝트와 함께 적으세요. 다음 겹침은 아무도 문서화하지 않은 프로젝트에서 옵니다. 유일한 방어는 범위를 무작위로 뽑지 않고 의도적으로 예약했다는 사실입니다.

관련 글