이 글은 Docker의 대표 네트워크 모드(none, host, bridge)를 정리한 뒤, “Docker가 실제로 리눅스에서 무엇을 만들고(bridge, veth, netns), 어떤 규칙(iptables NAT)으로 포트를 열어주는지”를 손으로 확인하는 형태로 구성했습니다.
앞에서 다뤘던 network namespace 실습(veth, bridge, routing, NAT, port-forward) 과 자연스럽게 이어지도록, 헷갈리기 쉬운 포인트(ARP/route, /24의 의미, master docker0가 뭘 하는지, 왜 NAT가 필요한지)도 중간중간 설명을 보강했습니다.
추가로, 많은 분들이 헷갈려하는 “그냥 띄운 nginx(컨테이너 내부 80)” vs “-p 8080:80으로 띄운 nginx(호스트 공개)” 차이를 별도 섹션으로 보강했습니다.
1) Docker 컨테이너는 “네트워크 옵션”을 고를 수 있다
호스트(도커가 설치된 리눅스 서버)가 있고, 호스트에는 보통 외부 LAN/VPC로 연결된 인터페이스 eth0가 있습니다. 예시로:
- Host
eth0:192.168.1.1
컨테이너를 실행할 때 Docker는 네트워킹 모드를 선택하게 해줍니다.
1.1 none 네트워크
- 컨테이너가 어떤 네트워크에도 붙지 않음
- 컨테이너 → 외부로 나갈 수 없음
- 외부 → 컨테이너로 들어올 수 없음
- 컨테이너끼리도 통신 불가(같은 네트워크가 아니니까)
실행 예:
docker run --rm --network none alpine ip addr
1.2 host 네트워크
- 컨테이너가 호스트의 네트워크 스택을 그대로 공유
- “네트워크 격리”가 거의 없음
- 컨테이너에서
80포트로 앱을 띄우면 → 호스트의80포트로 바로 열림(-p필요 없음)
실행 예:
docker run --rm --network host nginx
# 이제 호스트에서 바로:
curl -I http://127.0.0.1:80
중요한 제약
- 같은 포트(예: 80)를 쓰는 컨테이너를 두 개 못 올립니다.
- 같은 네트워크(호스트)에서 두 프로세스가 동일 포트 listen 불가
1.3 bridge 네트워크(가장 핵심)
- Docker가 호스트 내부에 사설망을 만들고
- 컨테이너는 그 사설망에 붙어서 IP를 할당받음(기본:
172.17.0.0/16) - 컨테이너끼리는 같은 사설망에서 통신 가능
- 외부에서 접근하려면
-p(포트 퍼블리싱)가 필요
이 글의 나머지는 bridge 모드가 리눅스에서 어떻게 구현되는지를 파고듭니다.
2) bridge 모드의 정체: docker0 = Linux bridge(가상 스위치)
Docker를 설치하면 기본 bridge 네트워크가 하나 생성됩니다.
docker network ls
Docker는 네트워크를 이름으로 보여주지만, 리눅스 호스트에서는 보통 docker0라는 브리지 인터페이스로 보입니다.
ip link show docker0
ip addr show docker0
대개 다음처럼 나옵니다.
docker0는 bridge 타입 인터페이스docker0에 IP가 하나 붙어 있음: 보통172.17.0.1/16
여기서 포인트:
- 브리지는 호스트 입장에선 “인터페이스”
- 컨테이너 입장에선 “스위치”
앞서 netns 실습에서 만들었던 vnet0와 완전히 같은 역할입니다.
3) 컨테이너 하나 만들면 Docker가 하는 일 (netns + veth + bridge)
컨테이너를 하나 띄워봅니다(nginx 예시).
docker run -d --name web nginx
Docker가 bridge 모드로 컨테이너를 붙일 때 내부적으로 하는 작업을 한 줄로 요약하면:
- 컨테이너용 네트워크 네임스페이스(netns) 생성
- veth pair(가상 케이블) 생성
- veth 한쪽 끝을 컨테이너 netns로 이동
- veth 다른 한쪽 끝을
docker0(브리지)에 포트로 연결 - 컨테이너 쪽 veth에 IP 할당(예:
172.17.0.3/16) + default route 설정
3.1 컨테이너의 netns 확인 방법(가장 확실한 방법)
Docker가 만든 netns를 “ip netns list”로 바로 보기 어렵다는 얘기가 나오는데, 실무/실습에선 아래가 가장 확실합니다.
PID=$(docker inspect -f '{{.State.Pid}}' web)
sudo nsenter -t "$PID" -n ip link
sudo nsenter -t "$PID" -n ip addr
sudo nsenter -t "$PID" -n ip route
여기서 보통 이런 걸 보게 됩니다.
- 컨테이너 안에는
lo,eth0가 보임- 중요: 컨테이너의
eth0는 호스트의eth0가 아닙니다. - 컨테이너 netns 안에 새로 만들어진 veth가
eth0라는 이름으로 보이는 것입니다.
- 중요: 컨테이너의
3.2 호스트에서 veth와 bridge 연결 확인
호스트에서 보면 docker0에 여러 포트(veth)가 붙어 있습니다.
bridge link | grep docker0
# 또는
ip link show master docker0
여기서 나오는 veth들이 “컨테이너로 들어가는 케이블의 호스트쪽 끝”입니다.
ip link set veth-xxx master docker0가 하는 일 (핵심 보강)
master docker0는 “이 인터페이스를 docker0 브리지의 포트로 등록”한다는 뜻입니다.- 브리지는 스위치고, veth는 스위치에 꽂는 케이블입니다.
4) veth로 붙이면 “라우팅이 되는 거 아니야?” → 아니다. ARP가 먼저다.
4.1 veth는 라우터가 아니라 “L2 케이블”
veth는 경로를 선택하지 않습니다(라우팅 X).
그냥 프레임을 반대편으로 전달해주는 가상 케이블입니다.
4.2 그럼 컨테이너끼리 통신은 왜 되나?
컨테이너들은 같은 브리지 네트워크(기본 172.17.0.0/16)에 붙어 있고, 같은 서브넷이기 때문입니다.
컨테이너 A(예: 172.17.0.3)가 컨테이너 B(예: 172.17.0.4)로 보낼 때 흐름:
- Route table(L3): “목적지가 내 서브넷(172.17.0.0/16) 안이네 → 직접 전송”
- ARP/Neighbor(L2): “172.17.0.4의 MAC이 뭐지?” → ARP 요청/응답
- MAC을 알게 되면 브리지(docker0)가 스위칭해서 상대 veth로 전달
컨테이너 안에서 확인:
sudo nsenter -t "$PID" -n ip neigh
sudo nsenter -t "$PID" -n ip route
요약: route가 “어디로 보낼지” 결정하고, ARP가 “MAC이 뭔지”를 해결합니다.
5) 왜 컨테이너 IP는 보통 /16 같은 큰 프리픽스를 쓰나? (/32가 아니라)
- 같은 브리지 네트워크에 있는 컨테이너끼리 “게이트웨이 없이 직접 통신”시키려면 “같은 서브넷”이어야 합니다.
/32면 “나 자신 IP만” 의미해서 connected route가 성립하지 않고, ARP 기반 직접 통신이 매우 불편해집니다.- Docker는 컨테이너 네트워크를 내부 L2 도메인처럼 쓰기 때문에 기본이 넓은 대역(
/16)인 경우가 많습니다.
6) (추가 보강) “그냥 띄운 nginx(컨테이너 내부 80)” vs “-p 8080:80으로 띄운 nginx” 차이
여기가 제일 헷갈리는 부분이라, 외부 요청이 어디로 가는지를 기준으로 정리합니다.
6.1 케이스 A: 그냥 띄움 (docker run nginx)
docker run -d --name web nginx
이때 nginx는 컨테이너 네트워크 네임스페이스 안에서 :80으로 listen합니다.
즉, “80으로 뜬다”는 말은 컨테이너 내부에서 80이 열린 것이지, 호스트에 80이 열린 게 아닙니다.
외부에서 http://<HOST_IP>:80으로 접속하면?
- 호스트 커널은 “내 IP()로 들어온 80이네”라고 보고,
- 호스트 네임스페이스에서
:80을 listen하는 프로세스를 찾습니다.- 있으면 → 그 호스트 프로세스로 전달
- 없으면 → 보통
connection refused(RST)또는 방화벽이면 timeout
결론:
-p가 없으면 외부에서 온 트래픽을 컨테이너로 보내는 매핑(DNAT)이 없기 때문에 컨테이너 nginx로 안 갑니다.
다만 호스트 자체에서는 컨테이너 IP로 접근 가능할 때가 많습니다.
CIP=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' web)
curl -I http://$CIP:80
이건 “외부 → 호스트IP”가 아니라 “호스트 → docker0를 통해 컨테이너IP”라서 가능한 경로입니다.
6.2 케이스 B: 8080:80으로 띄움 (docker run -p 8080:80 nginx)

