메뉴

문서정보

목차

Network Configuration

도커는 시작 할 때, 호스트 머신에 docker0이라는 이름의 가상 인터페이스(virtual interface)를 만든다. docker0에는 rfc1918에 따라서 사설 네트워크 ip(private ip)가 할당되된다. 보통 171.17.42.1/16을 사용한다. 16비트의 크기를 사용할 수 있으니, 대략 65,534의 도커 컨테이너에 ip를 할당 할 수 있다. 호스트에서 사용하기에는 충분히 큰 숫자라고 할 수 있겠다. MAC 주소는 ARP 충돌을 회피할 수 있도록 IP주소와 함께 만드는데, 02:42:ac:11:00:00 에서 02:42:ac:11:ff:ff범위에서 주소를 할당 한다.

그러나 docker0은 일반적인 인터페이스가 아닌, 가상 Ethernet bridge로 여기에 attached된 다른 네트워크 인터페이스들 간의 패킷을 포워딩 하는 역할을 한다. 호스트와 컨테이너, 컨테이너와 컨테이너가 서로 패킷을 주고 받는 통로라고 보면 된다.

컨테이너들은 eth0인터페이스를 이용해서 통신을 하는데, 이를 위해서 vethAQI2QT와 같은 이름의 가상 인터페이스를 만든다. 이들 가상 인터페이스(veth*)들은 docker0 브릿지에 attach 된다. 여기에 attach된 컨테이너들은 같은 서브넷을 공유하기 때문에, 자유롭게 통신이 가능하다.

설정 가이드

도커 네트워크 설정을 위한 주요 옵션들을 살펴보자. 몇 몇 네트워크 옵션들은 docker run에서만 사용할 수 있다.

Configuring DNS

hostname과 DNS를 설정한 커스텀이미지를 만들지 않고도 도커 컨테이너에 호스트이름과 DNS를 설정할 수 있다. 약간의 트릭을 사용하는데, 호스트 운영체제의 /etc 파일의 내용을 컨테이너 내부에 overlay해서 사용한다. 컨테이너에서 mount 정보를 이용해서 overlay한 파일의 목록을 확인할 수 있다.
# mount
/dev/mapper/ubuntu--vg-root on /etc/resolv.conf type ext4 ... 
/dev/mapper/ubuntu--vg-root on /etc/hostname type ext4 ...
/dev/mapper/ubuntu--vg-root on /etc/hosts type ext4 ... 
즉 모든 도커컨테이너들은 호스트 운영체제의 resolv.conf를 공유해서 사용한다.

이들 네트워크 설정을 변경할 수 있는 docker 옵션들이 있다. dns=IP_ADDRESS...와 dns-search=DOMAIN... 옵션이 없을 경우, 호스트 운영체제의 /etc/resolv.conf를 그대로 복사해서 사용한다.

컨테이너와 인터넷간의 통신

컨테이너가 호스트 바깥으로 연결하기 위해서는 두 가지 사항을 준비해야 한다.
  1. 호스트 운영체제의 IP 패킷 포워딩 정책 확인. 운영체제의 ip_forward가 활성화 돼야 한다. 이 파라메터의 값이 1인 경우에만 호스트 바깥으로 통신을 할 수 있다. sysctl을 이용해서 설정을 변경 할 수 있다.
    # sysctl net.ipv4.conf.all.forwarding
    net.ipv4.conf.all.forwarding = 0
    # sysctl net.ipv4.conf.all.forwarding=1
    net.ipv4.conf.all.forwarding = 1
    # sysctl net.ipv4.conf.all.forwarding
    net.ipv4.conf.all.forwarding = 1
  2. iptable이 특정 연결을 허용하는지를 확인해야 한다. 설정을 위해서 수고할 필요는 없다. 도커를 설치하면, 자동으로 포워딩 룰을 가지고 있는 DOCKER 체인을 만든다.
도커는 이미 설정된 DOCKER 체인을 수정하거나 삭제하지 않는다. 따라서 유저는 필요에 따라서 자신의 룰을 만들 수 있다.

