Recommanded Free YOUTUBE Lecture: <% selectedImage[1] %>

Contents

도커 네트워크

컨테이너들은 같은 노드에 있는 컨테이너들, 다른 노드에 있는 컨테이너들과 통신을 해야 한다. 이렇게 하기 위해서는 컨테이너를 위한 네트워크를 구성해야 한다. 이 문서에서는 다양한 네트워크 구성들을 살펴볼 것이다.

테스트 환경

VirtualBox로 아래와 같은 테스트 환경을 만들었다.

 테스트 환경

  1. 호스트 운영체제 즉, 개인 테스크탑 운영체제의 IP는 192.168.56.1 이다.
  2. 가상머신의 IP는 192.168.56.5 이다. 여기에 컨테이너가 실행된다.
  3. 컨테이너 네트워크는 172.17.0.0/16이다.

리눅스 브릿지 네트워크

같은 호스트 운영체제에 있는 컨테이너들은 같은 노드에 있는 다른 노드들과 통신을 할 수 있어야 한다. 가장 간단한 방법은 호스트 운영체제에 컨테이너 만을 위한 네트워크를 구성하는 거다.

리눅스 운영체제는 네트워크 브릿지 인터페이스를 지원한다. 브릿지는 MAC(Media Access Address) 기반으로 네트워크를 구성하는 단순한 네트워크 장비로 데이터 링크 계층(L2)에 있는 여러 개의 네트워크를 연결하는 일을 한다. L2 기반으로 패킷을 스위치 할 수 있기 때문에 L2 스위치로 부르기도 한다. 브릿지에 연결하는 네트워크는 NIC이 될 수도 있고 서로 다른 네트워크 세그먼트가 될 수도 있다. 네트워크 세그먼트를 연결하는 경우 두 개의 서로 다른 네트워크를 연결하는 브릿지 역할이 강조되기 때문에 브릿지라고 부른다. 이 문서에서는 여러 개의 NIC을 연결하는 L2 스위치의 역할에 집중 할 것이다. 아래는 네트워크 브릿지의 구성이다.

 네트워크 브릿지

브릿지는 여러 개의 포트들이 있고 여기에 (흔히 말하는 랜선을 이용)NIC이 연결된다. 브릿지는 각 포트에 연결된 NIC의 MAC 주소를 테이블에 저장을 하고 있다. 예를 들어 목적지가 e0:3f:49:47:49:03인 패킷이 있다면, PORT - 2번으로 패킷을 스위칭 한다.

여기에서 궁금 한점이 생길 것이다. NIC은 IP를 이용해서 통신을 한다. 그렇다면 IP에 대한 MAC 주소를 알고 있어야 브릿지를 이용한 통신이 가능 할 것이다. 운영체제들은 ARP를 이용해서 MAC과 IP를 맵핑하는 arp 테이블을 만든다. 예를들어 NIC-1을 가진 컴퓨터 운영체제는 아래와 같은 arp 테이블을 유지하고 있을 것이다.
IP 주소 MAC
172.17.0.3 e0:3f:49:47:49:03
172.17.0.4 e0:3f:49:5f:29:3a
172.17.0.3으로 패킷을 보낼 경우, ARP 테이블을 뒤져서 MAC 주소를 찾을 수 있다. 운영체제는 패킷에 MAC을 설정해서 브릿지로 보낸다. 브릿지는 패킷에 있는 MAC을 읽어서 PORT-4로 패킷을 보낸다.

지금까지 설명한 하드웨어 브릿지를 소프트웨어로 구현 한 것이 리눅스 브릿지다. 소프트웨어로 만들어졌다는 것과 물리적인 NIC 대신에 가상NIC(Veth)이 연결된 다는 것을 제외 하면, 하드웨어 브릿지와 동일하다.

 리눅스 브릿지

호스트 운영체제에 소프트웨어 브릿지가 만들어진다. 컨테이너가 만들어지면, 리눅스의 ip명령을 이용해서 veth 쌍 만든다. veth는 2개의 연결점을 가지고 있는데, 각각이 NIC 처럼 작동한다. veth 하나를 브릿지에 연결하고, 다른 하나는 컨테이너에 연결하면 브릿지와 컨테이너가 연결된다. veth는 아래와 같이 만든다.
# ip link add veth0 type veth peer name veth1
Veth를 만들고 연결하는 작업은 docker 가 알아서 해주기 때문에 지금 단계에서는 굳이 신경 쓸 필요는 없다. 언젠가 컨테이너 네트워크를 디버깅 하거나 네트워크를 직접 구출 할 때 사용하게 될 것이다. 자세한 내용은 Network Namespace문서를 참고하자.

