🐳 도커 네트워크를 왜 공부해야 할까?
도커를 막 배우기 시작하면 docker run만 알아도 컨테이너를 띄울 수 있어요. 단일 컨테이너로 간단한 실습을 할 땐 이걸로도 충분하죠.
하지만 실제 서비스를 운영하려고 하면 곧 이런 의문들이 생기기 시작해요:
"백엔드 컨테이너가 DB에 연결하려면?"
“외부 사용자가 내 서비스에 접속하려면 어떻게 하지?”
"컨테이너를 두 개 띄웠는데 어떻게 서로 통신하지?"
"컨테이너 여러 개에 부하를 나누려면?"
사실 이런 고민은 컨테이너를 하나만 실행할 때도 생겨요. 외부에서 접속하려면 포트를 열어야 하고, 외부 API나 DB에 연결하려면 DNS와 라우팅도 신경 써야 해요. 프록시나 보안 설정이 필요한 경우도 많고요. 결국 진짜 운영을 하려면 도커 네트워크를 반드시 이해해야 해요.
🐳 도커 네트워크는 어떻게 구성돼 있을까?
도커 네트워크는 단순히 도커만의 기술이 아니에요. 리눅스 커널이 제공하는 기능(Linux Networking)들 위에 도커가 구조를 덧붙여 만든 거예요. 주요 구성 요소를 하나씩 살펴보겠습니다.
🔹 Network Namespace – 격리된 네트워크 공간
리눅스 커널은 namespace라는 기능을 통해, 컨테이너 하나하나가 독립된 공간처럼 동작하도록 만들어줘요. 그중 network namespace는 컨테이너마다 자신만의 네트워크 공간을 만들어요. 즉, 각자 별도의 IP 주소, 라우팅 테이블, 네트워크 인터페이스를 가지게 되는 거죠.
그래서 각 컨테이너는 서로 완전히 격리되어 있고, 자기만의 eth0, lo 같은 인터페이스를 갖게 돼요.
💡ip netns list, ip netns exec 명령어로 리눅스에서도 네트워크 namespace를 확인할 수 있어요.
🔹 veth pair – 가상의 이더넷 인터페이스
veth(Virtual Ethernet)는 컨테이너와 호스트 네트워크를 연결하는 가상의 네트워크 케이블이에요.
- 한쪽은 컨테이너 내부의 eth0
- 다른 한쪽은 도커 호스트의 Linux Bridge(docker0)에 연결돼요.
이렇게 해서 컨테이너와 호스트 네트워크가 이어지는 통로가 생기는 거예요.
💡 docker exec <컨테이너> ip addr show eth0을 실행하면 eth0@ifX 같이 연결된 veth 인터페이스 정보를 볼 수 있어요.
🔹 Linux Bridge(docker0) – 컨테이너끼리 통신을 가능하게 하는 가상의 스위치
Linux Bridge는 리눅스에서 제공하는 가상의 네트워크 스위치예요. 도커는 기본적으로 docker0이라는 브리지를 자동으로 만들어두고, 실행되는 컨테이너들은 이 브리지에 연결돼요.
하지만 컨테이너가 직접 브리지에 연결되지 않고, 컨테이너 내부 eth0은 veth를 통해 브리지에 간접적으로 연결돼요.
[컨테이너 eth0] ↔ [vethA] ↔ [vethB] ↔ [Linux Bridge: docker0]
이렇게 브리지에 연결된 컨테이너들은 마치 같은 공유기에 연결된 장치들처럼 서로 자유롭게 통신할 수 있어요.
또한 브리지는 호스트의 실제 네트워크 인터페이스(예: eth0)와 iptables(NAT)를 통해 외부 네트워크와도 연결 가능해요. 그래서 브리지를 통해 컨테이너들은 서로 통신할 수 있을 뿐만 아니라, 외부 인터넷과도 통신할 수 있게 되는 거죠.
도커는 기본적으로 172.17.0.0/16이라는 프라이빗 IP 대역을 사용해 컨테이너에 IP를 부여해요. 사용자 정의 브리지를 만들면 192.168.0.0/20 또는 172.18.0.0/16 같은 범위를 자동으로 설정해 줘요.
💡 brctl show로 docker0 브리지에 연결된 veth 인터페이스들을 확인할 수 있어요.
🔹 iptables – 외부와의 연결을 제어하는 방화벽이자 라우터
컨테이너가 외부와 통신하려면, 단순히 브리지에 연결하는 것만으로 부족해요. 리눅스 커널은 '패킷이 어디에서 왔고, 어디로 가야 하는지'를 결정할 수단이 필요한데, 그걸 담당하는 게 바로 iptables예요.
iptable는 원래 리눅스의 방화벽 겸 트래픽 라우팅 도구로, 도커에서는 이를 활용해 다음 두 가지 주요 기능을 수행해요:
- DNAT (Destination NAT)
- 외부에서 특정 포트(예: 8080)로 들어오는 요청을, 실제 내부 컨테이너의 IP와 포트(예: 172.17.0.2:80)로 바꿔줘요.
- 예를 들어, localhost:8080 → 컨테이너_IP:80
localhost:8080으로 접속하면 커널은 이 요청을 iptables를 통해 다른 목적지 IP/포트로 전환(Destination Network Address Translation) 시켜요.
- SNAT (Source NAT 또는 MASQUERADE)
- 컨테이너에서 외부로 나갈 때 IP를 호스트의 IP로 바꿔줍니다.
이렇게 해서 컨테이너는 마치 호스트인 척하면서 외부와 통신할 수 있고, 외부에서 오는 요청도 마치 호스트에 도달한 것처럼 보이지만 실제로는 컨테이너로 연결돼요.
iptables -t nat -L -n
이 명령어로 도커가 자동으로 설정해 놓은 NAT(DNAT, SNAT) 규칙을 확인할 수 있어요.
🔹 CNM (Container Network Model) – 도커 네트워크의 내부 설계도
도커는 위의 모든 리눅스 네트워크 기능을 잘 추상화해서 관리하는 아키텍처를 만들었어요. 그게 바로 CNM(Container Network Model)이에요. 도커가 컨테이너 하나의 네트워크 환경을 만들고 네트워크에 연결하고 트래픽을 주고받는 모든 과정을 추상화한 구조예요.
CNM은 안에서 도커는 다음과 같은 단위로 네트워크를 조립해요:
구성 요소 | 설명 |
Sandbox | 컨테이너의 network namespace와 그 안의 인터페이스(eth0 등)를 포함하는 공간. 독립적인 네트워크 환경을 구성 |
Endpoint | veth의 한쪽처럼 네트워크와 연결되는 접점 |
Network | 여러 Endpoint가 붙는 논리적 네트워크 단위 (예: bridge network, overlay network) |
Driver | 실제 네트워크 기술을 사용하는 모듈 (bridge, host, overlay 등) |
💡 Overlay 네트워크란?
Overlay 네트워크는 여러 대의 서버(호스트)에 흩어져 있는 컨테이너들끼리 마치 한 대의 서버 안에 있는 것처럼 통신할 수 있도록 만들어주는 가상의 네트워크예요. 일반적으로 도커 Swarm이나 Kubernetes 같은 클러스터 환경에서 사용되고, 서버 간 통신은 VXLAN이라는 기술로 터널링 돼요.
도커는 이 구조를 바탕으로 아래와 같은 명령어 하나로 복잡한 네트워크 구성을 아주 쉽게 만들어줘요:
docker network create --driver bridge my-network
docker run --net=my-network ...
결국 CNM은 리눅스 네트워크 기술들을 도커 사용자에게 쉽고 일관되게 제공하기 위한 추상화 계층이에요. 도커는 네임스페이스 생성, veth 연결, 브리지 설정, iptables 구성까지 모든 걸 알아서 해주지만, 그 이면에는 이 구조가 작동하고 있는 거예요.
🐳 docker network 명령어
🔸 네트워크 목록 보기
docker network ls
🔸 네트워크 생성 및 주요 옵션
docker network create [OPTIONS] NETWORK_NAME
옵션 | 설명 |
--driver | 사용할 네트워크 드라이버 지정 - bridge: 컨테이너들 간의 통신이 가능하고, 호스트와도 통신 가능한 격리퇴 네트워크 - host: 격리 없이 호스트 네트워크 그대로 사용 - none: 네트워크에 접속하지 않음. 무지정(격리용) - overlay: Swarm 전용, 여러 호스트 간 네트워크 - macvlan: 실제 NIC처럼 동작하도록 만듦 (고급 사용) |
--subnet | 사용자 지정 서브넷 지정 (예: 192.168.100.0/24) |
--gateway | 네트워크의 게이트웨이 IP 지정. 이 주소는 컨테이너가 외부와 통신할 때 출입구 역할을 하는 IP. 일반적으로 서브넷의 첫 번째 IP를 사용 |
--ip-range | 컨테이너에 할당될 수 있는 IP 범위 제한 |
--internal | 외부 통신을 차단한 내부 네트워크로 설정 |
--attachable | 컨테이너가 docker run --network 옵션으로 네트워크에 동적으로 연결 가능하게 설정 |
--label | 네트워크에 메타 정보를 라벨로 추가 |
예시:
docker network create \
--driver=bridge \
--subnet=192.168.100.0/24 \
--gateway=192.168.100.1 \
my-network
- docker0 기본 브리지 말고, my-network라는 이름의 격리된 가상 네트워크가 하나 더 생깁니다.
- 여기에 컨테이너를 붙이면 자동으로 192.168.100.x 대역에서 IP를 받아요.
- 192.168.100.1은 게이트웨이로 지정되어 컨테이너가 외부랑 통신 가능합니다.
💡 언제 이렇게 사용할까?
1. 네트워크 충돌을 방지
기본 도커 네트워크는 172.17.x.x 등을 자동 할당해요. 이미 그 대역을 다른 네트워크에서 사용하고 있다면 충돌이 발생할 수 있어요. 직접 서브넷을 지정하면 이런 문제를 피할 수 있어요.
2. 예측 가능한 네트워크 구성
개발/운영 환경에서 동일한 IP구조를 유지하고 싶을 때 유리해요. IP 기반 방화벽, 프록시, 로깅 시스템 연동이 쉬워져요.
3. 멀티-컨테이너 시스템에서 네트워크 분리
예: DB와 백엔드만 통신 가능하게 하고 싶을 때 → 같은 네트워크에 묶어서 접근 가능하게 설정
4. 자동 DNS 지원
컨테이너 이름으로 통신할 수 있어어 IP 몰라도 되고, 환경마다 바뀌는 IP에 일일이 대응할 필요가 없어요.
5. 보안 격리
네트워크 단위로 서비스 그룹을 격리할 수 있어 보안성 향상과 관리 효율성을 얻을 수 있어요. (bridge, host, overlay 등)
예: 컨테이너 실행 시 네트워크 지정
docker run -d \
--name my-app \
--network my-network \
nginx:latest
이렇게 하면 nginx 컨테이너는 192.169.100.x 대역의 IP를 자동 할당받고, 게이트웨이 192.168.100.1을 통해 외부와 통신할 수 있어요.
🔸 네트워크 상세 정보 확인
docker network inspect my-network
해당 네트워크에 연결된 컨테이너, 각 컨테이너의 IP 정보, subnet, gateway 등을 볼 수 있어요.
🔸 네트워크 삭제
docker network rm my-network
연결된 컨테이너가 없을 때만 삭제할 수 있어요.
🔸 네트워크 추가 연결/분리하기
docker network connect|disconnect my-network my-app
도커에서는 컨테이너가 처음 실행될 때 하나의 네트워크만 연결되지만, 나중에 다른 네트워크를 추가로 연결하거나 분리할 수 있어요. 이 기능은 컨테이너가 복수의 네트워크에 연결되어 있어야 할 때, 특히 보안 구역을 나누거나 다양한 서비스와 독립적으로 통신할 때 유용하게 사용돼요.
💡 컨테이너를 처음 생성할 때 --network 옵션으로 하나의 네트워크만 지정할 수 있어요.
여러 네트워크에 연결하려면 docker network connect 명령어를 추가로 사용해야 해요.
+ 아니면 docker-compose.yml에서 networks를 여러 개 지정하는 방식으로 해야 해요.
예시:
frontend 컨테이너는 외부 요청을 받아야 하기에 public-net에 연결되어 있는 상태. 그런데 내부 서비스인 backend와도 통신해야 해서, private-net에도 연결되어야 하는 상황
docker network connect private-net frontend
기존에 frontend는 public-net에만 있었지만, 이제 private-net도 함께 연결돼서 두 네트워크를 모두 사용할 수 있게 돼요.
🐳 도커 DNS
도커는 사용자 정의 네트워크를 만들면 자동으로 내부 DNS 서버를 생성해줘요. 덕분에 컨테이너 이름만으로 통신이 가능해요.
- 컨테이너가 사용자 정의 네트워크에 연결되면, 도커 내부 DNS 서버가 컨테이너 이름을 자동으로 IP 매핑해 줘요.
- /etc/hosts 설정 없이도 동작하며, 컨테이너 이름이 DNS 도메인처럼 작동해요.
- 이 기능은 기본 bridge 네트워크에서는 동작하지 않고, 사용자 정의 네트워크에서만 가능해요.
ping my-app
curl http://my-app:3000
- 위 명령어는 my-app라는 이름을 가진 컨테이너의 IP를 자동으로 찾아 통신하는 예예요.
즉, 네트워크 생성하는 것은 단순한 IP 연결만 제공하는 게 아니라, DNS 기반 서비스 검색(Discovery) 기능까지 기본 내장돼 있는 강력한 구성 방식에요. 덕분에 컨테이너 이름만으로 통신이 가능해요.
🔸 --net-alias: 컨테이너에 또 다른 이름을 붙여주는 옵션
도커는 사용자 정의 네트워크를 만들면 컨테이너 이름으로 DNS 통신이 가능해져요. 즉, 컨테이너 이름만 알면 IP를 몰라도 아래처럼 바로 통신할 수 있죠.
curl http://my-app
그런데 실무에서는 때때로 컨테이너에 '또 다른 이름'을 붙이는 경우가 있어요. 바로 그럴 때 사용하는 옵션이 --net-alias예요. 아래와 같이 설명하면, 해당 컨테이너는 원래 이름 외에 backend라는 별칭으로도 통신할 수 있게 돼요.
docker run --net-alias backend ...
🤔 언제 alias가 필요할까?
"컨테이너에 이름(--name)을 주면 DNS로 접근이 가능한데 굳이 --net-alias가 왜 필요한 거지?"라는 의문점이 들 수 있어요. 아래와 같은 상황에서 --net-alias는 사용됩니다.
1. 여러 컨테이너를 같은 이름으로 접근하고 싶을 때 (예: 로드밸런싱)
--name은 컨테이너마다 반드시 교유해야 하기 때문에, 컨테이너 2개를 띄워도 이름은 다르게 줘야 해요.
docker run -d --name backend1 --network my-net my-app
docker run -d --name backend2 --network my-net my-app
nginx.conf(--name만 사용하는 경우):
upstream backend {
server backend1:3000;
server backend2:3000;
}
server {
listen 80;
location / {
proxy_pass http://backend;
}
}
⚠️ Nginx에서 로드밸런서를 구성하려면 각 컨테이너의 실제 이름 또는 IP를 모두 적어줘야 해요. 또한 컨테이너를 추가하거나 삭제할 때마다 nginx.conf를 고쳐야 하고 IP가 바뀌면 또다시 수정해야 하죠.
이제 --net-alias를 사용해 볼게요:
docker run -d --name backend1 --network my-net --net-alias backend my-app
docker run -d --name backend2 --network my-net --net-alias backend my-app
→ 위 두 컨테이너는 backend1, backend2라는 이름은 각각 다르지만, 공통적으로 backend라는 alias를 가지고 있어요.
도커는 같은 alias를 가진 컨테이너가 여러 개인 경우, 내부 DNS에서 라운드로빈 방식으로 IP를 분산해서 응답해 줍니다.
💡 도커의 DNS 라운드로빈이란?
도커는 하나의 alias에 여러 컨테이너가 연결되어 있으면, DNS 질의에 대해 등록된 IP 목록을 번갈아 응답해요.
curl http://backend # → backend1의 IP로 요청 curl http://backend # → 이번엔 backend2의 IP로 요청
✔️ Nginx가 내부 DNS에 backend 이름을 질의할 때마다, 컨테이너 IP가 돌아가면서 응답되기 때문에, 별도 로드밸런서 없이도 트래픽이 자동 분산됩니다.
nginx.conf(--net-alias를 사용하는 경우):
upstream backend {
server backend:3000;
}
server {
listen 80;
location / {
proxy_pass http://backend;
}
}
이제 nginx.conf에는 alias 하나만 쓰면 자동으로 분산 처리가 됩니다. 컨테이너가 몇 개든 --net-alias backend만 잘 붙여주면 설정은 그대로 유지돼요.