Open vSwitch, VxLAN을 이용한 분산 Docker 컨테이너 간의 네트워크 연결(1/2)
제목이 다소 길지만 앞으로 2회에 걸쳐서 정리할 내용은 Open vSwitch의 VxLAN 터널링을 이용하여 L2 network로 연결된 Docker 호스트의 Docker 컨테이너간 L3 통신 방법(1)과, L3 network로 연결된 Docker 호스트간 L2 통신 방법(2)에 대해 다루고자 한다. 본 1편에서 다루고자 하는 내용을 그림으로 나타내면 다음 그림과 같다. 도커/컨테이너에 대한 기본적인 개념 설명은 여기서는 다루지 않으며, 이후에 도커의 네트워크에 대해 간략히 따로 다룰 예정이다.
그림을 보면, 2개의 호스트가 Flat한 L2 Network로 연결되어 있고(SDN: Software-Defined-Network 개념으로 보면 Underlay) 각각의 호스트 내에 Docker Container 들은 서로 다른 네트워크를 가지며, VxLAN 터널을 통한 L3 연결(SDN 개념으로 보면 Overlay)가 가능한 구조임을 알 수 있다. 그림과 같은 네트워크를 설계할 때에 특히 주의해야 할 점은, 하단의 OVS Bridge의 네트워크(10.0.0.0/8)가 상단의 Linux Bridge(docker0)의 네트워크(10.x.0.0/16)를 포함하는 구조를 가져야 한다는 것이다(참고: http://www.ipaddressguide.com/cidr).
사전 준비
- Flat Network로 연결된 Docker Host(VM) 2대
- Docker Host #1: CentOS 7.3 Minimal Server(1611 ver), 192.168.10.163/16
- Docker Host #2: CentOS 7.3 Minimal Server(1611 ver), 192.168.10.164/16
Docker & Open vSwitch 설치와 firewall 설정
* Docker 호스트에 EPEL 리포지터리와 RDO 프로젝트를 통해서 Docker와 Open vSwitch를 설치
(Open vSwich의 별도 설치 방법에 대해서는 http://bryan.wiki/276 참고)
yum clean all yum install -y epel-release https://www.rdoproject.org/repos/rdo-release.rpm yum install -y firewalld docker openvswitch bridge-utils yum update -y systemctl start openvswitch firewalld systemctl enable openvswitch firewalld
* VxLAN 터널링을 위한 패킷은 UDP 4789, 8472 포트를 통해 전송된다. firewall 설정에서 이 2개 포트를 모두 개방
firewall-cmd --add-port=4789/udp --add-port=8472/udp firewall-cmd --permanent --add-port=4789/udp --add-port=8472/udp
* trusted 영역에 대해 10.*.*.* 대역의 통신이 모두 가능하도록 개방(docker0 네트워크 10.x.0.0/16 을 서브넷으로 포함하는 상위 네트워크의 예: 10.0.0.0/8)
* 다른 예: docker0 네트워크를 172.17.0.0/16 이나 172.18.0.0/16 ... 등으로 할 경우에는 상위 네트워크를 172.16.0.0/12 로 하면 된다
firewall-cmd --permanent --zone=trusted --add-source=10.0.0.0/8
각각의 도커 호스트에서 Docker 네트워크 설정
* Docker 에서 네트워크가 가능하려면 --net=bridge(기본 모드) 또는 --net=host 옵션으로 컨테이너를 생성하게 된다. 컨테이너 생성시에 별도의 옵션을 지정하지 않으면 bridge 모드가 사용되는데, 여기서는 기본 모드인 bridge 모드를 사용할 것이며, /etc/sysconfig 디렉토리의 docker-network 설정 파일을 다음과 같이 설정한다.
[root@docker01 ~#] /etc/sysconfig/docker-network
DOCKER_NETWORK_OPTIONS="--bip=10.1.0.1/16 --iptables=false --ip-masq=false --ip-forward=true"
[root@docker01 ~#] systemctl restart docker
[root@docker02 ~#] /etc/sysconfig/docker-network
DOCKER_NETWORK_OPTIONS="--bip=10.2.0.1/16 --iptables=false --ip-masq=false --ip-forward=true"
[root@docker02 ~#] systemctl restart docker
--bip 는 Bridge IP 를 뜻하며, 컨테이너의 네트워크에서 자동적으로 10.1.0.1, 10.2.0.1 을 default gateway 로 바라보게 된다.
위와 같이 설정하고 Docker 컨테이너를 생성하면, docker01 호스트에서 첫 번째로 만들어지는 컨테이너의 IP는 10.1.0.2 가 될 것이다(docker02 에서는 10.2.0.2).
여기까지 끝이라면 좋겠지만 아쉽게도 그렇지 않다. 2개의 호스트에서 만든 각각의 컨테이너에서 서로 다른 컨테이너로 접속하려 하면? 당연히, 안된다. 서로 다른 네트워크간의 연결은 통로가 열려 있지 않으면 당연히 통신이 되지 않는다(아직 컨테이너 생성까지 진도는 나가지 않았지만, busybox 컨테이너를 docker01, docker02 에서 각각 만들고 docker01 호스트에서 ping 10.2.0.2 해보자. 당연히 안된다).
접속 경로의 연결을 위한 Open vSwitch Bridge와 VxLAN 설정
* docker01 에서는 다음과 같이 Open vSwitch 를 설정
[root@docker01 ~#] ovs-vsctl add-br ovs_sw0
[root@docker01 ~#] ip addr add 10.100.0.1/8 dev ovs_sw0 && ip link set dev ovs_sw0 up
[root@docker01 ~#] ovs-vsctl add-port ovs_sw0 vxlan0 -- set Interface vxlan0 type=vxlan options:remote_ip=192.168.10.164
* docker02 에서는 다음과 같이 Open vSwitch 를 설정
[root@docker02 ~#] ovs-vsctl add-br ovs_sw0
[root@docker02~#] ip addr add 10.100.0.2/8 dev ovs_sw0 && ip link set dev ovs_sw0 up
[root@docker02 ~#] ovs-vsctl add-port ovs_sw0 vxlan0 -- set Interface vxlan0 type=vxlan options:remote_ip=192.168.10.163
위의 그림에서 보듯이 각 Docker 호스트에 sw0 라는 Open vSwitch 브리지간의 터널을 개통하는 과정이다. 주의 깊게 보아야 하는 부분은 vxlan0 라는 VTEP(VxLAN Terminal End Point)가 상대편 Docker 호스트의 네트워크 IP를 바라보게 설정하는 것이다.
여기까지 설정하였다면 그림에서의 하단(Open vSwitch 브리지간)의 터널 개통이 정상적으로 되었는지 확인해 보기로 하자
[root@docker01 ~]# ping 10.100.0.2
PING 10.100.0.2 (10.100.0.2) 56(84) bytes of data.
64 bytes from 10.100.0.2: icmp_seq=1 ttl=64 time=2.43 ms
64 bytes from 10.100.0.2: icmp_seq=2 ttl=64 time=0.418 ms
64 bytes from 10.100.0.2: icmp_seq=3 ttl=64 time=0.333 ms
64 bytes from 10.100.0.2: icmp_seq=4 ttl=64 time=0.354 ms
64 bytes from 10.100.0.2: icmp_seq=5 ttl=64 time=0.410 ms
^C
--- 10.100.0.2 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4001ms
rtt min/avg/max/mdev = 0.333/0.790/2.438/0.824 ms
10.100.0.1/8 과 10.100.0.2/8 은 동일한 네트워크 대역이고, VxLAN 터널을 통해 서로 연결이 잘 되고 있음을 확인할 수 있다.
Linux Bridge와 Open vSwitch Bridge 간의 연결선, Veth Pair
하단의 터널이 뚫렸으므로 상단의 연결선을 만들어 보자. Docker container(브리지 모드일 경우)는 기본적으로 Linux Bridge와 자동으로 연결이 맺어 진다. 즉, Docker 엔진이 docker0 라는 디폴트 브리지(Linux Bridge)에 Docker 컨테이너의 네트워크 디바이스를 내부적으로 연결시켜 주게 된다.
그렇다면 우리는, 그 아래의 docker0와 sw0 사이의 연결을 맺어 주기만 하면 되는데, 이를 위해 VETH pair 라고 하는, 양쪽 끝이 연결되어 있는 연결 쌍(pair)이라는 도구를 사용해서 위 아래의 접점에 붙여 주면 된다.
그림에서 처럼, docker0(Linux Bridge)와 sw0(Open vSwitch Bridge)간의 연결을, Veth Pair 라고 하는 연결선을 통해서 맺어 줄 수 있다. 다음과 같이 해 보자. 이 연결선의 End Point(끝점) 들 중에서 veth_d0는 docker0 쪽으로, veth_sw0는 sw0 쪽으로 연결한다.
[root@docker01 ~]# ip link add veth_sw0 type veth peer name veth_d0
[root@docker01 ~]# ovs-vsctl add-port ovs_sw0 veth_sw0
[root@docker01 ~]# brctl addif docker0 veth_d0
[root@docker01 ~]# ip link set dev veth_sw0 up
[root@docker01 ~]# ip link set dev veth_d0 up
docker02 에서도 위와 같이 설정해 주자. End Point 들의 이름은 docker01 에서와 다르게 주어도 무관하지만 script 작성 등의 자동화를 위해서는 최대한 일관성 유지를 하는 것이 좋다.
[root@docker02 ~]# ip link add veth_sw0 type veth peer name veth_d0
[root@docker02 ~]# ovs-vsctl add-port ovs_sw0 veth_sw0
[root@docker02 ~]# brctl addif docker0 veth_d0
[root@docker02 ~]# ip link set dev veth_sw0 up
[root@docker02 ~]# ip link set dev veth_d0 up
각각의 Docker 호스트에서 다음과 같이 실행해서 브리지 설정을 확인한다.
[root@docker01 ~]# ovs-vsctl show
f3c8825d-73ba-4ee5-a136-3db14a32e990
Bridge "ovs_sw0"
Port "veth_sw0"
Interface "veth_sw0"
Port "ovs_sw0"
Interface "ovs_sw0"
type: internal
Port "vxlan0"
Interface "vxlan0"
type: vxlan
options: {remote_ip="192.168.10.164"}
ovs_version: "2.5.2"
[root@docker01 ~]# brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.0242f84de852 no veth_d0
Docker 컨테이너를 만들고 L3 연결 확인
docker01, docker02 호스트에서 각각 컨테이너 생성, 네트워크 확인
[root@docker01 ~]# docker run -i -t busybox /bin/sh
/ # ifconfig
eth0 Link encap:Ethernet HWaddr 02:42:0A:01:00:02
inet addr:10.1.0.2 Bcast:0.0.0.0 Mask:255.255.0.0
inet6 addr: fe80::42:aff:fe01:2/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:88 errors:0 dropped:0 overruns:0 frame:0
TX packets:51 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:7240 (7.0 KiB) TX bytes:4470 (4.3 KiB)
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)
[root@docker02 ~]# docker run -i -t busybox /bin/sh
/ # ifconfig
eth0 Link encap:Ethernet HWaddr 02:42:0A:02:00:03
inet addr:10.2.0.3 Bcast:0.0.0.0 Mask:255.255.0.0
inet6 addr: fe80::42:aff:fe02:3/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:24 errors:0 dropped:0 overruns:0 frame:0
TX packets:20 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:1880 (1.8 KiB) TX bytes:1656 (1.6 KiB)
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)
docker01 호스트의 컨테이너에서 docker02 호스트의 컨테이너와 ping 테스트
/ # ping 10.2.0.2
PING 10.2.0.2 (10.2.0.2): 56 data bytes
64 bytes from 10.2.0.2: seq=0 ttl=63 time=1.730 ms
64 bytes from 10.2.0.2: seq=1 ttl=63 time=0.528 ms
64 bytes from 10.2.0.2: seq=2 ttl=63 time=0.321 ms
마지막으로, 실제로 2개의 컨테이너간의 연결이 확실한지를 nc(NetCat)을 통해서 테스트해 보자. busybox 에 기본적으로 있는 기능이다.
docker01 호스트의 컨테이너에서 8080 포트로 nc 리스닝
/ # nc -l -p 8080 0.0.0.0
Test from docker02 container...
/ #
docker02 호스트의 컨테이너에서 docker01 호스트의 컨테이너로 nc 접속(메시지 전송)
/ # echo "Test from docker02 container..." | nc 10.1.0.2 8080
/ #
컨테이너에서 외부 네트워크로 접속하려면?
서로 다른 호스트에 각각 분리되어 있는 컨테이너간의 네트워크 연결은 가능해 졌지만, 원래의 Docker 네트워크 구조를 바꿔 버렸기 떄문에 현재의 설정으로는 컨테이너 사이의 네트워크만 가능할 뿐이다. 즉 외부 네트워크(192.168.10.* 를 벗어난)으로의 접속은 불가능한 상태다.
이 때에는 다음과 같이 firewall-cmd 를 통해서 목적지가 다를 경우 masquerading(NATting)이 되도록 설정해 주면 된다(각 docker 호스트에서 실행).
* (참고) 컨테이너 prompt 에서 exit 또는 ctrl-d 로 빠져나오게 되면 컨테이너가 생성되면서 실행된 터미널이 종료되면서 컨테이너는 STOP 상태로 빠지게 된다. 이 컨테이너에 재접속 하려면 "docker start" 로 다시 동작시켜야 하는데, STOP 되지 않도록 빠져나오려면 ctrl-p 와 ctrl-q 를 차례로 누르면 되며, 이후에 "docker attach" 로 재접속할 수 있게 된다.
[root@docker01 ~]# firewall-cmd --direct --add-rule ipv4 nat POSTROUTING 0 -s 10.0.0.0/8 ! -d 10.0.0.0/8 -j MASQUERADE
success
[root@docker01 ~]# firewall-cmd --permanent --direct --add-rule ipv4 nat POSTROUTING 0 -s 10.0.0.0/8 ! -d 10.0.0.0/8 -j MASQUERADE
success
[root@docker01 ~]#
[root@docker01 ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
871a0f2765b6 busybox "/bin/sh" 3 days ago Up 3 days ecstatic_hopper
[root@docker01 ~]# docker attach 871a0f2765b6
/ # ping yahoo.com
PING yahoo.com (206.190.36.45): 56 data bytes
64 bytes from 206.190.36.45: seq=0 ttl=48 time=158.904 ms
64 bytes from 206.190.36.45: seq=1 ttl=48 time=159.080 ms
64 bytes from 206.190.36.45: seq=2 ttl=48 time=159.194 ms
64 bytes from 206.190.36.45: seq=3 ttl=48 time=164.183 ms
^C
--- yahoo.com ping statistics ---
4 packets transmitted, 4 packets received, 0% packet loss
round-trip min/avg/max = 158.904/160.340/164.183 ms
- Barracuda -
[관련 글]
2017/06/08 - [Technical/Cloud, 가상화] - Open vSwitch, VxLAN을 이용한 분산 Docker 컨테이너 간의 네트워크 연결(2/2)