도커 브릿지 네트워크

도커는 리눅스 브릿지를 기본 네트워크로 사용한다. 도커를 실행하면 docker0 이라는 이름의 브릿지 인터페이스를 확인 할 수 있다.
# ifconfig docker0
docker0   Link encap:Ethernet  HWaddr 02:42:7d:67:30:50  
          inet addr:172.17.0.1  Bcast:0.0.0.0  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)
brctl을 이용해서 브릿지를 관리 할 수 있다. 먼저 bridge-utils를 설치하자.
# apt-get install bridge-utils

brctl show명령으로 브릿지를 확인 할 수 있다. docker0브릿지가 있는 걸 확인 할 수 있다.
# brctl show
bridge name	bridge id		STP enabled	interfaces
docker0		8000.02427d673050	no		
이제 컨테이너를 만들면 가상 인터페이스가 docker0 에 연결된다. 컨테이너를 두개 만든 다음 브릿지 정보를 확인했다.
# brctl show docker0
bridge name	bridge id		STP enabled	interfaces
docker0		8000.02427d673050	no		vethacba87f
                                        vethf171fb7
두 개의 가상 인터페이스가 브릿지에 연결된 걸 확인 할 수 있다. 이들 가상 인터페이스는 ifconfig로도 확인 할 수 있다.
# ifconfig
vethacba87f Link encap:Ethernet  HWaddr 12:95:c2:82:af:2b  
          inet6 addr: fe80::1095:c2ff:fe82:af2b/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:16 errors:0 dropped:0 overruns:0 frame:0
          TX packets:33 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:1320 (1.3 KB)  TX bytes:2658 (2.6 KB)

vethf171fb7 Link encap:Ethernet  HWaddr e2:65:3c:bc:17:fe  
          inet6 addr: fe80::e065:3cff:febc:17fe/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:13 errors:0 dropped:0 overruns:0 frame:0
          TX packets:14 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:1026 (1.0 KB)  TX bytes:1068 (1.0 KB)

IP Masquerade를 이용한 인터넷 통신

브릿지에 연결된 컨테이너들은 private ip가 할당된다. Private ip로는 인터넷 통신이 불가능 하기 때문에, 출발지 IP를 Public ip로 바꾸는 방법으로 인터넷 통신이 가능하게 한다. 이 기술을 마스커레이드(Masquerade) 라고 한다. 흔히 NAT라고 부르기도 하는 이 기술은 가정이나 소규모 회사 네트워크에서 널리 사용하는 네트워크 기술이다. 아래 그림을 보자.

 IP Masquerade

IP가 172.17.0.2인 컨테이너가 8.8.8.8로 패킷을 보낸다고 가정해 보자. 이 패킷의 출발지 IP는 172.17.0.2다. 이 패킷을 받은 호스트 운영체제는 iptables를 이용해서 패킷의 출발지 IP를 자신의 IP로 변경을 한 다음 바깥으로 내보낸다. 이러한 Masquerade 과정을 2-3번 정도 더 거쳐서, 공인 IP를 가지고 있는 네트워크 장비에 도달하면(홈 네트워크라면 가정용 공유기가 될 것이다.) Public ip로 Masquerade 된 다음에 패킷을 목적지로 보낸다. 출발지 IP가 public ip이기 때문에 응답 패킷을 받게 될 거고, 각 네트워크 장비는 패킷을 검사해서 이번에는 도착지 IP를 바꾼다. 결국 컨테이너까지 패킷이 도착하게 되고 통신이 마무리 된다. Masquerade를 수행하는 운영체제들은 패킷을 추적하기 위한 connection tracking기능을 가지고 있다.

iptables로 호스트 운영체제의 Masquerade 설정을 볼 수 있다.
# iptables -t nat -L
// 생략 ....

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination         
MASQUERADE  all  --  172.17.0.0/16        anywhere            

// 생략 ....

이와 함께 net.ipv4.ip_forward가 1로 설정된다. 패킷 포워딩을 허가 하겠다는 의미다.
# sysctl  net.ipv4.ip_forward
net.ipv4.ip_forward = 1

Port forwarding

Masquerade 설정으로 컨테이너는 인터넷으로 패킷을 보낼 수 있게 됐다. 반대로 인터넷에서 컨테이너로 패킷을 보내는 경우를 생각해 보자. 인터넷에서 통신을 하려면 Public ip가 필요하다. 컨테이너가 가지고 있는 IP는 인터넷에 노출되지 않기 때문에, 컨테이너로는 직접 패킷을 보낼 수 없다. 이때 사용하는 기술이 포트 포워딩(Port forwarding)이다. 특정 포트로 들어오는 패킷을 다른 네트워크로 보내는 기능이다. 아래 그림은 포트 포워딩 과정을 묘사하고 있다.

 Port forwarding

