도커 데몬은 실행되면서 docker0라고 하는 가상 브리지 네트워크 인터페이스를 만들어요. 도커 데몬이 돌아가고 있는 시스템에서 ip addr 명령을 실행하면 아래와 같이 docker0라는 인터페이스가 생성되어 있는 것을 볼 수 있어요.

➜  ~ ip addr show docker0 
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default 
    link/ether 02:42:1a:08:d0:64 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:1aff:fe08:d064/64 scope link 
       valid_lft forever preferred_lft forever

docker0 인터페이스는 172.17.0.1/16의 IP를 사용하네요. 컨테이너들도 이 범위의 IP를 사용하고 랜덤하게 할당한다고는 하는데 보통 순서대로 사용하는 거 같아요. 컨테이너들은 이 네트워크를 사용해서 서로 통신하고 HOST와도 통신해요.

 

docker network ls 명령으로 확인해보면 아래와 같은 결과를 볼 수 있어요.

➜  ~ docker network ls 
NETWORK ID     NAME      DRIVER    SCOPE
8d3458727d03   bridge    bridge    local
3bec2ba8f217   host      host      local
3ace32da71f8   none      null      local

 

도커가 사용하는 네트워크는 크게 3가지로 볼 수 있어요. bridge 모드, host 모드 그리고 none이라는 이름의 null 모드.

 

Driver: Bridge

기본 설정은 bridge 모드에요. 도커가 컨테이너를 실행할 때마다 한 쌍의 가상의 인터페이스를 만들어요. 한쪽 끝은 HOST의 docker0 브리지 인터페이스에 연결해요.

또 한 쪽 끝은 새로 만들어진 컨테이너에 eth0이란 이름으로 만들어요.

이 두가지를 먼저 확인해볼게요.

➜  ~ docker run --rm -it alpine 
/ # ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
112: eth0@if113: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP 
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever
       
## In the HOST
ip addr
...
113: veth1cd48a9@if112: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default 
    link/ether de:ba:51:f1:40:43 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet6 fe80::dcba:51ff:fef1:4043/64 scope link 
       valid_lft forever preferred_lft forever

윗부분 컨테이너쪽에서 인터페이스를 확인해보면 112: eht0@if113:   ㅇ인터페이스를 확인할 수 있는데요 113은 호스트 쪽 인터페이스 인덱스로 보이네요. 그리고 IP는 172.17.0.2를 할당받은 것을 볼 수 있어요.

HOST에서 보면 113: veth1cd48a9@if112 가상 인터페이스를 확인할 수 있고 docker0 브리지 인터페이스에 binding 되어있는 것을 볼 수 있네요. HOST에서도 brctl 명령으로 확인해볼게요.

➜  ~ brctl show docker0 
bridge name     bridge id               STP enabled     interfaces
docker0         8000.02421a08d064       no              veth1cd48a9

 

도커는 또 docker0 브릿지 인터페이스를 세팅하는 것 외에 또 iptables NAT 정책도 설정하는데요 이 걸 통해서 컨테이너들은 외부와 통신할 수 있어요.

➜  ~ sudo iptables -t nat -nL
...
Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination         
MASQUERADE  all  --  172.17.0.0/16        0.0.0.0/0           

Chain DOCKER (2 references)
target     prot opt source               destination         
RETURN     all  --  0.0.0.0/0            0.0.0.0/0

여기까지는 좋아요. 그런데 컨테이너 안에 서비스를 넣고 호스팅 하려 하면 외부에서 컨테이너까지 닿아야 할 텐데 그럴 땐 어떻게 해야 할까요? 이전에도 몇 번 봤지만 다시 한번 볼꼐요.

 

기본적으로 컨테이너의 트래픽은 외부로 나가는 것은 허용하지만 외부에서 들어오는 건 따로 기본으로 되지 않아요 따로 설정을 해줘야 해요. 이때 두 가지 옵션을 사용할 수 있어요.

 

--publish, -p  컨테이너의 특정 포트를 호스트의 포트와 연결합니다.
--publish-all, -P 컨테이너의 모든 포트를 호스트의 임의의 포트와 연결합니다.

사실 --publish-all 옵션은 안 써봐서 잘 모르겠어요. 그냥 --publish 옵션만 볼게요.

➜  ~ docker run -d --rm -p 8000:8080 xodwkx2/show-me-host:2.0.0
ca8fab4ad14c7b8db598089f9a3608373b10df35e40f98ac962a23a9c843b3d1

➜  ~ docker container port ca8fab4ad14c7b8db598089
8080/tcp -> 0.0.0.0:8000
8080/tcp -> :::8000

➜  ~ curl localhost:8000
Your app is running on Host: ca8fab4ad14c

예전에 만들어본 show-me-host:2.0.0 이미지로 컨테이너를 만들고 HOST의 8000 포트와 컨테이너의 8080 포트를 연결했어요. 예전에 봤었던 docker container port 명령어를 통해서 컨테이너가 게재한 포트를 확인하고 curl localhost:8000 명령을 통해서 컨테이너에서 실행 중인 서비스에서 응답을 받아봤어요. 그럼 HOST 밖에서 HOST IP:8000 포트로도 서비스가 되는지 확인해볼까요?

 

잘되는 거 같네요. 그럼 다시 HOST의 iptables를 확인해볼게요.

➜  ~ sudo iptables -t nat -nL
...
Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination         
MASQUERADE  all  --  172.17.0.0/16        0.0.0.0/0           
MASQUERADE  tcp  --  172.17.0.3           172.17.0.3           tcp dpt:8080

