Open vSwitch, VxLAN을 이용한 분산 Docker 컨테이너 간의 네트워크 연결(2/2)
- 전편에 이어서 -
이번에는 아래 그림에서와 같이 L3 network로 연결된 Docker 호스트간 L2 통신 방법(2)에 대해 다룬다. 네트워크가 다른 2개의 호스트 사이에 인터넷이 있든, 여러 개의 라우터가 존재하든, 호스트간의 연결이 가능하다면 이 방법을 쓸 수 있다. 예를 들어 아마존 AWS의 가상머신과 사무실의 인터넷이 연결된 Docker 호스트 내부의 Docker 사이의 연결도 물론 가능할 것이다.
사전 준비
- 라우터로 연결된 서로 다른 네트워크를 가지는 Docker Host(VM) 2대
- Docker Host #1: CentOS 7.3 Minimal Server(1611 ver), 10.255.20.10/24
- Docker Host #2: CentOS 7.3 Minimal Server(1611 ver), 10.255.30.10/24
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=172.16.0.0/12
각각의 도커 호스트에서 Default Docker 네트워크 설정
2개의 Docker 호스트의 Docker 네트워크를 기본으로 설정해 둔다. 본 과정에서는 큰 의미가 없으며, 별도 옵션을 주지 않고 컨테이너를 만들면 여기서 지정된 네트워크를 Docker 엔진이 자동으로 설정해 주게 될 것이다(본 포스팅과는 직접 연관 없는 부분으로, 이 과정은 생략 가능).
[root@dockerhost-1 ~#] /etc/sysconfig/docker-network
DOCKER_NETWORK_OPTIONS="--bip=172.17.0.1/16 --iptables=false --ip-masq=false --ip-forward=true"
[root@docker01 ~#] systemctl restart docker
[root@dockerhost-2 ~#] /etc/sysconfig/docker-network
DOCKER_NETWORK_OPTIONS="--bip=172.17.0.2/16 --iptables=false --ip-masq=false --ip-forward=true"
[root@docker02 ~#] systemctl restart docker
접속 경로의 연결을 위한 Open vSwitch Bridge와 VxLAN 설정
* dockerhost-1, dockerhost-2 에서 다음의 bash 스크립트를 작성해 두자(앞서 #1회의 내용을 스크립트 방식으로 바꾸어 보았다). 스크립트 내부의 커멘트를 참고하고, 붉은 표시된 부분을 잘 구분해서 사용해야 한다.
* 양쪽 호스트의 연결 방식은 네트워크 정보가 바뀐 것 외에는 전 편 #1에서의 방식과 거의 유사하다.
[root@dockerhost-1 ~]# vi set-vxlan-all.sh
#!/bin/bash
#
# Making VxLAN L2 tunnel over L3 network
#
# 1. Lower part - OVS bridge & VxLAN tunnel to remote
#
if [ $# != 2 ]; then
echo "Check params..."
echo "Usage: $0 ovs_sw_ip/cidr target_host_ip"
exit 1
fi
ovs-vsctl add-br ovs_sw0
ip addr add $1 dev ovs_sw0 && ip link set dev ovs_sw0 up
ovs-vsctl add-port ovs_sw0 vtep0 -- set interface vtep0 type=vxlan options:remote_ip=$2
#
# 2. VETH pair between OVS bridge and Linux bridge(docker0)
#
ip link add veth_sw0 type veth peer name veth_d0
ovs-vsctl add-port ovs_sw0 veth_sw0
brctl addif docker0 veth_d0
ip link set dev veth_sw0 up
ip link set dev veth_d0 up
#
echo "Done."
[root@dockerhost-1 ~]# chmod a+x set-vxlan-all.sh
[root@dockerhost-1 ~]# ./set-vxlan-all.sh 172.31.0.1/12 10.255.30.10
[root@dockerhost-2 ~]# vi set-vxlan-all.sh
내용은 dockerhost-1 와 같음
[root@dockerhost-2 ~]# chmod a+x set-vxlan-all.sh
[root@dockerhost-2 ~]# ./set-vxlan-all.sh 172.31.0.2/12 10.255.20.10
여기까지 설정하였다면 그림에서의 하단(Open vSwitch 브리지간)의 터널 개통(VxLAN L2 터널)이 정상적으로 되었는지 확인해 보기로 하자
[root@dockerhost-1 ~]# ping 172.31.0.2
PING 172.31.0.2 (172.31.0.2) 56(84) bytes of data.
64 bytes from 172.31.0.2: icmp_seq=1 ttl=64 time=2.15 ms
64 bytes from 172.31.0.2: icmp_seq=2 ttl=64 time=3.47 ms
64 bytes from 172.31.0.2: icmp_seq=3 ttl=64 time=2.17 ms
^C
--- 172.31.0.2 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2019ms
rtt min/avg/max/mdev = 2.157/2.599/3.471/0.618 ms
[root@dockerhost-1 ~]#
[root@dockerhost-2 ~]# ping 172.31.0.1
PING 172.31.0.1 (172.31.0.1) 56(84) bytes of data.
64 bytes from 172.31.0.1: icmp_seq=1 ttl=64 time=1.95 ms
64 bytes from 172.31.0.1: icmp_seq=2 ttl=64 time=6.44 ms
64 bytes from 172.31.0.1: icmp_seq=3 ttl=64 time=1.80 ms
^C
--- 172.31.0.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2027ms
rtt min/avg/max/mdev = 1.809/3.400/6.440/2.150 ms
[root@dockerhost-2 ~]#
컨테이너와 Linux Bridge 간의 Veth Pair를 통한 연결
하단의 터널이 뚫렸으므로 상단의 연결선을 만들어 보자. 전 편 #1 에서와 다른 점은, 2개의 Docker 컨테이너가 동일한 네트워크(여기서의 예를 들면 172.17.0.0/16)를 가져야 하므로, 컨테이너의 IP를 직접 지정해 주어야 한다는 것이다.
하지만 아쉽게도 Docker 환경에서 기본적으로는 컨테이너의 IP를 직접 지정할 수 있는 방법은 User Defined Network(docker network create ... & docker run --net=myown --ip=... 형식) 외에는 따로 없으므로, 다음에 제공되는 스크립트를 이용해서 컨테이너의 내부 인터페이스(eth0)를 docker0 브리지와 직접 연결해 주어야 한다(Docker network namespace 영역을 직접 다룸). 처리 내용은 아래의 스크립트 내용에 담겨 있으니 자세히 보고 이해해 두자. 각 Docker 호스트에서 동일하게 set-docker-network.sh 라는 이름으로 스크립트 파일을 만들고 chmod a+x 로 실행 가능하게 설정해 둔다.
#!/bin/bash
# $1: Docker ID
# $2: Docker0 IP/CIDR
# $3: Docker0 Gateway
#
# Checking params
#
if [ $# != 3 ]; then
echo "Check params..."
echo "Usage: $0 docker-id-or-name docker-ip/cidr gateway"
exit 1
fi
echo "Seting docker $1's network to... " $2", GW:" $3
#
# get pid of the docker
#
pid=$(sudo docker inspect -f '{{.State.Pid}}' $1)
# echo $pid
#
# Prepare docker network namespace
#
mkdir -p /var/run/netns
ln -s /proc/$pid/ns/net /var/run/netns/$pid
echo "Network namespace created"
#
# VETH pair create, link to docker0 linux bridge side
#
ip link add veth_doc$pid type veth peer name veth_con$pid
brctl addif docker0 veth_doc$pid
ip link set veth_doc$pid up
echo "Created veth pair and linked veth_doc$pid to docker0"
#
# Link VETH pair end-point to container's eth0
#
ip link set veth_con$pid netns $pid
ip netns exec $pid ip link set dev veth_con$pid name eth0
ip netns exec $pid ip link set eth0 up
echo "Linked veth_con$pid to container's eth0"
#
# Set network information of container's eth0
#
ip netns exec $pid ip addr add $2 dev eth0
ip netns exec $pid ip route add default via $3
echo "Set container's eth0 network ... Done"
dockerhost-1 호스트에서 새로운 Docker 컨테이너를 --net=none 으로 즉, 네트워크 없이 생성한다.
[root@dockerhost-1 ~]# docker run -i -t --name=docker1 --net=none busybox /bin/sh
/ # ctrl+p, ctrl+q 로 빠져나온다
[root@dockerhost-1 ~]# ./set-docker-network.sh docker1 172.17.0.10/16 172.17.0.1
[root@dockerhost-1 ~]# docker attach docker1
/ # ifconfig
eth0 Link encap:Ethernet HWaddr EA:88:7E:B1:E5:08
inet addr:172.17.0.10 Bcast:0.0.0.0 Mask:255.255.0.0
inet6 addr: fe80::e888:7eff:feb1:e508/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:71 errors:0 dropped:0 overruns:0 frame:0
TX packets:47 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:5743 (5.6 KiB) TX bytes:4016 (3.9 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)
/ #
* docker1 부분은 이름 없이 생성한 경우는 docker ps 로 보이는 컨테이너 ID를 사용해도 된다
dockerhost-2 호스트에서 새로운 Docker 컨테이너를 같은 방식으로 생성하되 IP 부분만, 같은 네트워크의 다른 IP 주소로 바꾼다.
[root@dockerhost-2 ~]# docker run -i -t --name=docker2 --net=none busybox /bin/sh
/ # ctrl+p, ctrl+q 로 빠져나온다
[root@dockerhost-2 ~]# ./set-docker-network.sh docker2 172.17.0.11/16 172.17.0.2
[root@dockerhost-2 ~]# docker attach docker2
/ # ifconfig
eth0 Link encap:Ethernet HWaddr 2E:69:95:56:52:FA
inet addr:172.17.0.11 Bcast:0.0.0.0 Mask:255.255.0.0
inet6 addr: fe80::2c69:95ff:fe56:52fa/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:74 errors:0 dropped:0 overruns:0 frame:0
TX packets:70 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:6206 (6.0 KiB) TX bytes:5262 (5.1 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)
/ # ping 172.17.0.10
PING 172.17.0.10 (172.17.0.10): 56 data bytes
64 bytes from 172.17.0.10: seq=0 ttl=64 time=2.427 ms
64 bytes from 172.17.0.10: seq=1 ttl=64 time=2.061 ms
64 bytes from 172.17.0.10: seq=2 ttl=64 time=1.767 ms
64 bytes from 172.17.0.10: seq=3 ttl=64 time=2.203 ms
^C
--- 172.17.0.10 ping statistics ---
4 packets transmitted, 4 packets received, 0% packet loss
round-trip min/avg/max = 1.767/2.114/2.427 ms
/ #
* docker2 컨테이너에서 docker1 컨테이너로 ping 연결이 가능한지 확인
컨테이너간 연결 최종 확인
마지막으로, 실제로 2개의 컨테이너간의 연결이 확실한지를 nc(NetCat)을 통해서 테스트해 보자.
dockerhost-1 호스트의 컨테이너에서 라우팅 상태 확인 후, 80 포트로 nc 리스닝
/ # 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
/ # nc -l -p 8080 0.0.0.0
Test from docker2 container...
/ #
dockerhost-2 호스트의 컨테이너에서 라우팅 상태 확인 후, docker1 컨테이너로 nc 접속(메시지 전송)
/ # route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 172.17.0.2 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
/ # echo "Test from docker2 container..." | nc 172.17.0.10 80
/ #
Linux 브리지, OVS 브리지 & VxLAN 설정 초기화를 위한 스크립트
Bonus: 생성된 브리지와 인터페이스 등의 설정 정보를 초기화하는 스크립트가 필요해 보인다. 생성된 순서를 되짚어 가며 삭제 등의 처리를 일일이 해야 하므로, 테스트 과정을 수정, 반복하는 경우의 귀찮은 일을 해결해 보려는 것으로 필수 과정은 아니니 참고로 보아 두면 좋겠다(스크립트 파일명은, 위의 set-vxlan-all.sh 의 설정 정보를 삭제해 주는 의미이므로 del-vxlan-all.sh 정도로 하면 되겠다).
#!/bin/bash
if [ $# != 1 ]; then
echo "Check params..."
echo "Usage: $0 ovs_sw_ip/cidr"
exit 1
fi
# Turn off each port of veth pair
ip link set dev veth_d0 down
ip link set dev veth_sw0 down
# Detach port veth_d0 on docker0 linux-bridge
brctl delif docker0 veth_d0
# Detach port veth_sw0 on ovs_sw0 OVS-bridge, then remove it
ovs-vsctl del-port ovs_sw0 veth_sw0
ip link del veth_sw0
# vtep0, ovs_sw0
ovs-vsctl del-port ovs_sw0 vtep0
ip addr del $1 dev ovs_sw0
ip link set dev ovs_sw0 down
ovs-vsctl del-br ovs_sw0
* 17번째 라인의 $1 파라미터 부분은 해당 Docker 호스트에서 설정한 ovs_sw0 브리지에 할당된 IP/CIDR(여기서는 172.31.0.1/12 또는 172.31.0.2/12) 부분을 지정한다
- Barracuda -