본문 바로가기
IT Security/Docker & Kubernetes

[Docker & Kubernetes] 8. Docker 네트워크 1 - 기본 구조

by Rosmary 2022. 6. 1.
728x90
반응형

 

 

 

이번 포스팅에서는 Docker 컨테이너가 어떻게 외부와 통신이 가능한지에 대해 알아보려한다. 지난 포스팅에서 기본적인 명령어 사용의 몇몇 예시 중, nginx와 같이 웹 서비스를 제공하는 컨테이너를 구동해 웹 브라우저에서 서비스가 잘 이루어지는 것을 확인해보기도 했다. nginx는 웹 서비스인만큼 기본 통신 포트가 80번으로 지정되어 있고, 필자는 이를 --publish라는 옵션을 통해 HostOS의 포트와 컨테이너 포트(80)을 맵핑하여 컨테이너를 구동했다. 

 

그런데, 여기서 의문점이 하나 생긴다. nginx 컨테이너를 두 개 올렸다고 가정해보자. 두 컨테이너 모두 기본 포트인 80번을 서비스 포트로 사용한다. 1번 nginx는 HostOS의 8001번으로, 2번 nginx는 HostOS의 8002번으로 publish 한다고 하자.

 

 

실제로 이를 확인하기 위해 필자도 Docker Server에 nginx 컨테이너를 두 개 생성한다.

 

 

두 컨테이너가 제공하는 웹 페이지의 모양이 동일하기 때문에, 필자는 nginx2의 index.html 파일만 변형해서 사용해보려한다.

 

 

*  참고

위에서 사용한 docker cp 명령어는 Linux의 cp 명령어와 유사하나, HostOS에서 컨테이너로, 혹은 서로 다른 컨테이너 사이 파일을 이동시킬 수 있는 명령어다.

 

#  docker cp {Localfile 경로} {컨테이너명 또는 컨테이너ID}:{파일을 복사할 컨테이너경로}

#  docker cp {컨테이너명 또는 컨테이너ID1}:{파일 경로} {컨테이너명 또는 컨테이너ID2}:{파일을 복사할 컨테이너경로}

 

 

 

 

글자가 조금 깨지긴 했지만(고치기는 귀찮고), 필자가 의도한대로 8002번 웹 서비스, 즉 nginx2 는 기본 웹 페이지와는 다른 모습을 보이는 것을 확인할 수 있다. 

 

HostOS는 8002 번 포트로 들어오는 웹 페이지 요청 정보를 어떻게 nginx 2번으로 연결할 수 있는 것일까? 분명 필자는 컨테이너 구동 시 --publish 외에는 별도로 설정해 준 것도 없고, 두 nginx의 포트도 80번으로 동일한데 말이다. 이 현상을 이해하기 위해서는 docker의 네트워크 구조에 대해 간략하게나마 짚고 넘어가야 한다. 본격적으로 시작해보자.

 

* 시작에 앞서, 생성한 모든 컨테이너는 중지 및 삭제를 진행하자. 아래의 명령어에 대해 알고 싶으신 분들은 이 포스팅의 '2. 컨테이너 삭제'를 참고하자.

 

 

 

1. Docker 컨테이너 자체 네트워크 대역: 172.17.0.0/16

 

Docker 서비스가 정상적으로 설치되어 있다면, ifconfig를 입력하여 네트워크 인터페이스와 IP를 확인해보자. 아마 루프백(lo)과 VM에 달아놓은 가상 네트워크 인터페이스 외에도, docker0라는 이름의 네트워크 인터페이스가 172.17.0.1/16 주소를 가지고 있는 것이 확인할 수 있다.

 

docker 패키지를 삭제하는 순간, 저 docker 0도 같이 사라진다.

 

Docker의 docker0 인터페이스는 OS에 상관없이 항상 172.17.0.1의 IP와 255.255.0.0의 서브넷 마스크(prefix 16)을 가진다. 그렇다면 이 docker0 인터페이스는 도대체 무슨 역할을 하는 것일까? nginx 컨테이너를 test라는 이름으로 하나 구동해보자. 필자는 HostOS의 8001번으로 Pubilsh 옵션을 넣어주었다.

 

 

이 컨테이너의 경우 HostOS 8001번으로 들어오는 통신을 받아 웹 페이지를 제공하는데, 포트 통신을 한다는 것은 어찌되었든 이 컨테이너도 IP를 가진다는 것이다. 컨테이너의 IP확인은 아래와 같이 진행한다.

 

#  docker container inspect {컨테이너명 또는 컨테이너ID} | grep IPAddress

 

 

test 컨테이너의 인터페이스, 즉 Endpoint의 IP가 172.17.0.2로 설정된 것이 확인된다. HostOS에서 이 컨테이너로 ping을 날리면 ping도 정상적으로 진행되는 것이 확인된다.

 

 

nginx 컨테이너를 하나 더 올려보자. 이름은 test2로 지정하고, HostOS의 포트는 8002로 Publish 하려고 한다.

 

 

두 번째로 생성한 컨테이너는 172.17.0.3/16 IP가 할당되었다. 즉, 컨테이너가 생성될 때마다, 4옥텟부터 IP 번호가 순차적으로 컨테이너의 인터페이스에 부여된다는 것을 추론할 수 있다. 필자가 여기서 컨테이너를 하나 더 생성하면 172.17.0.4/16 IP가 세 번째 컨테이너에 부여될 것이다. nginx로 컨테이너를 하나 더 생성해보자.

 

nginx 뿐만 아니라 서비스를 제공하지 않는 순수 ubuntu 컨테이너도 IP가 순차 부여된다.

 

이들의 게이트웨이는 몇 번일까? 짐작하듯이 모든 컨테이너의 Gateway, 즉 외부와의 통신을 위한 주 인터페이스는 docker0의 IP로 설정되어 있다. 

 

