이번엔 Docker Network다. 기존에 Docker Network부분은 많이 헷갈렸는데 이 기회에 좀 바삭하게 알아놔야겠다.
Docker network
일다 Docker network에서 가장 핵심적인 단어를 뽑자면 CNM (Container Networking Model), Bridge네트워크를 기본적으로 알아야한다.

CNM (Container Networking Model)
Docker가 사용하는 기본 네트워크 모델입니다. CNM은 세 가지 기본 구성 요소로 나뉜다
Bridge 네트워크
Bridge 네트워크는 Docker의 기본 네트워크 드라이버로, 컨테이너들이 같은 호스트 내에서 서로 통신할 수 있게 해준다. 이를 Linux Bridge와 Veth 인터페이스로 관리한다.

veth와 eth0
컨테이너 내부에서 보면 eth0가 기본 네트워크 인터페이스로 보이는데, 사실 이 eth0는 외부의 veth 페어와 연결되어 있다. 컨테이너 외부에서는 이 인터페이스가 veth0로 표시되며, 이를 통해 호스트와 연결된 브리지로 데이터를 주고받습니다.
예를들어 Docker에서 컨테이너를 생성하면, 호스트의 docker0라는 기본 브리지에 연결된다. docker0은 앞서 말했듯이 Docker가 자동으로 만드는 가상 브리지이다.
이 브리지에 컨테이너를 연결하기 위해 veth pair가 생성되는데,하나는 호스트의 브리지 (docker0)에 붙고, 다른 하나는 컨테이너 안에 배치된다.(컨테이너 내에서는 eth0로 보임)
도커 네트워크의 패킷 흐름
위의 도커 네트워크의 패킷 흐름을 정리하자면
1. 컨테이너에서 네트워크 패킷을 전송하면, 이는 eth0를 통해 컨테이너 밖으로 나간다.
2. 그 패킷은 veth pair를 타고 호스트의 브리지 네트워크로 전달된다.
3. 브리지는 적절한 네트워크 라우팅을 통해 외부 네트워크로 패킷을 전달한다.
TMI
Container의 네트워크(eth0)는 vethxxxxxxx의 숫자보다 하나 작은 값을 가진다.
docker-proxy?
kernel이 아닌 사용자 환경에서 수행되기 때문에 kernel과 상관없이 host가 받은 패킷을 그대로 container의 port로 전달. port를 외부로 노출하도록 설정하게 되면, docker host에는 docker-proxy 라는 프로세스가 자동으로 생성
Docker overlay(주로 Swarm이나 ECS에서 활용)