80번 포트로 웹 서비스를 하는 컨테이너가 떠 있다고 가정해 보자. 이들 컨테이너는 private ip를 사용하기 때문에 외부에서 접근 할 수 없다. 포트포워딩을 사용하면 호스트 운영체제의 특정 포트로 들어오는 패킷을 다른 네트워크의 포트로 전송 할 수 있다. iptables 를 이용해서 포트포워딩 설정을 할 수 있다. 컨테이너를 실행 한 다음 아파치 웹 서버를 실행해 보자.
# docker run -it --name=web01 ubuntu /bin/bash
root@aa9780876045:/# apt-get install apachd2
....
root@aa9780876045:/# /etc/init.d/apache2 restart
 * Restarting web server apache2 
root@aa9780876045:/# netstat -na | grep 80
tcp6       0      0 :::80                   :::*                    LISTEN     
컨테이너의 IP는 172.17.0.2 이고, 호스트 운영체제의 ip 는 192.168.56.5다. 나는 192.168.56.1 호스트에서 172.17.0.2:80 으로 접근을 하려고 한다. 172.17.0.2 IP는 직접 접근 할 수 없기 때문에, 192.168.56.5:8888을 172.17.0.2:80으로 포트포워딩 하기로 했다.
192.168.56.5 # iptables -t nat -A PREROUTING -p tcp --dport 8888 -j DNAT --to-destination 172.17.0.2:80 
패킷포워딩은 목적지의 주소를 변경하는 NAT기술이다. 목적지(Destination) 주소를 NAT하기 때문에 DNAT라고 부른다. 보통 DNAT라는 말 대신에 이해하기 쉬운 포트포워딩이라는 말을 자주 사용한다. 위에 실행한 iptables 명령은 목적지 포트가 8888인 패킷의 경우 172.17.0.2:80으로 포트포워딩 하라는 규칙을 담고 있다. PREROUTING은 라우팅을 하기 전에 NAT를 적용하라는 의미다.

이제 192.168.56.1에서 테스트를 해보자.
# curl 192.168.56.5:8888 -I
HTTP/1.1 200 OK
Date: Mon, 04 Jul 2016 15:31:41 GMT
Server: Apache/2.4.7 (Ubuntu)
Last-Modified: Mon, 04 Jul 2016 15:20:56 GMT
ETag: "2cf6-536d0e1fd87a2"
Accept-Ranges: bytes
Content-Length: 11510
Vary: Accept-Encoding
Content-Type: text/html

도커에서 Port forwarding 구성

docker -p옵션으로 실행할 컨테이너에 대한 포트포워딩을 설정 할 수 있다.
# docker run -p 7777:80 --name=web03 -it ubuntu /bin/bash
root@f19e105fd185:/# 
도커역시 iptables를 이용해서 포트포워딩을 설정한다. iptables로 확인해 보자.
# iptables -t nat -L
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination         
DOCKER     all  --  anywhere             anywhere             ADDRTYPE match dst-type LOCAL
DNAT       tcp  --  anywhere             anywhere             tcp dpt:8888 to:172.17.0.2:80
....

Chain DOCKER (2 references)
target     prot opt source               destination         
RETURN     all  --  anywhere             anywhere            
DNAT       tcp  --  anywhere             anywhere             tcp dpt:7777 to:172.17.0.3:80
chain이 달라지긴 했으나 DNAT를 이용해서 포트포워딩을 하고 있는 것을 확인 할 수 있다.

포트포워딩은 컨테이너로 웹 서비스를 하기 위해서 사용하는 손 쉬운 방법이지만 해결해야 할 문제들이 있다.
  1. 호스트의 남는 포트를 관리 해야 한다. : iptables는 이미 사용중인 호스트 포트를 선택하더라도 에러를 출력하지 않는다. 따라서 포트포워딩을 할 때, 사용하는 포트인지를 검사해야 한다. SaaS/PaaS 시스템을 구축한다면, 컨테이너 포트포워딩을 위한 별도의 관리 소프트웨어를 개발(혹은 도입)해야 한다.
  2. docker를 일단 실행 한 뒤에는 docker 명령으로 포트포워딩 설정을 변경 할 수 없다. 직접 iptables 명령을 조작해야 한다. SaaS 시스템은 실행해야 할 애플리케이션이 고정 되므로 상관이 없지만, 컨테이너 기반으로 개발 환경을 만들경우에는 포트포워딩의 설정을 변경 할 수 있어야 한다. 포트포워딩 관리 소프트웨어를 개발 해야 한다.
