Menu

문서정보

목차

도커를 이용한 데이터 센터 구축이 목표다. 아마도 클라우드 인프라를 구축하는데, 가장 큰 어려움은 네트워크 구성일 것이다. 네트워크 구성을 집중적으로 살펴보려고 한다. 최종목표는 AWS VPC와 같은 네트워크 인프라를 구축하는 거다.

테스트 환경 구성

KVM을 기반으로 테스트 환경을 만들어 보기로 했다.

제대로 테스트하려면 두 개 이상의 물리적인 호스트와 스위치가 필요하다. 지금은 이런 여력이 없어서 KVM을 기반으로 구축하기로 했다. 하이퍼바이저를 기반으로 (개인 PC에)가상환경으로 구성 할 경우, VM위에 VM을 올리는 잉여스러운 삽질(인셉션 프로젝트..)을 해야 했다. 도커로 구축할 경우 한번의 가상화로 실제 환경과 거의 유사한 환경을 만들 수 있다.

VM 간 네트워크 구성

VM은 물리적 인프라에서 호스트에 해당한다. VM에서 컨테이너를 띄우는 것으로 물리적인 환경에서 컨테이너를 띄우는 것을 흉내낼 수 있다. OVS를 이용해서 브릿지 네트워크를 구성하기로 했다.

  1. OVS 브릿지 이름 : br0
  2. 브릿지 네트워크 : 192.168.5.0/24
  3. Gateway IP : 192.168.5.1
  4. tap : VM과의 연결에 사용한다.
  5. VM 인터페이스의 주소 : dhcp를 구성하는 것도 재미있겠지만, (dhcp 서버 구성이)귀찮으니 일단 static 하게 설정한다.

OVS 브릿지 생성

브릿지 인터페이스 br0 의 네트워크 설정정보다.
# cat /etc/network/interface
auto br0
iface br0 inet static
  address 192.168.5.1
  netmask 255.255.255.0

브릿지 인터페이스 br0을 만든다.
# ovs-vsctl add-br br0

네트워크 설정을 살펴보자.
# ovs-vsctl show 
41683821-59d5-4443-8cc5-c95dae7deaae
    Bridge "br0"
        Port "br0"
            Interface "br0"
                type: internal
    ovs_version: "2.3.1"

# ifconfig br0 
br0       Link encap:Ethernet  HWaddr a2:90:b8:95:c1:48  
          inet addr:192.168.5.1  Bcast:192.168.5.255  Mask:255.255.255.0
          inet6 addr: fe80::a090:b8ff:fe95:c148/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:40 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:0 (0.0 B)  TX bytes:5827 (5.8 KB)

# route -n 
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         192.168.43.1    0.0.0.0         UG    1024   0        0 wlan0
169.254.0.0     0.0.0.0         255.255.0.0     U     1000   0        0 br0
192.168.5.0     0.0.0.0         255.255.255.0   U     0      0        0 br0
192.168.43.0    0.0.0.0         255.255.255.0   U     0      0        0 wlan0
이렇게 만든 브릿지에 Tap 인터페이스를 연결하고, 여기에 VM을 붙이면 된다.

OVS 스크립트 파일

KVM으로 VM을 만들 때, 사용 할 네트워크 스크립트를 만들었다. ovs-ifup은 VM을 올릴 때 사용하고, ovs-ifdown은 VM을 내릴 때 사용한다.
# cat ovs-ifup  
#!/bin/sh
# br0 ovs 브릿지에 연결한다.
switch='br0'
# $1은 tap 인터페이스다.
/sbin/ifconfig $1 0.0.0.0 up
# tap 인터페이스를 br0 브릿지에 연결한다.
ovs-vsctl add-port ${switch} $1

# cat ovs-ifdown  
#!/bin/sh
switch='br0'
/sbin/ifconfig $1 0.0.0.0 down
ovs-vsctl del-port ${switch} $1

브릿지에 연결된 KVM 실행과 네트워크 설정