#  docker container inspect {컨테이너명 또는 컨테이너ID}  | grep Gateway

 

 

IP가 컨테이너마다 부여되다가 고갈되면 어떨까? 걱정은 안해도 될 듯 하다. 하나의 Docker 서버에는 이론 상 256 * 256 개(172.17.0.0 ~ 172.17.255.255까지)의 컨테이너에 대해 IP 부여가 가능하다. 대략 65000개 정도 되는데, 이 정도면 컨테이너 65000개가 전부 구동되기 전에 서버가 뻗지 않을까?

 

지금까지 확인한 내용으로만 추론할 때 내부적인 구조는 대략적으로 아래의 그림과 같다고 볼 수 있다.

 

실제로는 docker0와 각 컨테이너 가상 인터페이스 사이에 Veth라는 가상 이더넷 인터페이스가 존재한다.

 

 

정리하자면, Docker는 생성되는 각 컨테이너에 통신에 필요한 IP를 부여하기 위해, 172.17.0.0/16 네트워크 대역대를 가지며, docker0라는 브릿지 인터페이스를 통해 컨테이너가 HostOS 외부와도 통신이 가능하도록 만들어주는 것이다. 그리고 생성되는 컨테이너는 순차적으로 네트워크 대역대 내의 IP가 부여되는 것이고 말이다.

 

자, 그럼 컨테이너가 통신을 위한 IP를 가진다는 것까지는 이해가 되었는데... 특정 컨테이너의 IP와 포트는 어떻게 알고 들어오는 것일까? 이제  HostOS 8001번 포트가 nginx1 컨테이너의 포트 80번으로 들어오는 이유를 확인해보도록 하자.

 

 

2. docker proxy

 

외부에서 사용자가 웹 브라우저를 통해 컨테이너의 웹 페이지를 호출하는 과정을 보자. 사용자가 브라우저 주소창에 {HostOS 서버IP}:{외부포트번호} 를 입력하면, HostOS의 물리 인터페이스까지 다음과 같은 과정으로 통신이 진행된다.

 

 

컨테이너 생성 시, HostOS 포트 8001번과 nginx1번의 80포트를 --publish 옵션으로 맵핑했기 때문에 HostOS에서도 8001포트로 들어오는 통신을 nginx1로 전달하게 된다. 그런데, 무엇을 참조해서 이렇게 진행할 수 있는 것인가?

 

컨테이너가 구동되고 있는 상태에서, ps -ef | grep proxy 라는 명령어를 입력하여, proxy라는 단어가 들어가있는 프로세스를 전부 확인해보자. 

 

 

필자의 경우 7개의 docker-proxy 명령어가 구동되고 있는 것으로 나타난다. 앞의 /usr/bin/docker-proxy는 docker에서 제공하는 포트 publish를 위한 명령어의 경로를 나타내고, 뒤의 내용은 옵션과 옵션값을 나타낸다. 자세히 보면 필자가 --publish 옵션과 함께 구동한 컨테이너는 docker-proxy 프로세스가 컨테이너마다 2개씩 생성된 것을 알 수 있다(추가로, ubuntu1 컨테이너 - 172.17.0.5 - 는 별도의 docker-proxy 프로세스가 생성되지 않은 것을 확인할 수 있다).

 

docker-proxy에서 proxy라는 용어는 "대리", 즉 대신 업무를 처리하는 것을 의미한다. HostOS의 물리인터페이스, 특정 포트로 통신이 유입되면, docker-proxy는 해당 포트의 통신을 docker-proxy 명령어 옵션에 지정된 컨테이너의 IP, 포트로 전달한다.

 

순서도 3번에서 확인한 컨테이너 IP와 포트로 외부의 사용자 요청을 전달한다.

 

 

참고로, 필자가 마지막에 생성한 컨테이너인 Ubuntu의 경우, --publish 옵션을 사용하지 않았기 때문에 외부 통신 유입 시 참조할 수 있는 docker-proxy 값이 존재하지 않아 외부 접속이 불가능하다. 이 말은, 현재 --publish로 구동중인 컨테이너도 docker stop 명령어로 중단된다면 docker-proxy에서 해당 컨테이너의 포트 publish 내용이 사라져 접속이 되지 않는다는 것이다(물론 docker-proxy 값이 남아있어도 서비스가 죽어 어차피 접속은 안되겠지만 말이다).

 

추가로 생성된 각 컨테이너의 세부 정보는 docker container inspect 명령어를 통해 확인할 수 있는데, 이 명령어 결과 내에는 앞에서 확인한 컨테이너의 Docker IP 외에도, 포트 Publish 정보도 표시된다.

 

나머지 정보에 대해서는 추후 천천히 알아보도록 하자...

 

docker container inspect는 출력되는 내용이 상당히 많은데, 특정 항목만 추출하고 싶다면 --format 옵션을 사용하여 json 형태로 추출하는 것도 가능하다.

 

 

 

 


 

이번 포스팅에서는 Docker 컨테이너 생성 시 네트워크 구성과, 특정 포트를 통한 외부의 통신 시도 시 HostOS에서 특정 컨테이너로 해당 통신을 전달하는 과정까지 간략하게 살펴보았다. 오늘 내용 중 아래의 용어들이 중점 내용이다.

 

[ Docker Network 1]

-   docker0:  컨테이너 네트워크 대역을 지정을 위한 Bridge 인터페이스. 172.17.0.0/16 값 사용

-   Endpoint: 컨테이너 인터페이스

-   docker-proxy: 컨테이너 포트 publish 시, 외부에서 특정 포트로 들어오는 통신을 정확한 컨테이너로 전달하는 역할 수행

 

 

 

Fin.

 

 

 

 

반응형

댓글