컨테이너의 네트워크 관리를 위한 솔류션이 필요하다. XXX 장에서 자세히 다루도록 하겠다.

OpenVSwitch 브릿지를 이용한 네트워크 구성

리눅스에서 제공하는 브릿지는 패킷 스위칭 기능만 가지고 있다. 다른 어떤 기능도 가지고 있지 않다. 테스트나 개발 용도로 사용 할거라면 별 상관 없을 수도 있지만, SaaS/PaaS 환경을 만들려고 한다면 리눅스 브릿지의 한계가 문제가 될 수 있다.

다양한 가상화 환경에서 유연한 네트워크 환경의 구축을 지원하기 위해서 만든 소프트웨어가 Open vSwitch(이하 OVS)다. OVS는 패킷 스위칭외에도 다양한 프로토콜과 기능들을 지원한다. 아래 주요 기능들을 설명하고 있다.
  • NETFlow, sFlow, IPFIX, SPAN, RSPAN 등 다양한 프로토콜들을 지원한다.
  • LACP(Link Aggregation Control Protocol). 두 개 이상의 포트를 하나의 포트로 묶는 기술이다. 대역폭을 늘이기 위해서 사용한다. 예컨데 100M 포트 2개를 묶어서 200M 대역폭으로 만들 수 있다.
  • GRE(Generic Routing Encapsulation)의 지원. GRE는 멀리 떨어진 서로 다른 네트워크들을 연결하기 위해서 사용하는 터널링 프로토콜이다.
  • VLAN(802.1Q). Virtual LAN의 줄임말이다. 하나의 LAN을 여러 개의 네트워크로 나누는 기술이다. 가상화나 클라우드 환경을 만들 경우, 유저나 그룹에 독립적인 네트워크를 구성해줘야 하는 경우가 있다. VLAN을 이용하면 독립적인 네트워크를 구성 할 수 있다. 보통 멀티터넌트를 구현하기 위해서 사용한다.
  • VxLAN은 Virtual Extensible LAN의 줄임말이다. 이름에서 알 수 있듯이 VLAN을 확장한 기술이다. 관리 가능한 논리적인 네트워크의 수가 16백만으로 늘어났다(VLAN은 4096).
  • QoS(Quality of Service) : 네트워크 인터페이스, 유저, 애플리케이션 데이터 플로우 별로 QoS를 설정 할 수 있다.
  • 네트워크 인터페이스 별 Traffic policing 설정
  • NIC bonding, L4 해싱, Active backup
  • OpenFlow 프로토콜
  • IPv6
등등 사용 수준의 네트워크 환경 구성을 위한 다양한 기능들을 가지고 있다. VM 기반의 가상환경은 물론이고 컨테이너 기반 가상환경에도 확산되는 추세다.

OpenVSwitch 브릿지 생성 및 컨테이너 네트워크 구성

지금의 리눅스 브릿지를 Open vSwitch 브릿지로 바꾸기로 했다. 먼저 openvswitch 패키지를 설치한다.
# apt-get install openvswitch-switch
openvswitch 패키지를 설치하면 ovs-vsctl이라는 CLI 툴도 함께 설치된다. 이 툴을 이용해서 OVS 조작 할 수 있다. show명령으로 현재 상태를 확인 할 수 있다.
# ovs-vsctl show
74d4aba8-3f19-478a-a244-bf82ad0b9495
    ovs_version: "2.4.0"

OVS 브릿지를 만들자.
# ovs-vsctl add-br ovsbr-0
# ovs-vsctl show
74d4aba8-3f19-478a-a244-bf82ad0b9495
    Bridge "ovsbr-0"
        Port "ovsbr-0"
            Interface "ovsbr-0"
                type: internal
    ovs_version: "2.4.0"

Flat Network 구성 - 1

컨테이너 네트워크를 따로 분리하지 않고, LAN으로 부터 직접(direct) IP를 할당 받는 모델이다. 구성은 아래와 같다.

 Flat Network