vdisk1.img, vdisk2.img, vdis3.img, vdis4.img 4개의 VM 이미지를 준비했다.준비하는 과정은 생략한다.

브릿지 네트워크에 연결된 KVM을 실행했다.
# kvm -m 512 -net nic,macaddr=00:00:00:00:10:01 \
  -net tap,script=/etc/ovs-ifup,downscript=/etc/ovs-ifdown vdisk1.img
VM을 시작하면 ovs-ifup을 실행해서 tap을 만들고 br0에 연결을 한다. VM이 종료되면 ovs-ifdown을 호출해서 tap을 삭제하는 작업을 한다.

VM의 네트워크는 아래와 같이 설정했다.
# cat /etc/network/interfaces
auto eth0
iface eth0 inet static 
    address 192.168.5.2 
    netmask 255.255.255.0
    gateway 192.168.5.1

호스트 네트워크에서 각 VM으로, VM에서 VM으로 통신이 되는지 확인하자.
# ping 192.168.5.2
PING 192.168.5.2 (192.168.5.2) 56(84) bytes of data.
64 bytes from 192.168.5.2: icmp_seq=1 ttl=64 time=0.421 ms
64 bytes from 192.168.5.2: icmp_seq=2 ttl=64 time=0.172 ms

호스트 시스템의 브릿지 상태다.
# ovs-vsctl show 
41683821-59d5-4443-8cc5-c95dae7deaae
    Bridge "br0"
        Port "tap0"
            Interface "tap0"
        Port "tap3"
            Interface "tap3"
        Port "tap1"
            Interface "tap1"
        Port "tap2"
            Interface "tap2"
        Port "br0"
            Interface "br0"
                type: internal
    ovs_version: "2.3.1"
이렇게 해서 VM을 띄우고, VM을 위한 네트워크를 만들었다. 이제 이 위에 도커 네트워크를 구성한다.

Simple 네트워크 구성

도커 기본 네트워크

구성

(아무런 손도 대지 않은)지금의 네트워크 구성은 아래와 같다.

  1. 도커 네트워크는 서로 독립적이다. VM-01.Docker에서 VM-02.Docker로는 통신할 수 없다.
  2. 같은 VM에 있는 도커들은 서로 통신할 수 있으며, 스위치(OVS)과도 통신 할 수 있다.
2가 가능한 이유는 도커 네트워크를 구성하면서 마스커레이딩(SNAT)을 하기 때문이다. 도커 레벨의 네트워크는 아래와 같이 구성된다.

다른 VM 도커와의 통신

이 구성에서 다른 VM의 도커와 통신하기 위한 가장 간단한 방법은 도커의 포트포워딩 기능을 이용하는 거다.

VM2에서 5000:22로 포트포워딩을 적용한 도커를 띄웠다.
# docker run -p 5000:22 -i -t ubuntu bash

VM1.도커에서 VM2.도커로 연결 테스트
# ssh yundresm@192.168.5.3 -p 5000
The authenticity of host '[192.168.5.3]:5000 ([192.168.5.3]:5000)' can't be established.

VM2에서 iptables로 도커의 포트포워딩룰을 살펴봤다.
# iptables -t nat -L 
......
Chain DOCKER (2 references)
target     prot opt source      destination         
DNAT       tcp  --  anywhere    anywhere      tcp dpt:5000 to:172.17.0.4:22

인터넷으로 연결

호스트에 SNAT 설정하는 것으로 도커가 인터넷에 접근하게 할 수 있다.
# iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
도커에서 출발한 패킷은 총 2번의 SNAT를 거쳐서, 인터넷으로 향하게 된다.

Flat 네트워크 구성

앞선 기본 네트워크 구성은 "다른 VM의 도커 컨테이너와 네트워크가 단절 된 다는" 큰 문제를 가지고 있다. 포트포워딩을 이용해서 통신하는 방법이 있기는 한데, 좋은 방법은 아니다. 도커 컨테이너들을 같은 브로드캐스트 영역으로 묶어서, L2 통신이 가능하게 하는게 좋은 방법이다.(가장 좋은 방법이라고 하지 않은 이유는 오버레이 네트워크를 구성 L3에서 브로드캐스팅 영역을 만들 수 있기 때문이다. VXLAN, GRE에서 자세히 다룬다.)

