NAT를 꺼도 컨테이너가 안 열리는 이유 — Docker 브리지 routed 모드의 숨은 잠금

seonwooj0810·3일 전

1. 도입

docker network create로 브리지 네트워크를 routed 모드로 만들고, 라우터에 그 컨테이너 서브넷으로 가는 정적 경로까지 넣었다고 하자. NAT를 껐으니 이제 외부 호스트에서 컨테이너 IP로 바로 붙을 수 있을까? 실제로는 대부분 여기서 한 번 더 막힌다. Docker Engine 공식 문서를 보면 "라우팅이 가능하다"와 "실제로 뚫려 있다"는 서로 다른 조건이고, 그 사이에 opt-in 스위치가 하나 더 있다.

2. 핵심 개념

Docker 브리지 네트워크의 외부 접근성은 NAT 온/오프라는 단일 스위치가 아니라 두 개의 독립적인 축으로 결정된다. 하나는 마스커레이딩/DNAT 축(나가는 패킷의 소스 주소를 바꾸는지, 들어오는 패킷을 컨테이너로 포워딩하는지), 다른 하나는 direct-routing 허용 축(라우팅이 가능한 패킷을 실제로 통과시킬지)이다. 브리지 드라이버의 com.docker.network.bridge.gateway_mode_ipv4/_ipv6 옵션이 첫 번째 축을 4가지(nat/nat-unprotected/routed/isolated)로 결정하고, 두 번째 축은 별도의 opt-in 플래그가 결정한다.

3. 내부 동작

기본값인 nat 모드에서는 나가는 패킷이 항상 마스커레이딩되고, 들어오는 패킷은 -p로 게시한 포트만 DNAT/PAT 대상이 된다. 네 모드를 비교하면 아래와 같다.

모드NAT/masquerade발신 소스주소외부 도달 조건
nat (기본)있음호스트 주소로 치환호스트 게시 주소로만
nat-unprotected있음호스트 주소로 치환호스트 주소 + 필터 생략
routed없음컨테이너 자신 주소라우팅 경로 + direct-routing 허용 필요
isolated--브리지에 주소 자체 없음(--internal)

routed가 없애는 건 마스커레이딩/DNAT 규칙뿐이다. 나가는 패킷은 컨테이너 자신의 IP를 그대로 소스로 쓰고, 게시된 포트만 통과시키는 필터 규칙은 여전히 살아 있다. 그런데 라우팅 정보가 있어도 Docker는 한 번 더 잠금을 건다. opt-in 경로는 두 가지다.

  1. 데몬 전역 --allow-direct-routing (daemon.jsonallow-direct-routing: true) — 모든 브리지 네트워크에서 게시된 포트에 대한 direct routing 허용.
  2. 네트워크별 com.docker.network.bridge.trusted_host_interfaces — 지정한 인터페이스(예: eth3)로부터만 그 네트워크로의 direct routing 허용.

공식 문서가 이 이중 잠금의 이유를 명시하진 않지만, 라우팅 테이블 항목 하나가 실수로든 의도적으로든 배포됐을 때 컨테이너가 곧바로 노출되지 않도록 하는 방어선으로 읽힌다 — 라우팅 가능성과 실제 통과 허용을 분리해 둔 셈이다. 같은 nat 테이블 기반 DNAT 메커니즘은 kube-proxy의 iptables 모드 ClusterIP 라우팅에서도 conntrack과 함께 등장한다.

4. 예시 / 실패 케이스

# routed 모드 네트워크 생성 + 정적 라우트는 있다고 가정
docker network create --subnet 203.0.113.0/24 \
  -o com.docker.network.bridge.gateway_mode_ipv4=routed routednet
docker run -d --network routednet -p 8081:80 --name c-routed nginx

# 실제 opt-in 여부 확인
docker network inspect routednet -f '{{json .Options}}'
sudo iptables -t nat -L -n -v   # routednet 관련 규칙이 없어야 정상

라우팅 O, trusted_host_interfaces/allow-direct-routing X인 상태라면 패킷은 커널 라우팅 테이블을 타고 호스트까지 도착하지만, Docker가 만든 필터 규칙에서 걸러진다 — iptables -t nat -L에는 아무것도 안 보이니 "NAT 문제는 아닌데" 싶어지고, 정작 막고 있는 건 filter 테이블 쪽 규칙이라 원인을 한 단계 더 내려가서 봐야 한다.

5. 정리

NAT가 꺼진 것과 실제로 뚫려 있는 것은 다른 이야기다 — routed 모드는 첫 번째 잠금(마스커레이딩)만 풀 뿐, 두 번째 잠금(direct-routing opt-in)은 별도로 열어야 한다. 다음에 더 볼 만한 것은 iptables 대신 nftables 백엔드를 쓸 때 이 필터 규칙 구조가 어떻게 달라지는지, 그리고 단일 호스트 브리지 모델이 Swarm overlay(VXLAN) 같은 멀티호스트 네트워크와 어느 지점부터 갈라지는지다.

참고 자료

  • Docker Engine Docs — Networking overview (/engine/network/)
  • Docker Engine Docs — Port publishing and mapping (/engine/network/port-publishing/)
  • Docker Engine Docs — Bridge network driver, Gateway modes (/engine/network/drivers/bridge/)

0개의 댓글