Container Node에 별도의 컨테이너 네트워크를 구성하지 않고, 상위 네트워크인 192.168.56.0/24 네트워크에 직접 연결을 한다. 컨테이너의 네트워크 설정은 192.168.56.0/24에 있는 DHCP 서버가 담당한다. 이 방식의 장점과 단점은 아래와 같이 정리 할 수 있다.
  • 별도의 네트워크를 구성 할 필요가 없이 기존의 네트워크를 그대로 사용 할 수 있으므로 네트워크 구성이 단순해 진다.
  • 컨테이너는 VM에 비해서 다수를 동시에 실행 할 수 있다. 따라서 IP가 부족해질 수 있다. 만약 SaaS 환경이라면 크게 문제가 되지 않을 수 있다. 로드밸런서 클러스터를 VM이 아닌 컨테이너로 구성한다고 가정해보자. 로드밸런서는 대역폭을 확보해야 하기 때문에 하나의 노드에 얼정 개수 이상의 컨테이너를 만들 수가 없다. IP가 부족할 일도 없을 것이다. 그게 아닌 일반적인 컨테이너 실행 환경이라면, 상위 네트워크를 16비트로 하고 컨테이너 노드 네트워크를 24 비트로 하는 등의 응용을 해야 할 것이다.

Flat Network 구성 - 2

Container Node에 24bit ip 네트워크를 구성하고, 이 네트워크를 다시 16 bit 네트워크로 연결하는 방법이 있다.

 Flat Network 구성 - 2

각각의 Container 노드에 10.0.0.0/24, 10.0.1.0/24 네트워크를 구성하고 10.0.0.0/16 네트워크로 묶었다. 하나의 Container Node에 255개의 컨테이너를 만 수 있다. 이 정도면 부족함이 없을 것이다. 전체 네트워크에 최대 16581375 개의 컨테이너를 만들 수 있으니 역시 충분하다. KVM으로는 아래와 같이 테스트 할 수 있을 것이다.

 KVM 기반의 Flat Network - 2

괜찮은 방법이긴 하지만 OVS기반으로 만들 경우 모든 Container Node에 DHCP 서버를 운용해야 한다는 불편함이 있기는 하다. 리눅스 Bridge의 경우 네트워크만 24bit로 바꿔주면 될 거다. OVS의 여러 기능들이 필요하지 않다면, 리눅스 Bridge를 이용하자. 퍼블릭 서비스를 위한 인프라를 개발해야 한다면, OVS를 이용해야만 할 것이다. 이 방법은 AWS VPC에도 사용 할 수 있다. VPC 라우터에 static 하게 룰을 추가하면 된다.

Proxy Server를 이용한 구성

일반 응용 애플리케이션을 컨테이너로 실행 하려 할 때, 가장 큰 문제중 하나는 "포트 번호가 바뀔 수 있다는 점"이다. 프락시를 이용해서 이 문제를 해결 할 수 있다.

 Proxy를 이용한 SaaS 애플리케이션

애플리케이션 포트(Target Port)가 변경되더라도 Proxy 에서 포트를 고정 할 수 있으므로 컨테이너 네트워크 정보가 변경되더라도 애플리케이션을 찾아갈 수 있다. 이 방식은 표준포트를 사용 할 수 없다. 예컨데 Mysql의 표준포트는 3306 이지만 개발자나 관리자는 3306 포트가 아닌 다른(임의로 주어지는)포트를 사용해야 한다. 이 점만 무시할 수 있다면 괜찮은 방법이다.

VxLan을 이용한 Overlay Network 구성

VxLAN(Virtual Extensible LAN)은 클라우드 컨퓨팅에서 네트워크의 확장성 문제를 해결하기 위해서 만든 프로토콜이다. L2에서 작동하는 VLAN과는 달리 L4(UDP)에서 작동한다. 따라서 L3 네트워크에서 L2 네트워크의 구성이 가능하다. VLAN은 4096만큼의 네트워크를 만들 수 있으나 VxLAN은 16백만개의 네트워크를 만들 수 있어서, 클라우드에서의 멀티테넌트를 충분히 지원 할 수 있다.

OVS의 VxLAN을 이용하면 유저를 위한 격리된 네트워크를 구성 할 수 있다.

 OVS VxLAN 네트워크

VxLAN은 VNID라는 태그를 발급하는데, 이 태그를 이용해서 서로 다른 네트워크를 묶을 수 있다. 그림에서는 VNID 100으로 172.17.42.2 와 172.17.42.10을 묶고 있다. VxLAN을 다루익 위해서는 Open vSwitch와 VxLAN에 대한 정보들이 필요하다. 여기에서는 소개만 한다. 자세한 내용은 Open vSwitch with VxLAN문서를 참고하자.

Open vSwitch Plugin

Open vSwitch Docker 플러그인이 있다. 자세한 정보는 docker ovs plugin을 참고하자. 아직 테스트 해 보진 않았다.