도커의 기본 포워딩 룰은 모든 외부 source ip를 허용하고 있다. 만약 특정 IP나 네트워크로 포워딩을 제한하고 싶다면 DOCKER 체인의 첫 부분(top)에 부정(negated) 규칙을 추가해야 한다. 예를들어서 8.8.8.8에서 출발한 패킷을 제외한 모든 패킷을 막고 싶다면 아래와 같이 설정하면 된다.
# iptables -I DOCKER -i ext_if ! -s 8.8.8.8 -j DROP

컨테이너간 통신

두 가지 요소에 의해서 컨테이너간 통신이 결정된다.
  1. 동일한 네트워크 브릿지에 컨테이너를 연결했는가 ? 기본적으로 도커는 docker0브릿지에 모든 컨테이너를 연결한다. 같은 브릿지에 연결된 컨테이너들은 서로 통신할 수 있다.
  2. iptables에서 연결을 허용하고 있는가 ? iptables=false로 도커데몬을 실행했다면, 도커는 iptables를 변경하지 않는다. 도커는 단지 FORWARD룰 만을 설정한다. 만약 ACCEPT 정책을 기본으로 두고 싶다면 icc=true로 설정한다. DROP를 기본 정책으로 하고 싶다면 --icc=false로 한다.

컨테이너 포트를 호스트에 바인딩하기

도커 컨테이너는 NAT을 이용해서 인터넷으로 나갈 수 있다. 하지만 인터넷에서 컨테이너로 접근할 수는 없다. 도커는 iptable에 마스커레이딩(Masquerade)를 설정해서 인터넷으로 접근한다. 도커의 마스커레이딩 설정은 아래와 같다.
# iptables -t nat -L -n
......
Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination
MASQUERADE  all  --  172.17.0.0/16        0.0.0.0/0 
외부에서 접속하게 하려면 포트포워딩을 해야 한다. 이를 위해서는 두 개의 값을 조정해야 한다.

먼저 docker run을 할 때 -p옵션을 이용해서 포워딩 포트를 설정하면 된다. 호스트의 5000번 포트를 컨테이너의 22번 포트로 포워딩 하기 원한다면 아래와 같이 실행한다.
# docker run -p 5000:22 -i -t ubuntu /bin/bash
도커 서버는 iptable에 포트포워딩룰을 추가 한다.
# iptables -t nat -L
Chain DOCKER (2 references)
target  prot opt source     destination         
DNAT    tcp  --  anywhere   anywhere     tcp dpt:5000 to:172.17.0.3:22

docker0 커스터마이징

기본적으로 도커는 호스트 시스템에 있는 docker0 이더넷 브릿지를 사용한다. 이 브릿지에 호스트의 물리적인 네트워크 인터페이스와 컨테이너를 위한 가상 인터페이스를 서로 묶는 네트워크를 만든다. 이 브릿지는 리눅스 운영체제에서 제공하는 brctl을 이용해서 제어 할 수 있는, 가장 단순한 형태의 브릿지다.

도커는 docker0의 IP주소, 넷마스크(netmask), IP 할당 범위등을 설정할 수 있다. 또한 브릿지에 연결된 컨테이너의 송/수신 패킷의 MTU(Maximum Transmission Unit - 인터페이스가 허용하는 최대 패킷크기)도 수정할 수 있다. 이 값들은 도커 서버 실행시 옵션을 변경하는 걸로 적용 할 수 있다. brctl 명령을 찾을 수 없다면 bridge-utils 패키지를 인스톨하자.
# apt-get install bridge-utils
docker run을 실행하면, 도커서버는 새로운 컨테이너에 대한 가상 인터페이스를 만들고 docker0에 attach 한다. 컨테이너는 (가상 인터페이스에 연결된)eth0이 올라오고, IP가 설정된다.
# docker run -i -t ubuntu /bin/bash
// docker 컨테이너에서 테스트
# ip addr show eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
  link/ether 00:00:00:00:10:01 brd ff:ff:ff:ff:ff:ff
  inet 192.168.219.192/24 brd 192.168.219.255 scope global eth0
    valid_lft forever preferred_lft forever
  inet6 fe80::200:ff:fe00:1001/64 scope link 
    valid_lft forever preferred_lft forever

