Docker 네트워크 이해
Docker 를 단순한 빠르고 가볍게 애플리케이션을 구성하게 해주는 컨테이너(Container) 로만 이해하는 건 좁은 시각이다. Docker 는 이러한 컨테이너들을 위한 네트워크 환경도 제공하고 있다. 이것은 Docker 가 종합적인 IT 인프라를 제공할 수 있다는 것을 의미한다.
Docker 네크워크 상태
Docker 를 호스트에 설치하면 우선적으로 다음과 같은 네트워크 장치가 하나 보인다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
~$ ifconfig docker0 Link encap:Ethernet HWaddr 02:42:33:37:4b:7f inet addr:172.17.0.1 Bcast:172.17.255.255 Mask:255.255.0.0 UP BROADCAST MULTICAST MTU:1500 Metric:1 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:0 (0.0 B) TX bytes:0 (0.0 B) eth0 Link encap:Ethernet HWaddr 52:54:00:dd:77:bd inet addr:192.168.96.16 Bcast:192.168.111.255 Mask:255.255.240.0 UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:388 errors:0 dropped:4 overruns:0 frame:0 TX packets:151 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:49759 (49.7 KB) TX bytes:20675 (20.6 KB) |
컨테이너를 하나도 실행하지 않은 상태에서 단지 Docker 데몬이 실행만 되어도 이렇게 docker0 라는 네트워크 장치가 나타난다. 아이피는 172.17.0.1 이며 브로드캐스트(Broadcast) 가 172.17.255.255 인 것으로 봐서 docker0 장치가 속하는 네트워크 대역은 172.17.0.0/16 임을 알 수 있다.
docker0 는 Docker 가 실행되면서 자동으로 생성되는 가상의 네트워크 장치다. 네트워크 장치에도 여러가지 역할 혹은 모드를 부여할 수 있는데, docker0 가상네트워크는 브릿지모드로 동작하도록되어 있다.
1 2 3 |
~$ brctl show bridge name bridge id STP enabled interfaces docker0 8000.024233374b7f no |
docker0 가상네트워크가 브릿지 모드로 동작한다는 것은 이 브릿지에 연결한 네트워크 장비들을 외부와 통신하도록 해준다. 말 그대로 다리 역할을 하는 것이 docker0 이다.
이제 컨테이너 하나를 생성해 보자.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
~$ docker run -d -it ubuntu:latest /bin/bash Unable to find image 'ubuntu:latest' locally latest: Pulling from library/ubuntu 6cf436f81810: Pull complete 987088a85b96: Pull complete b4624b3efe06: Pull complete d42beb8ded59: Pull complete Digest: sha256:7a47ccc3bbe8a451b500d2b53104868b46d60ee8f5b35a24b41a86077c650210 Status: Downloaded newer image for ubuntu:latest 3c9407b7b752da50d390fb9f14bcf9cb76af5211affede7f63b9930d7918028a ~$ docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 3c9407b7b752 ubuntu:latest "/bin/bash" 3 seconds ago Up 2 seconds keen_jang |
우분투(Ubuntu) 컨테이너 하나를 실행했다. 이 상태에서 호스트 네트워크를 살펴보면 다음과 같다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
~$ ifconfig docker0 Link encap:Ethernet HWaddr 02:42:33:37:4b:7f inet addr:172.17.0.1 Bcast:172.17.255.255 Mask:255.255.0.0 UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:0 (0.0 B) TX bytes:0 (0.0 B) eth0 Link encap:Ethernet HWaddr 52:54:00:dd:77:bd inet addr:192.168.96.16 Bcast:192.168.111.255 Mask:255.255.240.0 UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:38936 errors:0 dropped:4 overruns:0 frame:0 TX packets:21943 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:67359433 (67.3 MB) TX bytes:1321049 (1.3 MB) lo Link encap:Local Loopback inet addr:127.0.0.1 Mask:255.0.0.0 inet6 addr: ::1/128 Scope:Host UP LOOPBACK RUNNING MTU:65536 Metric:1 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1 RX bytes:0 (0.0 B) TX bytes:0 (0.0 B) veth83317a4 Link encap:Ethernet HWaddr 66:4e:89:52:6b:0b UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:0 (0.0 B) TX bytes:0 (0.0 B) |
없었던 veth83317a4 가 생겼다. 그리고 docker0 가상네트워크의 브릿지 상태는 다음과 같다.
1 2 3 |
~$ brctl show bridge name bridge id STP enabled interfaces docker0 8000.024233374b7f no veth83317a4 |
docker0 에 veth83317a4 가상네트워크 장치가 연결되었다는 것을 볼 수 있다. 이것은 ‘ip link’ 나 ‘ip a’ 명령어로도 확인할 수 있다.
1 2 3 |
~$ ip link 9: veth83317a4@if8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT group default link/ether 66:4e:89:52:6b:0b brd ff:ff:ff:ff:ff:ff |
‘master docker0’ 가 이 가상네트워크 장치가 docker0 브릿지에 연결되었다는 것을 말해준다.
우분투 컨테이너의 네트워크 상태는 어떻게 될까? 다음과 같이 확인이 가능하다.
1 2 3 4 5 6 7 8 9 |
~$ docker exec 3c9407b7b752 ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1 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 12: eth0@if13: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0 valid_lft forever preferred_lft forever |
eth0 장치가 있고, 아이피는 172.17.0.2 로 할당되어 있다. 그리고 Default Gateway 는 다음과 같이 확인할 수 있다.
1 2 3 4 5 |
~$ docker exec 3c9407b7b752 route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 0.0.0.0 172.17.0.1 0.0.0.0 UG 0 0 0 eth0 172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 eth0 |
우분투 컨테이너의 Default Gateway 는 컨테이너를 실행한 호스트의 Docker 가상네트워크인 docker0 를 가르키고 있다. 결국 Docker 컨테이너의 모든 네트워크는 호스트의 docker0 를 가르키고 이는 모든 외부와의 트래픽은 docker0 을 통해서만 가능하다.
이를 도식으로 나타내면 다음과 같다.
Docker0 브릿지 네트워크
이 docker0 브릿지 네트워크는 Docker 네트워크중에 하나다.
1 2 3 4 5 |
~$ docker network ls NETWORK ID NAME DRIVER SCOPE 13ed3c333711 bridge bridge local 84d5591305ca host host local 352cc4d7e9bf none null local |
Docker 가 기본으로 제공하는 네트워크 기능들이 나온다. 여기서 bridge 에 대한 내용을 자세하 살펴보면 다음과 같다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
~$ docker network inspect 13ed3c333711 [ { "Name": "bridge", "Id": "13ed3c3337110485dfb84afbcc794baf3e40c14808af39d3436996bebcb937cd", "Created": "2019-03-07T21:54:04.617037741+09:00", "Scope": "local", "Driver": "bridge", "EnableIPv6": false, "IPAM": { "Driver": "default", "Options": null, "Config": [ { "Subnet": "172.17.0.0/16", "Gateway": "172.17.0.1" } ] }, "Internal": false, "Attachable": false, "Ingress": false, "ConfigFrom": { "Network": "" }, "ConfigOnly": false, "Containers": { "3c9407b7b752da50d390fb9f14bcf9cb76af5211affede7f63b9930d7918028a": { "Name": "keen_jang", "EndpointID": "a4efbaecfd52b29be7bb3e22ad4e941e1d5e057a1215bd5a759159bd9eea8d05", "MacAddress": "02:42:ac:11:00:02", "IPv4Address": "172.17.0.2/16", "IPv6Address": "" } }, "Options": { "com.docker.network.bridge.default_bridge": "true", "com.docker.network.bridge.enable_icc": "true", "com.docker.network.bridge.enable_ip_masquerade": "true", "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0", "com.docker.network.bridge.name": "docker0", "com.docker.network.driver.mtu": "1500" }, "Labels": {} } ] |
위 내용을 보면 IPAM 에 Config 를보면 네트워크 설정 내용이 보인다. 그리고 Options 에 “com.docker.network.bridge.name”: “docker0” 라는게 보인다. 이렇게 docker0 가상네트워크가 Docker 에서 생성한 Bridge 라는 것을 알 수 있다.
또, 현재 Bridge 에 연결되 실행되는 컨테이너 네트워크 정보도 함께 알 수 있다.
Docker 네트워크 특징
도커 호스트에 있는 컨테이너를 위한 가상네트워크 장비는 컨테이너와 peer 된 상태가 된다. 컨테이너를 하나씩 실행할때마다 하나씩 늘어난다. 컨테이너 갯수에 맞춰서 존재한다는 것이다.
도커 호스트에 컨테이너를 위한 가상네트워크 장비인 veth4327b92 는 컨테이너가 재시작될때마다 veth 를 제외한 숫자가 매번 바뀐다. 우분투 컨테이너를 재시작하고 난후 vethffc000e 로 변경됐다.
또, 위 도식에 보면 컨테이너 네트워크에 12라는 숫자가 보인다. 그리고 도커 호스트에 컨테이너를 위한 가상네트워크 번호가 13으로 나온다. 이를 보면 도커 컨테이너 네트크워크 숫자는 veth 가상호스트의 숫자보다 하나 작은 값을 가진다는 것을 알 수 있다. 이 숫자도 도커 컨테이너가 재시작될때마다 바뀌지만 1 작은 상태는 그대로 유지 된다. 이로써 도커 컨테이너 네트워크가 외부에 어느 veth 네트워크와 연결되는지를 알수 있게 된다.
또, 컨테이너에서 외부의 veth 가상네트워크를 볼 수가 없다. 이는 커널의 Network Namespace 로 격리를 하기 때문이다.
Docker Network Namespace
docker 의 Network Namespace 는 Docker 컨테이너가 실행될때마다 /var/run/docker/netns 디렉토리에 생성된다. 이에 대한 정보는 컨테이너 상세정보를 보면 알 수 있다.
1 2 3 4 5 |
~$ docker inspect 3c9407b7b752 ... "NetworkSettings": { "SandboxKey": "/var/run/docker/netns/9b10e5e950d7", ... |
이제 이것을 ip netns 에서 사용할 수 있도록 심볼릭 링크를 작성해 보자.
1 |
~$ sudo ln -s /var/run/docker/netns /var/run/netns |
이렇게 한 후에 netns 를 살펴보면 다음과 같은 결과가 나온다.
1 2 |
~$ sudo ip netns 9b10e5e950d7 |
이것을 이용하면 네트워크 정보를 확인할 수 있다.
1 2 3 4 5 6 7 8 9 |
~$ sudo ip netns exec 9b10e5e950d7 ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1 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 14: eth0@if15: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 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 |
Network Namespace 를 통해서 컨테이너에 네트워크 관련 명령어를 내릴 수 있다.
docker0 와 eth0 의 연결: iptables -t nat
docker0 에서 외부 네트워크 연결을 위해서 특별한 프로그램을 별도 운영하진 않는다. 단지, NAT 를 위한 Iptables 를 세팅한다. 이것은 오래전에 리눅스 박스를 라우터 장비로 이용하기 위한 방법인(지금에 비교를 하자면 리눅스 박스를 공유기로 이용하는 방법이다.) 마스커레이드(MASQUERADE) 과 완전 동일하다.
docker0 가상네트워크를 공유기로서 동작하도록 설정하는 것이다. iptables 를 한번 봐보자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
~$ sudo iptables -t nat -nvL Chain PREROUTING (policy ACCEPT 3462 packets, 630K bytes) pkts bytes target prot opt in out source destination 2 160 DOCKER all -- * * 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL Chain INPUT (policy ACCEPT 40 packets, 4131 bytes) pkts bytes target prot opt in out source destination Chain OUTPUT (policy ACCEPT 67 packets, 4403 bytes) pkts bytes target prot opt in out source destination 0 0 DOCKER all -- * * 0.0.0.0/0 !127.0.0.0/8 ADDRTYPE match dst-type LOCAL Chain POSTROUTING (policy ACCEPT 67 packets, 4403 bytes) pkts bytes target prot opt in out source destination 11 677 MASQUERADE all -- * !docker0 172.17.0.0/16 0.0.0.0/0 Chain DOCKER (2 references) pkts bytes target prot opt in out source destination 0 0 RETURN all -- docker0 * 0.0.0.0/0 0.0.0.0/0 |
패킷이 docker 컨테이너로 들어올때는 PREROUTING 을 거치고 외부로 나갈때는 POSTROUTING 을 거치게 된다.
만일 iptables 명령어로 rule 를 무효화 하게 되면 docker 컨테이너는 외부와 통신하는게 불가능해 진다.
Inter-Container Communication
Docker 의 네트워크는 기본적으로 Inter-Container Communication 이다. 여러개의 컨테이너를 생성하면 각 컨테이너사이에 통신이 가능하다.
만일 이기능을 끄고 싶다면 docker 를 기동할 때에 다음과 같이 옵션을 주면 된다.
1 2 |
$ sudo systemctl stop docker (service docker stop) $ sudo docker --icc=false & |
혹은 daemon.json 파일을 다음과 같이 작성해서 서비스를 재시작하는 방법도 있다.
1 2 3 4 |
$ sudo vim /etc/docker/daemon.json { "icc": false } |
이렇게 한 후에 컨테이너 사이에 통신은 불가능해 진다. 이것을 도커 컨테이너 네트워크 격리(Docker Container Network Isolation) 라고해서 구글 검색을 하면 꽤 많은 내용이 나온다.
왜 도커의 네트워크를 격리해야하는가? 이것을 잘 이용하면 네트워크 설계를 보다 보안성을 강화하면서 안전하게 설계할 수 있다. 이것은 마치 AWS 에서 VPC 설계시에 라우터를 조작해 네트워크를 구성하는것과 흡사하다.
이런것을 보면 Docker 는 단순한 컨테이너로서의 기능만을 위한게 아니라는 것을 알 수 있다. 네트워크 레이어 설계도 Docker 를 이해하는데 매우 중요한 요소다.
docker0 브릿지 네트워크 ip 변경
구글을 검색하면 많은 글들이 존재하는데, 최신의 docker 에서는 그렇게 많은 노력을 기울이지 않아도 docker0 브릿지 네트워크의 기본 ip 를 변경할 수 있다.
docker0 브릿지의 기본 네트워크 ip 정보를 변경하는 것은 서브넷, 게이트웨이등을 함께 변경하는 것을 뜻한다. 이는 /etc/docker/daemon.json 파일에서 bind ip 를 지정함으로써 쉽게 바꿀 수 있다.
1 2 3 4 |
$ sudo vim /etc/docker/daemon.json { "bip":"10.31.0.1/16" } |
이렇게 변경하고자하는 아이피를 지정하고 docker 를 다시 시작하면 된다. docker 를 재시작해야하는 일이기 때문에 많은 컨테이너가 운영중일때에 하기에는 문제가 있다.
따라서 이런경우에는 default 브릿지인 docker0 를 애써 변경하기 보다는 또다른 브릿지를 하나 생성하고 이것을 각각 컨테이너에 붙이는 방법으로 변경하는 것이 좋다.
사용자가 정의한 브릿지를 생성하는것을 사용자 정의 네트워크(User-Defined Network) 라고 한다.