가장 간단한 구성은 모든 도커 컨테이너를 L2 네트워크에 묶어버리는 방법이다. 나는 지금 홈 네트워크에서 테스트 중이니, 대략 아래와 같은 구성이 될테다.

내 홈네트워크는 공유기가 구성을 하고 있다. 공유기의 네트워크는 192.168.219.1/24이며, DHCP 서버를 이용해서 홈 네트워크에 연결하는 기기들의 네트워크를 설정한다. 도커 컨테이너가 올라오면, 모두 공유기 네트워크에 직접 붙게 구성을 한다. 그림에서 OVS와 Bridge를 제거하고, 도커 컨테이너가 직접 공유기에 붙는다고 생각하면 이해가 쉬울 거다.

먼저 호스트의 네트워크를 바꿔서 VM들이 공유기 네트워크에 직접 연결되게 만들어야 한다.
  1. OVS 브릿지 스위치 br0를 만든다.
    # ovs-vsctl add-br br0
  2. br0에 eth0을 add 한다.
    # ovs-vsctl add-port br0 eth0
  3. br0을 uplink로 만들기 위해서 ip를 할당한다.
    # cat /etc/network/interfaces
    auto br0
    iface br0 inet dhcp 
  4. eth0를 초기화 한다.
    # ifconfig eth0 0
  5. 이제 VM을 만들면, br0를 통해서 공유기 네트워크에 연결된다. 현재 각 VM은 static 하게 ip를 설정했는데, dhcp로 설정하도록 변경한다.
    # cat /etc/network/interfaces
    auto eth0 
    iface eth0 inet dhcp 
여기 까지 했으면, vm이 dhcp로 네트워크가 설정되는 걸 확인 할 수 있을 거다.

다음 도커 컨테이너를 공유기 네트워크에 물려야 한다. 기본 원리는 VM과 동일하다. 즉
  1. eth0의 네트워크 설정을 초기화 하고
  2. docker0 브릿지를 uplink로 만든다.
  3. 도커 컨테이너를 실행한다.
1번은 동일하다.
# ifconfig eth0 0 

2번을 위해서는 작업이 좀 필요하다. 도커의 기본 브릿지 네트워크를 사용할 경우, 브릿지의 이런 저런 설정들(브릿지 고유의 dhcp 설정 등)을 따라가서 원하는 대로 설정하기가 어렵기 때문이다.

먼저 기본 docker0 브릿지를 삭제한다.
# brctl delbr docker0
커스텀 브릿지에 연결하도록 도커 설정을 변경한다. 이름은 docker0으로 했다. 이름만 같을 뿐, (예전 브릿지와는) 전혀 다른 브릿지다.
# cat /etc/default/docker
DOCKER_OPTS="-b=docker0"

docker0이 uplink 역할을 하도록 네트워크 설정을 한다.
# cat /etc/network/interfaces
......
auto docker0=docker0
iface docker0 inet dhcp
도커 서버를 재시작 한다.
# service docker restart
docker0은 dhcp를 통해서 네트워크 설정이 된다. 여기까지 작업한 결과 docker 네트워크를 홈 네트워크에 연결했다.

이제 컨테이너를 실행하면, 공유기 네트워크에 물려서 IP를 할당받는 걸 확인 할 수 있을 거다. 모든 도커 컨테이너가 같은 (공유기)네트워크에 물려 있으니, IP를 이용해서 직접 통신이 가능하다.

평가

내가 구성한 네트워크는 아래와 같이 묘사할 수 있다.

목적에 따라서 평가가 달라지는 모델이다. 인스턴스들이 마치 인터넷을 구성하는 노드들 처럼 L3에서 독립적으로 구성이 된다. 매우 간단한 모델로 구성이 쉽다는 장점이 있다. 설계, 구성, 운용 모든게 쉽다. 일반 유저를 대상으로 퍼블릭 서비스를 하지 않고, 내부 인프라 용으로 사용할 경우 괜찮은 모델이 될 수 있다.

