Docker 컨테이너가 VPN의 DNS 리졸버를 무시하는 이유
컨테이너는 시작할 때 호스트의 리졸버를 복사하므로, 나중에 DNS를 다시 쓰는 VPN은 그 안에서 보이지 않습니다. 리졸버를 명시적으로 전달하세요.
VPN은 켜져 있고 브라우저는 내부 사이트에 접속하는데, 컨테이너는 같은 이름을 여전히 해석하지 못합니다. 패키지 설치가 EAI_AGAIN 같은 이름 해석 오류로 죽거나, 애플리케이션이 호스트 머신이 아무 불평 없이 해석하는 데이터베이스 호스트를 찾지 못합니다. 컨테이너는 VPN이 설치한 리졸버를 쓰지 않습니다. Docker는 컨테이너를 만들 때 호스트의 리졸버 구성을 복사했고, 그 뒤로 아무것도 다시 복사하지 않았습니다.
한 머신에 있는 리졸버의 두 가지 모습
Linux에서 Docker는 컨테이너를 만들 때 호스트의 리졸버 구성의 사본을 각 컨테이너에 주고, 루프백 주소를 걸러내며, 127.0.0.11에서 쿼리를 밖으로 전달하는 내장 DNS 서버를 실행합니다. 컨테이너가 시작된 뒤 /etc/resolv.conf를 다시 쓰는 VPN 클라이언트는 그 안의 무엇도 바꾸지 않습니다. 컨테이너를 다시 시작하는 것은 그 시점에 호스트 리졸버가 우연히 올바를 때만 도움이 됩니다.
macOS나 Windows의 Docker Desktop은 계층을 하나 더합니다. 데몬이 가상 머신 안에서 살기 때문입니다. 호스트의 VPN 리졸버가 그 VM에 보이지 않을 수 있으므로 다시 시작해도 아무것도 바뀌지 않습니다. 해결책을 고르기 전에 두 모습을 비교하세요. 호스트의 리졸버 목록 옆에서 docker exec mycontainer cat /etc/resolv.conf를 실행합니다.
호스트가 내부 리졸버를 나열하는데 컨테이너가 공용 리졸버를 나열하면, 공용 이름은 해석되고 내부 이름에서는 실패합니다. 이 분기가 이 문제의 특징입니다.
옵션 A: 컨테이너마다 터널 리졸버를 전달
Docker는 컨테이너별 DNS 설정을 받아들이며, 이것이 동작하는 가장 작은 변경입니다. 먼저 VPN의 리졸버 주소를 읽으세요. macOS에서는 scutil --dns, Linux에서는 resolvectl status 또는 클라이언트 자체의 상태 출력입니다. 그다음 그 주소로 컨테이너를 시작합니다. 예를 들어 docker run --dns 192.168.2.1 --dns 192.168.123.1 myimage입니다. Compose에서는 같은 설정이 서비스 아래의 목록이 되고, 누군가의 셸 기록 대신 버전 관리에 남습니다.
/etc/docker/daemon.json의 dns 배열은 모든 새 컨테이너에 적용되며, 더 깔끔해 보이지만 그렇지 않습니다. 터널이 내려가 있을 때도 적용되고, 그러면 VPN으로만 닿는 리졸버가 평범한 이미지 내려받기를 망칩니다. 컨테이너별 설정이 설명하기도 되돌리기도 쉽습니다.
옵션 B: 컨테이너가 호스트 네트워크를 공유하게 하기
호스트 네트워크는 사본을 완전히 우회합니다. 컨테이너가 호스트의 리졸버와 호스트의 터널을 쓰기 때문입니다. 다만 원했을 가능성이 큰 네트워크 격리를 없애고, Docker Desktop에서는 같은 방식으로 동작하지 않습니다. Linux에서는 내부 이름을 해석해야 하는 일회성 도구에 현실적인 선택입니다.
빌드에는 두 옵션 모두 적용되지 않습니다. 빌드 단계는 런타임 플래그를 상속하지 않기 때문입니다. 플랫폼이 허용하는 곳에서는 호스트 네트워크로 빌드하거나, 의존성 설치를 터널이 필요 없는 단계에 두세요.
두 옵션 모두 고치지 못하는 것
컨테이너에 경로가 없으면 DNS 주소는 쓸모가 없습니다. VPN 리졸버는 보통 터널의 주소 공간 안에 있으므로, 컨테이너는 그 프리픽스로 가는 경로가 필요합니다. Docker의 기본 브리지는 보통 호스트를 통해 밖으로 라우팅하며, 호스트에 터널 경로가 있고 포워딩이 켜져 있으면 그걸로 충분합니다. 자체 주소 풀을 가진 사용자 정의 브리지 네트워크는 VPN 서브넷과 충돌할 수 있고, 그러면 쿼리는 전혀 다른 곳으로 갑니다.
리졸버 자체를 디버깅하기 전에 Docker 네트워크의 서브넷을 VPN 서브넷과 맞춰 보세요. 겹치면 고장 난 DNS 서버와 똑같이 보이는 장애가 생깁니다.
컨테이너 안에서 확인
브라우저나 언어 런타임 대신 컨테이너 안에서 getent hosts db.internal로 시험하세요. 그것들은 자체 DNS 정책과 자체 캐시를 적용하기 때문입니다. getent이 이름을 해석하는데 애플리케이션이 여전히 실패한다면 리졸버는 이제 올바르고, 남은 문제는 애플리케이션 계층입니다. 해석하지 못한다면 DNS 단계는 끝나지 않은 것입니다.
DNS 설정을 바꾼 뒤 컨테이너를 다시 만드세요. 실행 중인 컨테이너 안에서 /etc/resolv.conf을 편집하면 컨테이너와 함께 사라지고, 누군가 다시 시작하는 시간만큼 구성 문제를 숨깁니다.