Docker Network Type
docker network "host"
docker run 명령어 host --net 옵션을 붙여주면 네트워크를 Docker의 기본 네트워크인 Docker0을 사용하는것이 아닌 호스트 운영체제에서 직접 PID를 할당 받아 서비스한다. 즉 Host Network를 사용한다.
docker run -d --name=nginx_host --net=host nginx:latest
따라서 컨테이너에는 별도의 IP가 부여되지 않는다.
~$ docker inspect nginx_host | grep IPAddress
"IPAddress": "",
"IPAddress": "",
docker network create
docker에서 사용하는 기본 Bridge네트워크가 아닌 별도의 Bridge 네트워크를 만들어 컨테이너를 연결시킬수 있다.
docker network create kdjnet
이 네트워크에 여러대의 컨테이너를 올린후 이름으로 ping을 날려보면 통신이 가능하다. 왜냐하면 Docker DNS에 의한 Service Discovery 기능을 사용할 수 있기 때문이다. 기본 Docker Bridge는 Docker DNS 사용이 불가능하다.
또한 다음과 같이 특정 IP대역 지정도 가능하다.
~$ docker network create \
> --driver bridge \
> --subnet 172.30.1.0/24 \
> --ip-range 172.30.1.0/24 \
> --gateway 172.30.1.1 \
> kdj-net
TMI
여기서 네트워크의 Docker bridge의 2계층통신이 어떻게 정확이 되는건지 네트워크 개념이 약하다보니 통신흐름대로 다시 한번 정리해보았다.MAC 주소 기반 통신
Bridge 네트워크는 마치 스위치처럼 동작한다. 각 컨테이너는 자신의 고유한 MAC 주소를 가지며, Bridge는 이 MAC 주소를 사용하여 컨테이너 간에 데이터를 전달한다.
호스트가 컨테이너로 데이터를 보내려면, 호스트는 해당 컨테이너의 MAC 주소를 알아야 한다. 호스트는 네트워크의 ARP(Address Resolution Protocol)를 사용하여 컨테이너의 IP 주소를 MAC 주소로 변환한다.
데이터를 전송할 때, 호스트는 해당 컨테이너의 MAC 주소를 이용해 Bridge 네트워크를 통해 데이터를 보냅니다. 이 데이터는 Bridge 네트워크에서 올바른 컨테이너의 veth 인터페이스로 전달된다.
통신 과정 요약
1. 컨테이너 생성
- 컨테이너가 생성되면 Docker가 자동으로 veth pair를 만든다.
- 한쪽 veth는 호스트 쪽 네트워크(Bridge 네트워크)에 연결되고, 다른쪽 veth는 컨테이너 내부로 연결된다.
- 데이터 전송
- 호스트에서 컨테이너로 데이터를 보내려면, 호스트는 컨테이너의 MAC 주소를 알아야 한다.
- ARP 프로토콜을 통해 IP 주소를 MAC 주소로 변환
- 호스트는 데이터를 Bridge 네트워크를 통해 컨테이너의 MAC 주소로 보낸다.
- 이때, Bridge 네트워크는 MAC 주소를 보고 데이터를 올바른 veth 인터페이스로 전달합니다.
- 데이터 수신
- 컨테이너는 veth pair를 통해 데이터를 받는다. 호스트에서 전송된 데이터는 veth를 타고 컨테이너의 네트워크 인터페이스로 들어간다.
반대로, 컨테이너에서 호스트로도 데이터를 보낼 수 있다. 이때도 veth pair가 데이터를 전달해준다.
비유로 설명
veth pair는 두 개의 네트워크 포트가 서로 연결된 가상 케이블과 같습니다. 한쪽 포트에서 데이터를 보내면 이 케이블을 통해 다른 쪽 포트로 전달됩니다.요약
호스트와 컨테이너 간의 네트워크 통신은 veth pair를 통해 이루어지며, 각각의 인터페이스는 고유한 MAC 주소를 가진다. 호스트는 MAC 주소를 사용하여 컨테이너의 네트워크 인터페이스를 찾고 데이터를 전달한다. 이 과정에서 호스트는 컨테이너의 MAC 주소를 기반으로 통신하며, 데이터는 브리지를 통해 컨테이너로 전달된다.
docker network topology

그림과 같이 서로 다른 브릿지네트워크 에서는 통신이 불가능하고 같은 브릿지 네트워크에서만 통신이 가능하다.

그래서 컨테이너를 이용해서 3Tier를 구성할때는 다른 브릿지네트워크로 분류해도 docker network connect/diconnect명령어를 통해 네트워크간 통신을 유지하도록한다.
Docker DNS

도메인으로 예를 들어보면
하나의 도메인 이름이 여러 VM의 IP 주소에 매핑되어 있을 때, 사용자가 도메인으로 요청을 보내면 DNS 라운드 로빈 방식으로 여러 서버 중 하나로 트래픽이 분산되듯이, Docker의 net-alias는 동일한 별칭으로 묶인 여러 컨테이너 중 하나로 요청을 라우팅해준다.
Proxy
Forward Proxy

Forward Proxy는 클라이언트와 외부 서버 사이에 위치하며, 클라이언트의 요청을 대신 처리해 외부 서버로 보내고, 응답을 클라이언트에게 전달한다. 이를 통해 클라이언트의 IP를 숨기거나 특정 사이트에 대한 접근을 제어할 수 있으며, 캐싱을 통해 네트워크 성능을 향상시킬수도 있다. 주로 회사나 공공 네트워크에서 웹 콘텐츠 필터링을 할떄 주로 사용된다.
Reverse Proxy

Reverse Proxy는 클라이언트와 내부 서버 사이에 위치한다. 클라이언트가 내부 서버에 직접 접근하지 않고 리버스 프록시 서버에 요청을 보내면, 이 서버가 요청을 적절한 내부 서버로 전달하거나 캐싱된 데이터를 반환한다. 주로 로드밸런싱, 서버 보안 강화, SSL 처리 그리고 캐싱을 통한 성능 최적화를 위해 사용된다. 웹 애플리케이션 앞단에서 우리가 주로 흔히 사용한다.
Nginx Reverse Proxy구성
Nginx Reverse Proxy를 구성하는 방법에는 크게 두가지로 나눌 수 있다. 호스트 nginx에서 타겟 컨테이너로 프록시해주냐 아니면 컨테이너 nginx를 띄운 후 타겟 컨테이너로 프록시해주냐의 차이이다.
호스트에서 Nginx 설치 및 설정