반면 단점은 네트워크 격리(isolation)이 제한 적이다 라는 단점이 있다. Security group 기반의 네트워크 격리만 가능하다. 서비스별 네트워크 할당이라든지 이런거 안된다.

인터넷 서비스는 L4 switch를 이용하거나 Haproxy 클러스터를 구성하는 방법이 있겠다.

Advanced 네트워크 구성을 위한 준비

도커 컨테이너를 OpenvSwitch와 연결하기

도커의 기본 네트워크는 brctl로 관리하는 간단한 브릿지다. 이 브릿지는 간단한 기능만을 제공하기 때문에 VLAN, GRE, VXLAN등의 고급 네트워크 구성이 힘들다. 고급 네트워크를 구성하려면 역시 OVS에 연결해야 한다.

해서 VLAN, GRE, VXLAN을 다루기 전에 도커와 OpenvSwitch를 연결하는 방법을 먼저 살펴보기로 했다. 테스트를 위한 네트워크 구성은 2장 VM간 네트워크 구성의 static 구성이다. 192.168.5.2 VM 에서 작업을 한다.

도커 컨테이너를 실행 할 때 네트워크 타입을 --net=none 로 한다. 이렇게 하면 네트워크가 없는 컨테이너가 실행된다.
# docker.io run --net=none -i -t ubuntu /bin/bash
root@d605bcab2877:/# ifconfig 
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:0 
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)
OVS 브릿지 br0 만들고, veth 쌍(pair)를 만들어서 하나는 br0에 두고 다른 하나는 컨테이너(d605bcab2877)의 네트워크 디바이스 eth0 으로 추가를 할 것이다.

OVS 브릿지를 만든다.
# ovs-vsctl add-br br0
br0의 네트워크 정보는 아래와 같다.
# cat /etc/network/interfaces 
iface br0 inet static
        address 172.17.42.1
        network 172.17.0.0
        netmask 255.255.0.0

다른 쉘을 띄워서 컨테이너의 프로세스 ID를 찾아서 변수에 저장한다.
# docker inspect -f '{{.State.Pid}}' d605bcab2877 
1794
# pid=1794 
# echo $pid 
1794

이 프로세스의 네트워크 네임스페이스를 저장할 디렉토리 /var/run/netns 를 만든다.
# mkdir -p /var/run/netns 
# ln -s /proc/$pid/ns/net /var/run/netns/$pid 

veth 쌍을 만든다. 하나는 veth-a, 다른 하나는 veth-b로 했다. veth네트워크 네임스페이스에서 간단히 설명하고 있다.
# ip link add veth-a type veth peer name veth-b

veth-a를 br0에 추가하고, veth-a를 up link 한다.
# ovs-vsctl add-port br0 veth-a
# ip link set veth-a up
# ovs-vsctl show 
3e95bbe3-88b3-4b4d-b39f-b9a24cb53a8e
    Bridge "br0"
        Port veth-a
            Interface veth-a
        Port "br0"
            Interface "br0"
                type: internal
    ovs_version: "2.1.3"

veth-b를 컨테이너의 네트워크 네임스페이스에 추가한다.
# ip link set veth-b netns $pid
# ip netns exec $pid ip link set dev veth-b name eth0
# ip netns exec $pid ip link set eth0 address 12:34:56:78:9a:bc
# ip netns exec $pid ip link set eth0 up
# ip netns exec $pid ip addr add 172.17.42.2/16 dev eth0
# ip netns exec $pid ip route add default via 172.17.42.1
컨테이너에 eht0 네트워크가 올라온 걸 확인 할 수 있을 거다.
# ifconfig
eth0      Link encap:Ethernet  HWaddr 12:34:56:78:9a:bc  
          inet addr:172.17.42.2  Bcast:0.0.0.0  Mask:255.255.0.0
          inet6 addr: fe80::1034:56ff:fe78:9abc/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:16 errors:0 dropped:0 overruns:0 frame:0
          TX packets:8 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:1296 (1.2 KB)  TX bytes:648 (648.0 B)

