← all posts
DEV 2026.05.02 · 11 min read Intermediate

Docker 네트워크는 어떻게 패킷을 옮기는가

veth pair와 bridge 생성부터 iptables NAT, Overlay VXLAN, 네트워크 보안 정책까지 — Docker 컨테이너 네트워킹의 전체 흐름을 추적한다.


Docker는 컨테이너가 서로 통신하게 만든다. 그런데 그 “통신”이 어떻게 이뤄지는지 제대로 아는 사람은 많지 않다. 컨테이너 하나가 외부 인터넷에 패킷을 보낼 때, 그 패킷은 정확히 어떤 경로를 거치는가?

모든 것의 출발점: veth pair와 bridge

Linux는 컨테이너마다 독립된 Network Namespace를 부여한다. 각 네임스페이스는 자체 인터페이스, IP, 라우팅 테이블을 갖는다. 문제는 격리된 네임스페이스끼리 어떻게 연결하느냐다.

답은 veth pair다. 가상 네트워크 케이블 양쪽 끝으로, 한쪽은 컨테이너 네임스페이스 안에(eth0), 다른 쪽은 호스트에 남는다. 한쪽으로 들어간 패킷은 반드시 다른 쪽으로 나온다.

여러 컨테이너를 연결하려면 bridge가 필요하다. Docker는 docker0라는 가상 스위치를 생성하고, 모든 컨테이너의 veth 호스트 쪽 끝을 여기에 붙인다. Layer 2 MAC 주소 기반으로 패킷을 전달하는 가상 스위치다.

Container A (eth0, 172.17.0.2)
    │ veth-a
    ├───────────────┐
    │               ▼
    │          docker0 (172.17.0.1)
    │               │
    └───────────────┤
    │ veth-b         │
Container B (eth0, 172.17.0.3)

컨테이너 간 통신은 이 구조 안에서 완결된다. NAT도 없고, 외부도 거치지 않는다.

iptables가 패킷을 인터넷으로 내보내는 방법

컨테이너가 외부(예: 8.8.8.8)에 패킷을 보내면 경로가 달라진다.

Container (172.17.0.2) → docker0 → FORWARD 체인 → POSTROUTING → eth0 → Internet

POSTROUTING 체인에는 Docker가 자동으로 추가한 MASQUERADE 규칙이 있다.

# iptables NAT 테이블 확인
sudo iptables -t nat -L POSTROUTING -n -v
# MASQUERADE  all  --  172.17.0.0/16  0.0.0.0/0

MASQUERADE는 패킷의 출발지 IP를 호스트 eth0 IP로 바꾼다. 인터넷에서 보면 이 패킷이 컨테이너에서 온 것인지 알 수 없다. 응답 패킷은 conntrack이 추적해서 원래 컨테이너로 되돌린다.

포트 포워딩(-p 8080:80)은 반대 방향이다. PREROUTING 체인에 DNAT 규칙이 추가되고, 외부에서 들어온 :8080 패킷의 목적지를 172.17.x.x:80으로 변환한다.

트레이드오프

Bridge + NAT 방식은 설정이 단순하고 이식성이 높지만, NAT 오버헤드가 있다. 이를 피하려면 Host 네트워크 모드(NAT 없음, 성능 +20%)나 Macvlan(컨테이너가 물리 네트워크 IP 직접 보유)을 쓸 수 있다. 대신 Host 모드는 포트 충돌과 격리 부재라는 비용이 따른다.

멀티 호스트: VXLAN이 네트워크를 확장하는 방식

단일 호스트에서는 bridge로 충분하다. 하지만 컨테이너가 여러 물리 서버에 흩어지면 Layer 2 브로드캐스트 도메인을 어떻게 연결하는가?

Docker Swarm의 Overlay 네트워크VXLAN(Virtual Extensible LAN)으로 이 문제를 푼다. 컨테이너 간 패킷을 UDP 4789 포트 안에 캡슐화해서 물리 네트워크 위로 터널링한다.

[Inner: Container IP Header] [Data]
        ↓ VXLAN 캡슐화
[Outer: Host IP Header] [UDP:4789] [VXLAN VNI] [Inner Ethernet] [Inner IP] [Data]

Host 2의 커널은 UDP 패킷을 받아 VXLAN 헤더를 제거하고, 내부 패킷을 목적 컨테이너에 전달한다. 컨테이너 입장에서는 같은 호스트에 있는 것과 다름없다.

캡슐화 오버헤드는 약 50바이트, 성능 저하는 5~10% 수준이다. 민감한 데이터라면 --opt encrypted 옵션으로 IPSec을 추가할 수 있다.

DNS가 IP를 대신하는 이유

사용자 정의 네트워크를 생성하면 Docker는 컨테이너마다 127.0.0.11:53에 내장 DNS 서버를 연결한다. 컨테이너 이름이 자동으로 등록된다.

docker exec app1 nslookup db
# Server:   127.0.0.11
# Name:     db
# Address:  172.18.0.3

같은 별칭(--network-alias)을 공유하는 컨테이너가 여러 개면 DNS는 모든 IP를 반환한다 — 단순한 라운드 로빈 로드 밸런싱이 된다. IP를 하드코딩하지 않아도 되고, 컨테이너가 교체되면 DNS 레코드만 갱신된다.

기본 bridge 네트워크(docker0)에는 이 DNS가 없다. 이름 기반 통신이 필요하면 반드시 사용자 정의 네트워크를 써야 한다.

보안: 기본값은 “모두 연결”이다

Docker 네트워크의 기본 상태는 같은 네트워크 안의 컨테이너가 서로 자유롭게 통신할 수 있다는 것이다. Kubernetes에서 Calico나 Cilium 같은 CNI 플러그인을 쓰더라도, 명시적인 NetworkPolicy가 없으면 마찬가지다.

# 기본 거부 — 이것부터 시작해야 한다
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-all
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  - Egress

이후 필요한 경로만 열어준다. frontend → api-gateway → backend → database 경로에서 frontend → database 직접 연결은 차단된다.

정리

  • 컨테이너 네트워크는 veth pair + bridge로 시작한다. 같은 bridge 안의 컨테이너는 Layer 2로 직접 통신한다.
  • 외부 통신은 iptables MASQUERADE가 담당한다. 포트 포워딩은 DNAT, 컨테이너→인터넷은 SNAT이다.
  • 멀티 호스트는 VXLAN Overlay로 해결한다. 물리 네트워크 위에 가상 Layer 2를 구성한다.
  • 내장 DNS(127.0.0.11)는 IP 대신 이름으로 서비스를 찾게 해준다. 기본 bridge에는 없다.
  • 보안의 출발점은 기본 거부다. 열려 있는 것을 닫는 것보다 닫혀 있는 것을 여는 것이 안전하다.

다음 글에서는 Docker 볼륨의 내부 구조와 overlay2 스토리지 드라이버가 레이어를 어떻게 관리하는지 추적한다.