Chain DOCKER (2 references)
target     prot opt source               destination         
RETURN     all  --  0.0.0.0/0            0.0.0.0/0           
DNAT       tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:8000 to:172.17.0.3:8080

chain DOCKER에 tcp dpt:8000 to 172.17.0.3:8000이 보이네요

172.17.0.3은 show-me-host의 IP에요. 출발지 주소가 어디든 HOST IP로 들어온 tcp 패킷의 목적지 포트가 8000이면 172.17.0.3의 8080 포트로 NAT 하는 설정인 것 같네요.

 

--publish 옵션을 사용하는 방법은 4가지가 있어요. 이 건 다음에 알아볼게요. 공부중이에요.

 

 

Driver: Host

앞에서 alpine 리눅스 컨테이너를 브리지 네트워크에 붙여서 확인해봤었는데요 이번에는 host 모드로 컨테이너를 생성했을 때는 어떻게 변하는지 한 번 볼게요.

➜  ~ docker container run -it --rm --network host alpine 
/ # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 52:54:00:f1:1b:46 brd ff:ff:ff:ff:ff:ff
    inet 192.168.122.140/24 brd 192.168.122.255 scope global dynamic noprefixroute eth0
       valid_lft 3186sec preferred_lft 3186sec
    inet6 fe80::e611:b5f2:a8b8:949/64 scope link tentative dadfailed noprefixroute 
       valid_lft forever preferred_lft forever
    inet6 fe80::9f4e:5590:3aac:15c1/64 scope link tentative dadfailed noprefixroute 
       valid_lft forever preferred_lft forever
    inet6 fe80::2439:b703:a6ee:127e/64 scope link tentative dadfailed noprefixroute 
       valid_lft forever preferred_lft forever
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP 
    link/ether 02:42:1a:08:d0:64 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:1aff:fe08:d064/64 scope link 
       valid_lft forever preferred_lft forever
113: veth1cd48a9@if112: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue master docker0 state UP 
    link/ether de:ba:51:f1:40:43 brd ff:ff:ff:ff:ff:ff
    inet6 fe80::dcba:51ff:fef1:4043/64 scope link 
       valid_lft forever preferred_lft forever
117: veth270c243@if116: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue master docker0 state UP 
    link/ether aa:ab:50:74:f3:e7 brd ff:ff:ff:ff:ff:ff
    inet6 fe80::a8ab:50ff:fe74:f3e7/64 scope link 
       valid_lft forever preferred_lft forever
119: veth81dd11d@if118: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue master docker0 state UP 
    link/ether 16:3b:ee:7d:4f:a2 brd ff:ff:ff:ff:ff:ff
    inet6 fe80::143b:eeff:fe7d:4fa2/64 scope link 
       valid_lft forever preferred_lft forever

확실히 이전이랑은 많이 다르네요. 자세히 보면 그냥 HOST의 네트워크와 똑같아요. 도커는 컨테이너를 만들 때 컨테이너의 네트워크 네임스페이스를 만들고 컨테이너에 IP를 할당하는데요 host 모드를 사용하면 컨테이너를 HOST의 네트워크 스택에 바로 붙여버리고 컨테이너는 HOST의 네트워크 스택에 fully access 할 수 있게 돼요.

 

그럼 이 host 모드에서 똑같이 xodkwx2/show-me-host:2.0.0 이미지로 컨테이너를 생성하면 어떻게 될까요?

먼저 만들었던 컨테이너는 삭제하고 해 봤어요.

➜  ~ docker run -d -it --network host xodwkx2/show-me-host:2.0.0
9fc69c691c1ab844738be97f73f3ba3d4833109f6818de5f649a57e73ee22664

➜  ~ docker ps 
CONTAINER ID   IMAGE                        COMMAND                  CREATED         STATUS         PORTS     NAMES
9fc69c691c1a   xodwkx2/show-me-host:2.0.0   "docker-entrypoint.s…"   2 seconds ago   Up 2 seconds             frosty_goldwasser

➜  ~ sudo netstat -nlpt 
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0 0.0.0.0:8080            0.0.0.0:*               LISTEN      4502/node

➜  ~ curl localhost:8080 
Your app is running on Host: testos

➜  ~ docker exec -it 9fc69 bash 
root@testos:/usr/src/app#

-p 옵션으로 따로 포트를 게재하지 않았는데 HOST의 8080 포트가 열려있네요. 그리고 중요한 거.

애플리케이션은 컨테이너 안에서 돌아가고 있는데 host의 이름을 찍어주는 애플리케이션이 자기 컨테이너 이름을 반환하는 게 아니라 HOST의 hostname을 보여주네요? docker exec 명령으로 쉘에 접속해서 보니 컨테이너 안에 hostname이 컨테이너 이름이 아니라 HOST의 hostname으로 되어있네요.

 

와우.

 

 

Driver: Null

마지막으로 null 모드는 ip를 할당받지 않고 어느 네트워크에도 속하지 않아요.

➜  ~ docker run -d -it --network none alpine   
4d7b59003840af3294730a059c9aa77b93cda6a0e829ae18e1866d99288429df

➜  ~ docker exec -it 4d7b sh   
/ # ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever

위와 같이 루프백 인터페이스만 생성되어 있는 것을 확인할 수 있는데요. 보통 cronjob같이 배치작업을 할 때 쓴다고 해요. 뭐.. 볼륨 같은 걸 연결해서 단순히 배치작업만 실행하게 하는 거 같아요. 써보지 않아서 잘 모르겠어요.

아무튼 null 모드의 네트워크는 컨테이너를 생성하면서 veth를 붙이지 않고 루프백 인터페이스만 가지고 컨테이너가 만들어진다.라고 알고 있으면 될 것 같아요.

 

끗.