ping 테스트
# 게이트웨이와의 통신
# ping 172.17.42.1
PING 172.17.42.1 (172.17.42.1) 56(84) bytes of data.
64 bytes from 172.17.42.1: icmp_seq=1 ttl=64 time=0.709 ms
64 bytes from 172.17.42.1: icmp_seq=2 ttl=64 time=0.072 ms

# 호스트 운영체제와의 통신
# ping 192.168.5.1
PING 192.168.5.1 (192.168.5.1) 56(84) bytes of data.
64 bytes from 192.168.5.1: icmp_seq=1 ttl=63 time=2.34 ms
64 bytes from 192.168.5.1: icmp_seq=2 ttl=63 time=0.201 ms

# 인터넷과의 통신 
ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=40 time=84.0 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=40 time=78.6 ms

지금의 네트워크 구성이다.

이것으로 컨테이너의 네트워크를 OVS 브릿지로 대체 했다. 이제 OVS의 기능들을 이용해서 고급 네트워크 환경을 구축할 준비가 끝났다.

Multi NIC 구성

하나의 NIC을 이용해서 도커 네트워크를 테스트하려면 애로사항이 꽃필 것이다. 브릿지 네트워크 설정을 조금만 잘못해도 네트워크가 끊겨버리기 때문이다. 그래서 VM의 네트워크 인터페이스를 2개로 분리하기로 했다.
  1. br0 : 도커 네트워크
  2. br1 : 관리 네트워크
도커 네트워크는 도커들간 통신만을 위해서 사용한다. 관리자는 관리 네트워크에 접속해서, 도커 컨테이너 실행, 네트워크 설정, 패키지 관리, 도커 네트워크 문제 해결과 같은 작업을 한다. 관리자는 br1 인터페이스로 접속을 하기 때문에, 도커 네트워크 구성을 하다가 실수를 하더라도, 네트워크가 끊기는 일은 없을 것이다. 구성은 아래와 같다.

br1 브릿지를 새로 만들었다.
# ovs-vsctl add-br br1

br1 브릿지의 네트워크를 설정했다.
# cat /etc/network/interfaces
auto br1
iface br1 inet static 
  address 192.168.200.1
  netmask 255.255.255.0

vm 실행 명령은 대략 아래와 같다.(관리를 위해서는 적당한 스크립트를 만들어야 겠지만 일단 패스)
# kvm -m 512 -net nic,macaddr=00:00:00:00:10:01 \
  -net tap,script=/etc/ovs-ifup,downscript=/etc/ovs-ifdown \
  -net nic,macaddr=00:00:00:00:02:01 \
  -net tap,ifname=tap100,script=no  vdisk1.img

vm은 eth0, eth1 두개의 인터페이스를 가진다. eth1의 네트워크 설정은 아래와 같다.
auto eth1
    iface eth1 inet static 
    address 192.168.200.2
    network 192.168.200.0
    netmask 255.255.255.0
    gateway 192.168.200.1

vm에 도커 네트워크를 위한 ovs 브릿지를 만든다.
# ovs-vsctl add-br br0
# cat /etc/network/interfaces
iface br0 inet static
    address 192.168.5.2
    network 192.168.5.0
    netmask 255.255.255.0
    gateway 192.168.5.1

도커 네트워크 구성을 위한 스크립트를 만들었다. 에러처리 같은 건 기대하지 말자(귀찮다).
#!/bin/bash
# $1 : 네트워크 작업할 컨테이너의 아이디 
cid=$1
ip=$2

# 도커를 위한 MAC ADDRESS
macaddr=$(echo $RANDOM|md5sum|sed 's/^\(..\)\(..\)\(..\)\(..\)\(..\).*$/02:\1:\2:\3:\4:\5/')