Nginx 설치
sudo apt update
sudo apt install nginx
Nginx 설정 파일 수정
/etc/nginx/nginx.conf 파일을 아래와 같이 수정한다.
events {
worker_connections 1024;
}
http {
# 로드 밸런싱 대상 서버 설정
upstream backend-alb {
server 127.0.0.1:5001;
server 127.0.0.1:5002;
server 127.0.0.1:5003;
}
# 서버 설정
server {
# 80번 포트에서 수신
listen 80 default_server;
# 프록시 설정
location / {
proxy_pass http://backend-alb;
}
}
}
docker run -d -p 5001:80 --name nginx1 nginx
docker run -d -p 5002:80 --name nginx2 nginx
docker run -d -p 5003:80 --name nginx3 nginx
sudo systemctl restart nginx.service
요청을 여러 번 보내면, 각 컨테이너가 로드 밸런싱을 통해 순차적으로 응답하는 것을 확인할 수 있다. Nginx가 설정된 대로 호스트의 80포트로 신호를 보내면 127.0.0.1:5001, 5002, 5003으로 요청을 분배한다.
Nginx Container Proxy 설치 및 설정

docker run -d -p 8001:80 --name=proxy-container nginx:1.25.0-alpine
호스트의 80포트로 요청이오면 8001포트 컨테이너로 프록시된다.
events {
worker_connections 1024;
}
http {
upstream backend-alb {
server 192.168.56.101:5001;
server 192.168.56.101:5002;
server 192.168.56.101:5003;
}
server {
listen 80 default_server;
location / {
proxy_pass http://backend-alb;
}
}
}
docker cp nginx.conf proxy-container:/etc/nginx/nginx.conf
주로 Docker Volume을 통해 관리한다.
docker restart proxy-container
curl 명령어를 사용하여 포트 8001로 요청을 보내, 각 백엔드 서버로 요청이 분배되는지 확인할 수 있다.
로드밸런싱에 대한 가중치를 nginx.conf파일에서 다음과 같이 정의해 줄 수 있다.
http {
upstream backend-alb {
server 192.168.56.101:5001 weight=60;
server 192.168.56.101:5002 weight=20;
server 192.168.56.101:5003 weight=20;
}
HA Proxy
HA Proxy도 Proxy를 해주는 오픈 소스이다. 주로 로드 밸런싱과 프록스 기능을 위해 사용한다. HA Proxy의 중요한점은 4계층 TCP와 7계층 HTTP프로토콜을 이용할 수 있다는 장점이 있다. 그래서 간단한 Proxy는 nginx의 proxy pass를 이용하지만 대규모 트래픽 같은 경우는 HA Proxy를 사용하곤 한다. IDC센터에서 일을 해보지 않았지만 주로 그럴것같다...
여기 실습해선 간단하게 3가지 방법으로 Proxy를 하는 방법에 대해 소개하였다.
기본 mode http 방식
1. Docker 네트워크 생성
HAProxy와 백엔드 서버들이 서로 통신할 수 있도록 Docker 네트워크를 생성한다. 네트워크로 분류하여 주기 위함이다.
docker network create proxy-net
docker run -d --name=echo-web1 --net=proxy-net -h echo-web1 dbgurum/haproxy:echo
docker run -d --name=echo-web2 --net=proxy-net -h echo-web2 dbgurum/haproxy:echo
docker run -d --name=echo-web3 --net=proxy-net -h echo-web3 dbgurum/haproxy:echo
global
stats socket /var/run/api.sock user haproxy group haproxy mode 660 level admin expose-fd listeners
log stdout format raw local0 info
defaults
mode http
timeout client 10s
timeout connect 5s
timeout server 10s
timeout http-request 10s
log global
frontend stats
bind *:8404
stats enable
stats uri /
stats refresh 10s
frontend myfrontend
bind :80
default_backend webservers
backend webservers
server s1 echo-web1:8080 check
server s2 echo-web2:8080 check
server s3 echo-web3:8080 check
docker run -d --name=haproxy-container --net=proxy-net -p 80:80 -p 8404:8404 -v $(pwd)/conf:/usr/local/etc/haproxy:ro haproxytech/haproxy-alpine:2.5
docker ps
docker port haproxy-container
HAProxy를 통해 로드 밸런싱이 잘 되는지 curl 명령어로 확인하면 된다.
이 과정을 통해 HAProxy를 사용하여 컨테이너 기반 로드 밸런서를 성공적으로 구성할 수 있다. HAProxy가 클라이언트 요청을 수신하고, 설정한 백엔드 서버로 요청을 분산하여 로드 밸런싱을 수행한다.
로컬호스트:8404를 통핸 Haproxy 통계정보
하지만 실무에서는 URL 방식을 사용하기 때문에 다음과 같은 방법을 주로 사용한다.
URL 방식 예시 1
나머지 방식은 똑같고 haproxy.cfg파일만 다음과 같이 바꿔주면된다.
vi haproxy.cfg
global
stats socket /var/run/api.sock user haproxy group haproxy mode 660 level admin expose-fd listeners
log stdout format raw local0 info
defaults
mode http
timeout client 10s
timeout connect 5s
timeout server 10s
timeout http-request 10s
log global
frontend stats
bind *:8404
stats enable
stats uri /
stats refresh 10s
frontend myfrontend
bind :80
default_backend webservers
# ACLs for path-based routing
acl echo-web1 path_beg /echo-web1
acl echo-web2 path_beg /echo-web2
acl echo-web3 path_beg /echo-web3
# Routing based on ACLs
use_backend echo-web1_backend if echo-web1
use_backend echo-web2_backend if echo-web2
use_backend echo-web3_backend if echo-web3
backend webservers
balance roundrobin
server s1 echo-web1:8080 check
server s2 echo-web2:8080 check
server s3 echo-web3:8080 check
backend echo-web1_backend
server s1 echo-web1:8080 check
backend echo-web2_backend
server s2 echo-web2:8080 check
backend echo-web3_backend
server s3 echo-web3:8080 check
동작 설명
- 기본 요청 (localhost:80):
요청은 webservers 백엔드로 라운드 로빈 방식으로 분산된다.
이 백엔드는 echo-web1, echo-web2, echo-web3 서버를 포함하고 있다.- 경로별 요청
localhost:80/echo-web1은 echo-web1_backend로 라우팅된다.
localhost:80/echo-web2은 echo-web2_backend로 라우팅된다.
localhost:80/echo-web3은 echo-web3_backend로 라우팅된다.
URL 방식 예시 2
기존 컨테이너를 내리고 다음과 같이 컨테이너를 올려준다.
kevin@hostos1:~/fastcampus/ch06/conf$ docker run -d --name=echo-web1-item --
net=proxy-net -h echo-web1-item dbgurum/haproxy:echo
kevin@hostos1:~/fastcampus/ch06/conf$ docker run -d --name=echo-web2-item --
net=proxy-net -h echo-web2-item dbgurum/haproxy:echo
kevin@hostos1:~/fastcampus/ch06/conf$ docker run -d --name=echo-web3-basket --
net=proxy-net -h echo-web3-basket dbgurum/haproxy:echo
kevin@hostos1:~/fastcampus/ch06/conf$ docker run -d --name=echo-web4-basket --
net=proxy-net -h echo-web4-basket dbgurum/haproxy:echo
그리고 haproxy.cfg파일을 다음과 같이 바꿔준다.
global
stats socket /var/run/api.sock user haproxy group haproxy mode 660 level admin expose-fd listeners
log stdout format raw local0 info
defaults
mode http
timeout client 10s
timeout connect 5s
timeout server 10s
timeout http-request 10s
log global
frontend stats
bind *:8404
stats enable
stats uri /
stats refresh 10s
frontend myfrontend
bind :80
default_backend webservers
# ACLs for path-based routing
acl echo-web1-item path_beg /item
acl echo-web2-item path_beg /item
acl echo-web3-basket path_beg /basket
acl echo-web4-basket path_beg /basket
# Routing based on ACLs
use_backend echo-web1_backend if echo-web1-item
use_backend echo-web1_backend if echo-web2-item
use_backend echo-web2_backend if echo-web3-basket
use_backend echo-web2_backend if echo-web4-basket
backend webservers
balance roundrobin
server s1 echo-web1-item:8080 check
server s2 echo-web2-item:8080 check
server s3 echo-web3-basket:8080 check
server s4 echo-web4-basket:8080 check
backend echo-web1_backend
server s1 echo-web1-item:8080 check
server s2 echo-web2-item:8080 check
backend echo-web2_backend
server s3 echo-web3-basket:8080 check
server s4 echo-web4-basket:8080 check
동작 설명
요청은 webservers 백엔드로 라운드 로빈 방식으로 분산된다.
/item 경로는 echo-web1 또는 echo-web2로 라운드 로빈된다.
/basket 경로는 echo-web3또는 echo-web4로 라운드 로빈된다.