docker run -d --name web -p 8080:80 nginx
이 경우 외부에서 http://<HOST_IP>:8080으로 접속하면 Docker가 만든 규칙이 동작합니다.
- 호스트 :8080으로 들어온 요청을
- 컨테이너IP :80으로 바꿔서(DNAT) 전달
이 “바꿔치기/포워딩”을 수행하는 주체가 보통 iptables NAT 규칙입니다(환경에 따라 nftables일 수도 있지만 개념은 동일).
결론:
-p 8080:80은 “호스트 8080을 열고”, “그 트래픽을 컨테이너 80으로 넘겨준다”는 의미입니다.
컨테이너는 여전히 내부에서:80만 listen하고, 외부 공개는 호스트 쪽 포트가 담당합니다.
7) 컨테이너의 웹(80)을 외부에 공개: -p 8080:80의 정체는 iptables DNAT(포트포워딩)
컨테이너 web(nginx)는 내부에서 80을 listen합니다.
- 같은 docker0 네트워크의 다른 컨테이너/호스트는
http://컨테이너IP:80으로 접근 가능 - 하지만 외부에서 컨테이너 사설 IP(172.17.x.x)는 원칙적으로 접근이 안 됩니다.
그래서 Docker는 포트 퍼블리싱 옵션을 제공합니다.
docker run -d --name web -p 8080:80 nginx
이제 외부/호스트에서:
curl -I http://<HOST_IP>:8080
7.1 Docker는 이걸 어떻게 구현하나?
- “서버로 들어오는 8080 포트를 172.17.0.3:80으로 포워딩해라”
- 이건 전형적인 iptables NAT(PREROUTING) + DNAT 입니다.
Docker는 iptables에 rule을 추가합니다(대개 DOCKER 체인).
확인:
sudo iptables -t nat -L -n -v
sudo iptables -t nat -S | grep -E 'DOCKER|8080|DNAT'
8) 컨테이너가 인터넷으로 나가는 건 왜 되나? (ip_forward + MASQUERADE)
bridge 모드 컨테이너는 사설망(172.17.0.0/16)입니다. 외부는 이 네트워크를 모릅니다.
그런데 대부분의 Docker 환경에서 컨테이너는 인터넷으로 나갑니다.
그 이유는:
- 호스트가 IP forwarding을 켜서(라우터처럼) docker0 ↔ eth0 사이를 포워딩하고
- 외부가 172.17.0.0/16을 모르므로 SNAT(MASQUERADE) 로 출발지 주소를 호스트 IP로 바꿔서 나가기 때문입니다.
확인:
sysctl net.ipv4.ip_forward
sudo iptables -t nat -S | grep -E 'MASQUERADE|POSTROUTING'
9) 한 눈에 보는 구조(bridge 모드)
[Internet/LAN]
|
(eth0) 192.168.1.1 <-- Host
|
ip_forward + NAT(MASQUERADE)
|
(docker0 bridge) 172.17.0.1/16
| |
veth veth
| |
[netns] [netns]
web api
eth0 eth0
172.17.0.3 172.17.0.4
- 컨테이너 eth0 = netns 안의 veth 끝
- docker0 = 리눅스 브리지(스위치)
-p= iptables DNAT(포트포워딩)- egress = iptables MASQUERADE(SNAT)
10) 실습 체크리스트 (핵심 명령 모음)
컨테이너/호스트에서 네트워크 구조를 확실히 “눈으로 확인”하려면 아래만 보면 됩니다.
컨테이너 netns 들어가기
PID=$(docker inspect -f '{{.State.Pid}}' web)
sudo nsenter -t "$PID" -n ip addr
sudo nsenter -t "$PID" -n ip route
sudo nsenter -t "$PID" -n ip neigh
호스트에서 bridge/veth 확인
ip link show docker0
ip addr show docker0
bridge link | grep docker0
ip link show master docker0
포트퍼블리시/ NAT 룰 확인
sudo iptables -t nat -L -n -v
sudo iptables -t nat -S | grep -E 'DOCKER|DNAT|MASQUERADE'
sysctl net.ipv4.ip_forward
11) Kubernetes(CNI)로 연결되는 포인트
Docker bridge 모드에서 본 핵심(=netns 생성, veth pair, bridge 연결, iptables NAT/port-forward)은 Kubernetes에서도 형태만 바뀌어 반복됩니다.
- 컨테이너 런타임이 netns를 만들고
- CNI 플러그인이 veth를 만들고/붙이고/IP를 주고/라우팅을 잡고
- Service/NodePort/LoadBalancer가 iptables/ipvs 규칙을 활용해 트래픽을 분산/포워딩
다음으로 넘어가면 “Docker가 iptables를 만지듯이”, Kubernetes에서는 CNI/Service가 어떻게 규칙을 만들어내는지가 자연스럽게 이어집니다.
요약
none: 네트워크 없음(격리 극대화)host: 호스트 네트워크 공유(성능/단순, 포트 충돌/격리 약함)bridge: docker0(bridge) + netns + veth로 내부 사설망 구성- 컨테이너 통신은 route 결정 + ARP(neigh)로 MAC 해석 위에서 동작
docker run nginx(그냥 띄움): 컨테이너:80은 컨테이너 내부에만, 외부HOST:80과 연결 없음-p 8080:80: 호스트 :8080 → 컨테이너 :80으로 DNAT(포트포워딩) 자동 생성- 컨테이너 egress는 ip_forward + MASQUERADE(SNAT) 로 성립
'CKA' 카테고리의 다른 글
| Network - 정리(1) (1) | 2026.01.05 |
|---|---|
| Network - CNI(Container Network Interface) (0) | 2026.01.05 |
| Network - 네트워크 기초(veth, bridge, routing, NAT, port-forward) (1) | 2026.01.05 |
| Network - 네트워크 기초(DNS) (0) | 2026.01.05 |
| Network - 네트워크 기초(스위치, 라우터, 게이트웨이) (0) | 2026.01.05 |