도커 네트워크 개요
도커는 호스트 서버의 물리적 네트워크와 도커의 가상 네트워크가 혼합되어 네트워크가 이루어진다. 아래 그림의 예시와 같은 네트워크가 구성된다.
그림 맨 아래의 eth0 은 서버의 물리적 인터넷 포트이며, 이 포트가 인터넷에 연결되어 있다고 가정한다. 이 인터넷 포트가 iptables를 통해 docker0 또는 다른 사용자가 직접 만든 가상 브릿지(위 그림에서 Mybridge)와 통신이 된다. 또한 이 가상 브릿지는 각각의 컨테이너와 통신하게 된다. 가상브릿지와 컨테이너간 통신은 맥주소로 통신한다.
컨테이너를 생성하면, 자동으로 컨테이너에 eth0 interface가 생기며 (물론 옵션을 통해 더 추가할 수도 있다) 그 컨테이너의 eth0 과 1:1 매치(pair)되는 vethXXXXXX interface가 생성된다. 이 가상 interface는 OSI 참조 모델의 레이어2인 가상 네트워크 인터페이스로, pair인 eth0(컨테이너)과 터널링 통신을 한다.
이렇게 pair로 구성된 두개의 interface를 통해 격리된 네트워크 환경(namespace)이 제공되며, MAC주소와 Private ip가 부여된다. 마치 직접 다이렉트로 케이블을 연결한 두 대의 PC가 서로 패킷을 주고받는 형태와 같다.
또한 따로 옵션을 주지 않았다면 컨테이너의 eth0와 한 쌍인 vethXXXXXX은 docker0 가상 브릿지 네트워크에 연결된다. 이 가상 네트워크는 컨테이너끼리 통신할 수 있도록 한다. 도커는 1서비스당 1컨테이너를 권고하므로, 한 컨테이너에 여러 서비스를 포함하지 않고 각각 컨테이너로 하나씩 만들고 이 가상 브리지 네트워크로 연결되는 것이 권고된다.
예를들어 데이터베이스 컨테이너와 웹서버 컨테이너는 각각 다른 컨테이너야 한다는 것. 결론적으로 컨테이너(eth0) <-> vethXXXXXX(호스트) <-> docker0(호스트) <-> 외부네트워크 이런식으로 네트워크가 연결된다.
도커의 기본 가상 브리지 네트워크 : docker0
도커 설치 시, default 로 docker0라는 가상 브리지 네트워크가 생성된다. docker0 가상 브리지 네트워크는 는 소프트웨어적인 스위치방식으로, 일반적인 스위치 개념과는 다르게 DHCP로 연결된 container에게 사전에 정의된 IP pool을 할당한다. docker0 는 default로 172.17.0.0/16 서브넷을 가진 IP 대역을 가진다.
컨테이너가 특별한 옵션 없이 생성된다면 이 docker0 에 기본적으로 연결된다. docker0 는 default 브리지 네트워크일 뿐이고, 사용자가 얼마든지 추가로 가상 브리지 네트워크를 생성할 수 있다.
docker0와 사용자 정의 네트워크
docker0는 도커 설치시 자동으로 만들어지며, 컨테이너 생성시 명시적으로 지정하지 않고 run 하는 경우 이 네트워크로 컨테이너가 시작된다. docker0 의 문제점은, DNS를 사용해 자동으로 서비스를 찾아주는 기능이 비활성화 되어있다. 즉, 이름식별이 안되므로 통신이 되지 않는다. IP로만 통신이 가능한 상태이다. 컨테이너는 일종의 프로세스이므로, 내렸다 올리면 아이피가 바뀌는 등의 유동성이 있다. 따라서 문제가 된다.
컨테이너끼리 연동을 하려면 IP 기반의 설정은 좋지 않으며, link 옵션을 사용하여 연동해야 한다. 즉 docker container run 할 때 --link 옵션을 지정하야 한다. --link 옵션을 지정한다는 것은 컨테이너 안에 있는 /etc/hosts 파일에 컨테이너명과 컨테이너에 할당된 IP가 등록되고 이를 통해 통신이 가능하게 된다. link를 하지 않으면 당연히 그 정보는 없다. link가 걸린상태에서 컨테이너가 재기동하면 IP가 바뀔 수 있는데, 바뀐 IP값이 자동으로 /etc/hosts에 등록되어서 container의 ip정보가 바뀌어도 서로간 문제없이 통신이 가능하다.
사용자 정의 네트워크는 도커 데몬에 내장된 내부 DNS 서버에 의해 이름 해결이 이루어진다. 내부 DNS 서버를 사용하면, link 기능과 같이 /etc/hosts 파일에 의존하지 않고 이름 해결을 할 수 있다. 따라서 docker0 에 연결된 컨테이너들은 ping을 할 때 IP만 가능하지만 사용자 정의 네트워크에 연결된 컨테이너들은 컨테이너명(docker container run --name 으로 생성되는 컨테이너명)뿐만 아니라 컨테이너 시작 시 지정한 --net-alias 옵션을 사용한 알리아스명으로도 통신이 가능하다. 사용자 정의 네트워크를 쓰는 편이 보다 유연하고 쉽게 네트워크 구성관리를 할 수 있어서 이쪽이 권고된다.
# 참고 : link 명령
위에서 설명한 link 명령은 이해를 위해 설명한 것이며, 실제로는 아래와 같이 legacy 기능으로써 조만간 제거될 수 있다.
각 네트워크 상태 예시
도커를 설치한 직후 네트워크 상태
lo : 루프백 네트워크. 서버 자기 자신을 의미하며 물리적인 네트워크가 아닌 가상으로 생성된 네트워크이다. 신경쓰지 않아도 됨.
enp0s3 : 해당 서버의 물리적인 네트워크 포트. CentOS7 이전에는 eth0 이었으나, CentOS7 부터는 이러한 형식을 가진다.
docker0 : 도커를 설치하고 생성된 가상 브릿지. 도커에서 생성된 컨테이너와 통신하며, 이 서버의 물리적 네트워크와 브릿지 되어 있다.
아래처럼 2개의 컨테이너를 띄운 후 네트워크 상태
2개의 가상 네트워크 장치가 생긴것을 볼 수 있다. 또한, 각 컨테이너는 eth0 을 가지고 있다. 바로 아래 "#참고" 에서 각각 컨테이너의 ip a 명령 결과를 확인할 수 있다.
가상 브릿지 docker0 와 컨테이너의 연결상태
그렇다면 각 컨테이너의 eth와 매핑되는 vethXXXXX은 어디에 연결되어 있을까? 호스트 서버에서 brctl show 명령을 사용하면, 가상 네트워크 interface가 어디에 연결되어 있는지 확인할 수 있다.
호스트 서버에서 ip a를 쳤을 때 나온 vethXXXXX 2개가 docker0 와 연결되어 있는 것을 확인할 수 있다. docker0 외에 다른 가상 네트워크를 만들 수 있고, 다른 가상 네트워크에 다른 컨테이너들을 또한 연결할 수 있다. 물론 ip도 각 가상 네트워크마다 다른 대역으로 생성된다.
# 참고 : 각 컨테이너와 가상 네트워크 장치 파악
어떤 가상 네트워크가 어떤 컨테이너의 네트워크인지 확인하려면 다음과 같이 확인할 수 있다. 아래 그림에서 새로 생긴 2개의 가상 네트워크 장치는 13번 항목의 if12로 끝나는 장치, 15번 항목의 if14로 끝나는 장치가 생성되어 있다.
또한 각 컨테이너의 네트워크 정보도 다음과 같다.
8835bd89la60 : nginx 컨테이너, 12번 항목의 eth0@if13 을 가진다.
28485a9293db : CentOS 컨테이너, 14번 항목의 eth0@if15 를 가진다.
즉, 컨테이너와 호스트 사이 서로 매핑되는 장치는 항목과 if값이 거꾸로 되어있다. nginx 컨테이너의 12번 항목의 if13과 호스트 13번 항목의 if12가 일치하며, CentOS 컨테이너의 14번 항목의 if15와 호스트 1번 항목의 if14가 일치한다.
외부에서 컨테이너로 연결
컨테이너는 기본적으로 docker0 브릿지 네트워크에 있으므로 호스트에서만 접근이 가능하다. 기본적으로 외부접근은 불가하다. 외부에 컨테이너 에플리케이션을 노출하기 위해서는 컨테이너의eth0의 ip와 포트를 host의 ip+포트에 바인딩을 수행해야 한다.
컨테이너 실행 시 포트옵션으로 바인딩할 수 있다. (docker conatiner run 포스팅에서 자세히 설명한다) 이러한 바인딩은 기술적으로 가상 브릿지 docker0와 호스트OS의 물리 NIC에서 패킷을 전송하는 장치가 필요하다. 도커에서는 NAPT 기능을 사용하여 연결한다.
# 참고 : NAPT(Network Address Port Translation) 이란?
Private IP 를 Public IP로 변환하여 Pirvate IP가 외부에 연결할 수 있도록 하는 기술은 NAT, NAPT 두가지가 있다. NAT는 단순히 Private IP를 변경하는 것이지만, NAPT는 Private IP와 함께 포트번호까지 함께 변환한다. 따라서 하나의 Public 주소를 여러 개의 Private IP 주소로 변환할 수 있다. NAPT는 이러한 기술의 이름으로써, LINUX에서 NAPT를 사용하는 것을 IP Masquerade 라고 부른다. docker에서는 NAPT 기술을 사용하기 위해 LINUX의 iptables 프로그램을 사용한다.
ip 마스커레이드는 패킷의 소스 주소를 자체 공용 ip주소로 변경하면서 패킷을 전달하는 기능이다. 브릿지에 연결된 컨테이너들은 private ip가 할당된다. Private ip로는 인터넷 통신이 불가능 하기 때문에, 출발지 IP를 Public ip로 바꾸는 방법으로 인터넷 통신이 가능하게 한다. 해당 패킷에 대한 응답을 받을 때는, 받을 대상으로써 자체 공용 ip주소가 응답을 받고, 그걸 다시 원래 호스트 주소로 수정하고 패킷을 전송한다. NAT의 한 형태이다.
NAPT와 NAT 두가지의 예시를 아래와 같이 정리할 수 있다.
외부에서 컨테이너로 들어올 때 : 웹서버 컨테이너를 시작할 때 컨테이너 안의 웹서버가 사용하는 80번 포트를 호스트OS의 8080포트로 전송하도록 설정한다. 그러면 외부 네트워크에서 호스트OS의 8080포트로 접속하면, 컨테이너안의 80번 포트로 연결하게 된다.
컨테이너에서 외부로 나갈 때 : 192.168.0.1, 192.168.0.2 각각 컨테이너가 있고, NAT 54.10.10.1이 있다고 가정하자. 각 컨테이너가 외부로 나가려면 192.168.0.1 이 54.10.10.1 변환되어 할일을 다 한 후, 그 후에야 192.168.0.2가 54.10.10.1로 변환되어 할일을 할 수 있다. 하지만 NAT가 아니라 NAPT라면, 192.168.0.1:1500 포트로 NAPT 54.10.10.1 로 변환해서 나가고, 192.168.0.2:1501 포트가 NAPT 54.10.10.1로 변환해서 나갈 수 있다.
# 참고 : docker-proxy
컨테이너 포트를 외부로 노출하도록 설정하면, 도커 호스트에는 docker-proxy라는 프로세스가 자동으로 생성된다. docker-proxy는 이름처럼 docker host로 들어온 요청을 container로 넘기는 것 뿐이다. docker-proxy는 커널이 아닌 userland에서 수행되므로 kernel과 상관없이 host가 받은 패킷을 그대로 containt의 포트로 넘긴다. 이렇게 외부로 binding 된 컨테이너가 있다면, 아래처럼 docker-proxy라는 프로세스가 생기며, 포트 LISTEN도 확인할 수 있다.
예를들어, -p 8080:80 옵션을 사용해서 컨테이너 포트를 오픈하면, docker-proxy가 생성되며, 8080포트를 리스닝하는것을 확인할 수 있다.
root@~~# netstat -nlp | grep 8080
tcp6 0 0 :::8080 :::* LISTEN 12581/docker-proxy
'Docker Basic' 카테고리의 다른 글
[Docker Basic] 14. Docker 중요 명령어 - docker container run (1) | 2020.05.15 |
---|---|
[Docker Basic] 13. Docker 기본 명령어 - 네트워크 명령 및 예시 (0) | 2020.04.13 |
[Docker Basic] 11. Docker 기본 명령어 - 컨테이너/이미지 백업 (0) | 2020.04.11 |
[Docker Basic] 10. Docker 기본 명령어 - 컨테이너 관리 명령 (0) | 2020.04.11 |
[Docker Basic] 09. Docker 기본 명령어 - 컨테이너 정보 확인 명령 (0) | 2020.04.10 |