# ip route
default via 192.168.219.1 dev eth0 
172.17.0.0/16 dev docker0  proto kernel  scope link  src 172.17.42.1 
192.168.219.0/24 dev eth0  proto kernel  scope link  src 192.168.219.192 

# exit

자신의 브릿지 만들기

개인적(테스트나 개발)용으로 사용할 거라면 도커의 기본 브릿지 네트워크를 사용해도 상관없겠지만, 비지니스용으로 서비스를 만들어서 배포할 거라면 비지니스 환경에 맞게 브릿지를 수정해야 한다. 도커는 이미 docker0을 가지고 있으니, 이 것을 지운 다음 새로운 브릿지를 만들어야 한다.
// 브릿지 만들기
# brctl addbr bridge0
# ip addr add 192.168.5.1/24 dev bridge0
# ip link set dev bridge0 up

# ip addr show bridge0
# ip addr show bridge0 
4: bridge0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNO
WN group default 
    link/ether 56:71:ad:2f:e5:d9 brd ff:ff:ff:ff:ff:ff
    inet 192.168.5.1/24 scope global bridge0
       valid_lft forever preferred_lft forever
    inet6 fe80::5471:adff:fe2f:e5d9/64 scope link 
       valid_lft forever preferred_lft forever

# echo 'DOCKER_OPTS="-b=bridge0"' >> /etc/default/docker
# service docker start
# iptables -t nat -L -n
...
Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination         
MASQUERADE  all  --  192.168.5.0/24       0.0.0.0/0           
이제 새로운 브릿지 bridge0이 만들어졌다. 컨테이너를 만들면 bridge0의 서브넷 에서 자동으로 IP가 할당된다.

컨테이너 네트워크

인터넷 프로토콜(IP) 통신을 위해서 머신(machine)는 패킷을 주고 받기 위한 통로가 되는 "하나 이상의" 네트워크 인터페이스를 가지고 있어야 한다. 그리고 IP 패킷을 인터페이스로 보내기 위한 라우팅 테이블이 설정돼야 한다. 이때 네트워크 인터페이스는 물리적일 필요가 없다. 예컨데 리눅스에서 사용하는 lo(루프백 인터페이스)는 가상 인터페이스인 것처럼, 가상의 인터페이스를 사용해도 상관없다.(루프백 인터페이스의 경우, 한쪽 프로세스의 메모리에 있는 데이터를 읽어서 다른 쪽 프로세스의 메모리에 쓰는 방식으로 통신이 이루어진다.)

컨테이너의 네트워크를 설정하는 과정이다.
  1. peer 가상인터페이스의 쌍(pair)을 만든다.
  2. 가상 인터페이스의 이름은 vetch65f9 형식으로 유일하게 만들어진다. 도커는 이 가상 인터페이스를 도커 브릿지(docker0)에 attach 한다.
  3. 이 인터페이스는 새로 만들어지는 컨테이너에 던지는데(toss), 이해하기 쉽게 eth0으로 이름을 변경한다.
  4. 인터페이스의 MAC 주소는 --mac-address로 할당하거나 랜덤하게 만들어진다.
  5. 컨테이너의 eth0에는 브릿지의 네트워크 주소 범위내에서 새로운 IP 주소가 할당되고 브릿지의 IP를 게이트웨이로 설정한다.
이 과정이 끝나면, 컨테이너는 eth0 (가상)네트워크 카드를 이용해서 다른 컨테이너 혹은 인터넷으로 통신할 수 있다.

docker run을 수행 할 때, --net= 값을 설정하는 걸로 네트워크 환경을 변경할 수 있다.

참고