echo "Network Configuration"
echo "MAC : $macaddr"
echo "IP  : $ip"
echo "====================="
# 컨테이너의 process id를 이용해서 veth 쌍을 만든다.
# 컨테이너의 process id는 네트워크 도메인이름으로도 사용한다.
pid=`docker inspect -f '{{.State.Pid}}' $cid` 

mkdir -p /var/run/netns
ln -s /proc/$pid/ns/net /var/run/netns/$pid

vetha="veth$pid-1"
vethb="veth$pid-2"
ip link add $vetha type veth peer name $vethb 

echo "Create veth pair : $vetha <-> $vethb" 

# 브릿지에 $vetha를 추가 
ovs-vsctl add-port br0 $vetha
ip link set $vetha up

# 도커 컨테이너를 위한 네트워크 설정 
ip link set $vethb netns $pid
ip netns exec $pid ip link set dev $vethb name eth0
ip netns exec $pid ip link set eth0 address $macaddr
ip netns exec $pid ip link set eth0 up
ip netns exec $pid ip addr add $ip/24 dev eth0
ip netns exec $pid ip route add default via 192.168.5.1
테스트를 위해서 이름이 2eec9bd8b289인 도커 컨테이너를 실행했다. 스크립트 실행
# ./ovs-work.sh 2eec9bd8b289 192.168.5.202
Network Configuration
MAC : 02:4f:3b:a7:37:3a
IP  : 192.168.5.202
=====================
Create veth pair : veth5692-1 <-> veth5692-2

도커 컨테이너에서 네트워크 테스트
# ifconfig eth0 
eth0    Link encap:Ethernet  HWaddr 02:4f:3b:a7:37:3a  
        inet addr:192.168.5.202  Bcast:0.0.0.0  Mask:255.255.255.0
        inet6 addr: fe80::4f:3bff:fea7:373a/64 Scope:Link
        UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
        RX packets:8 errors:0 dropped:0 overruns:0 frame:0
        TX packets:8 errors:0 dropped:0 overruns:0 carrier:0
        collisions:0 txqueuelen:1000 
        RX bytes:648 (648.0 B)  TX bytes:648 (648.0 B)

# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         192.168.5.1     0.0.0.0         UG    0      0        0 eth0
192.168.5.0     0.0.0.0         255.255.255.0   U     0      0        0 eth0

# ping 192.168.5.1
PING 192.168.5.1 (192.168.5.1) 56(84) bytes of data.
64 bytes from 192.168.5.1: icmp_seq=1 ttl=64 time=1.27 ms
64 bytes from 192.168.5.1: icmp_seq=2 ttl=64 time=0.211 ms
성공!!

이렇게 해서 VM에 멀티닉을 붙이고, 관리 트래픽과 (도커를 위한) 서비스 트래픽을 분리 함으로써, 좀더 편하게 테스트 할 수 있는 환경을 만들었다. 실제 도커 기반으로 클라우드 인프라를 구성한다면, 호스트에 3개 이상의 NIC(서비스 트래픽, 관리 트래픽, 스토리지 트래픽)를 붙일 테니 일반적인 구성이라고 볼 수 있겠다.

실제 물리적 환경으로 구성해 본다면 대략 아래와 같은 모습이 될 것이다.

Advanced 네트워크

VLAN 네트워크 구성

Flat Network는 아래와 문제점들이 있다.
  1. 모든 컨테이너가 같은 브로드캐스트 영역에 놓인다.
  2. 멀티터넌트 환경을 만들 수 없다.
Flat Network의 구성을 변경하지 않고, 위 문제를 해결 할 수 있는 가장 간단한 방법은 VLAN 구성이다. 대략 4K 정도의 격리된 네트워크를 구성 할 수 있을 테니, 중/소 규모에서는 적은비용으로 사용 할 만한 방법이다.

100, 200 두 개의 VLAN을 만든 다음, 각 VLAN에 연결된 도커들끼리 통신이 가능하게 하는게 목적이다. 네트워크 구성은 아래와 같을 것이다.

VXLAN 네트워크 구성

Open vSwitch를 이용한 VXLAN 네트워크 구축 참고

GRE 네트워크 구성