Docker #5. 여러 대의 도커 호스트를 다뤄보기
Prog. Langs & Tools/Docker

Docker #5. 여러 대의 도커 호스트를 다뤄보기

이번 실습은 여러 호스트를 다루면서 컨테이너를 배치하는 방법에 대해서 다뤄보는 튜토리얼이다.

이번 튜토리얼에서는 도커 스웜(docker swarm)을 사용한다. 도커 스웜은 여러 도커 호스트를 클러스터로 묶어주는 컨테이너 오케스트레이션 도구이다. 여러 도커 호스트를 사용하여 확장성 있는 어플리케이션을 만들기 위해서는 (거의) 필수적이다. 오케스트레이션 도구를 사용하면 어느 도커 호스트에 어떤 컨테이너를 배치해야 하는지, 서로 다른 호스트에 위치한 컨테이너 간의 통신은 어떻게 해야 하는지 등의 조율을 수월하게 할 수 있다.

여기서 잠깐, 도커에서 쓰이는 다양한 도구들의 역할을 복습하고 가자

  • 도커 컴포즈(docker-compose) : 여러 컨테이너로 구성된 도커 어플리케이션을 관리 (주로 단일 호스트)
  • 도커 스웜(docker swarm) : 클러스터 구축 및 관리 (주로 멀티 호스트)
  • 도커 서비스(docker service) : 스웜에서 클러스터 안의 서비스 (컨테이너 하나 이상의 집합) 를 관리
  • 도커 스택(docker stack) : 스웜에서 여러 개의 서비스를 합한 전체 어플리케이션을 관리

 

여러 대의 도커 호스트로 스웜 클러스터 구성하기

여러 개의 도커 호스트를 구성하는 방법에는 어떠한 것들이 있을까? VirtualBox와 같은 가상화 소프트웨어를 사용하면 물리 호스트 1대에 여러 대의 도커 호스트를 실행할 수 있다. 이 방법은 도커 공식 문서에도 소개하고 있는 방법이지만 문제가 없지는 않다. 예를 들면 Hyper-v 설정으로 인해 docker와 VirtualBox 둘 중 하나가 충돌이 나서 실행이 되지 않는 경우 등의 문제가 생길 수가 있다. 아니면 Docker Machines를 사용할 수도 있지만 추가적인 드라이버를 설치 해 주어야 하고 비교적 번거롭다.

간단한 방법이 하나 있는데 바로 도커 호스트 역할을 하는 도커 컨테이너를 여러 개 실행하는 것이다. 이번 튜토리얼도 이 방법(Docker in Docker, dind)을 통해 스웜 클러스터를 구성하는 것으로 해보려 한다.

도커/쿠버네티스를 활용한 컨테이너 개발 실전 입문 p109

사용하는 컨테이너는 다음 세 가지 종류로 총 다섯 개이다

  • registry 1개
  • manager 1개
  • worker 3개

registry는 도커 레지스트리 역할을 할 컨테이너로, manager 및 worker 컨테이너가 사용하는 컨테이너이다. 외부 도커에 저장된 이미지를 먼저 registry 컨테이너에 등록했다가 여기서 manager 및 worker 컨테이너가 이미지를 받아 가도록 한다.

이 구성을 도커 컴포즈(docker-compose.yml)로 구성하면 아래와 같다. 모든 manager 및 worker 컨테이너는 registry 컨테이너에 의존한다. 그리고 도커 레지스트리에는 일반적으로 HTTPS를 통해 접근하지만, 여기서는 HTTP를 사용하기 때문에 도커 이미지를 내려받을 수 없다. 이 문제를 해결하기 위해 command 요소에 --insecure-registry registry:5000 값을 주어서 HTTP로도 이미지를 내려받을 수 있게 하였다.

 

도커 컴포즈를 아래 명령어로 실행하면 registry 컨테이너 1대, manager 컨테이너 1대, worker 컨테이너가 3대, 총 5대 컨테이너가 실행 상태가 된다.

현재 상태는 단순하게 컨테이너가 여러 대 실행이 되어 있는 상태이다. 여기서 클러스터를 관리하는 manager가 필요하다. docker swarm init 명령을 실행해 스웜의 manager 역할을 호스트의 manager 컨테이너에 맡긴다.

swarm init이 성공하면 해당 도커 호스트는 manager로 마킹되고 스웜 모드가 활성화 된다. 이제 여러 대의 worker 컨테이너를 등록하여 클러스터를 형성해보자. 앞서 swarm init 명령을 통해 join 토큰이 생성되어 출력이 되는데 스웜 클러스터에 도커 호스트를 worker로 등록하려면 이 join 토큰이 필요하다.

이런 식으로 join 토큰을 발급받는다.

join 토큰을 사용하여 3대의 노드를 스웜 클러스터에 worker로 등록한다. manager 및 모든 worker 컨테이너는 컴포즈로 생성한 기본 네트워크 위에서 실행된다.

스웜 클러스터의 상태를 확인해 보자.

 

도커 레지스트리에 이미지 등록하기

실행중인 registry 컨테이너에 도커 이미지를 등록한다. 이번 튜토리얼에서는 example/echo라는 이미지를 사용한다. 외부 도커에서 빌드한 이미지는 레지스트리를 통해서만 안쪽 도커에서 사용할 수 있음을 명심하자.

태그 포맷은 [레지스트리_호스트/]리포지토리명[:태그]인데, 여기서 레지스트리_호스트는 이미지를 등록하거나 내려받는 레지스트리를 의미한다. 지금 만든 레지스트리는 localhost:5000과 같이 접근할 수 있으므로 리포지토리명 앞에 이 주소를 붙여 localhost:5000/example/echo:latest라는 태그가 된 것이다.

docker image push 명령에 인자로 이 포맷을 따르는 태그를 그대로 사용하면 registry 컨테이너에 이미지가 등록된다.

이번에는 worker 컨테이너가 registry 컨테이너로부터 도커 이미지를 내려받을 수 있는지 확인해 보려고 한다. worker 컨테이너에서 docker image pull 명령을 실행한 후 docker image ls 명령으로 이미지를 받았는지 확인한다. 호스트에서 본 레지스트리는 localhost:5000이었지만, worker01에서 보면 registry라는 이름이므로 registry:5000을 지정하면 된다.

이제 worker01 컨테이너에서 example/echo:latest 이미지를 사용할 준비는 끝났다.

 

서비스

단일 도커 호스트에 대한 컨테이너 배포는 docker container run 명령으로 컨테이너를 일일이 실행하거나 컴포즈를 사용해 여러 컨테이너를 동시에 실행하는 방법이 있다. 스웜의 경우는 서비스라는 개념을 가지고 컨테이너들을 제어한다. 아래는 docker service create 명령어를 사용 하여 서비스를 하나 만드는 명령이다. 

방금 만든 서비스는 레플리카가 1개이며 그 이름은 echo이다. 서비스가 제어하는 레플리카의 수를 늘리고 싶다면 docker service scale 명령으로 해당 서비스의 컨테이너 수를 늘리거나 줄일 수 있다. 여러 노드에 거쳐 컨테이너 수를 조정할 수 있으므로 스케일 아웃을 적용할 때 유용하다.

스웜 클러스터 위에서 동작하는 컨테이너를 확인해보면 echo 컨테이너가 6개 실행 중이다. NODE 칼럼을 보면 서비스가 스웜 클러스터의 노드를 분산 배치했음을 알 수 있다.

스택

스택은 하나 이상의 서비스를 그룹으로 묶은 단위로, 어플리케이션 전체 구성을 정의한다. 서비스는 어플리케이션 이미지를 하나밖에 다루지 못하지만, 여러 서비스가 협조해 동작하는 형태로는 다양한 어플리케이션을 구성할 수 있다. 이를 구현하기 위한 상위 개념이 스택이다. 스택을 사용하면 여러 서비스를 함께 다룰 수 있다. 스택이 다루는 어플리케이션의 입도(granularity)는 컴포즈와 같다. 스택은 말하자면 스웜에서 동작하는 scale-in, scale-out, 제약조건 부여가 가능한 컴포즈다.

스택을 사용하여 배포된 서비스 그룹은 overlay 네트워크에 속한다. overlay 네트워크란 여러 도커 호스트에 걸쳐 배포된 컨테이너 그룹을 같은 네트워크에 배치하기 위한 기술을 말한다. overlay 네트워크를 사용해야 서로 다른 호스트에 위치한 컨테이너끼리 통신할 수 있다.

도커/쿠버네티스를 활용한 컨테이너 개발 실전 입문 p118

다음 명령어를 통해 overlay 네트워크 ch03을 구성한 후, 스택으로 만든 각 서비스를 여기에 소속시킨다. 스택도 manager 컨테이너에서 조작한다.

stack 디렉토리에 다음과 같이 ch03-webapi.yml 스택을 생성한다. Nginx를 프론트엔드로 삼고, 백엔드 API(echo 컨테이너)의 리버스 프록시를 맡기는 구성이다. Nginx는 gihyodocker/nginx-proxy 이미지를 사용한다. 이 컨테이너는 환경 변수 BACKEND_HOST에 요청을 전송할 대상을 설정할 수 있는데, 스택으로 배포된 api의 서비스명인 echo_api의 8000포트를 설정한다. 

docker stack에는 다음과 같은 명령어들이 있다.

  • deploy : 스택을 새로 배포, 혹은 업데이트 함
  • ls : 배포된 스택의 목록을 출력
  • ps : 스택에 의해 배포된 컨테이너의 목록을 출력
  • rm : 배포된 스택을 삭제
  • services : 스택에 포함된 서비스 목록을 출력

다음과 같이 스택명을 echo라고 짓고 스택 배포를 실행한다.

배포된 스택을 확인하는 명령어는 docker stack services, 스택에 배포된 컨테이너를 확인하는 명령어는 docker stack ps 이다.

 

스웜 클러스터에 컨테이너 그룹이 어떤 노드에 어떻게 배치되었는지 시각화해주는 visualizer 어플리케이션을 사용해 보자. stack 디렉토리에 다음과 같이 visualizer.yml 파일을 작성한다.

deploy 아래에 위치한 mode 속성값이 global로 되어 있는데, 이 설정은 특정 컨테이너를 클러스터 상의 모든 노드에 배치하라는 의미이다. constraints를 보면 manager 노드에만 배치하라 되어 있다. 그리고 manager 노드의 포트 9000를 visualizer 컨테이너의 포트 8000으로 포트 포워딩을 설정했다. 호스트와 manager 노드 사이에는 9000:9000 포트 포워딩이 설정되어 있으므로 로컬 머신에서 visualizer에 접근하려면 http://localhost:9000 으로 접근해야 한다.

http://localhost:9000

 

스웜 클러스터 외부에서 서비스 사용하기

위에서 살펴본 visualizer는 스웜 클러스터 외부(호스트)에서 접근할 수 있다. 이것은 constraints 설정에서 visualizer 컨테이너가 반드시 manager에 배치되도록 했기 때문이다. 호스트에서 manager까지 여러 단계에 걸쳐서 포트 포워딩을 사용했기 때문에 문제가 되지 않았다. 

하지만 echo_nginx 서비스는 여러 컨테이너가 여러 노드에 흩어져 배치되어 있기 때문에 이런 방법을 사용할 수 없다. 호스트에서 이 서비스에 접근하려면 어떻게 해야 할까? 해결책은 프록시 서버이다. 프록시 서버는 서비스 클러스터 외부에서 오는 트래픽을 목적하는 서비스로 보내준다.

HAProxy(다기능 프록시 서버로 HTTP 및 TCP 로드 밸런서 역할)를 이 프록시 서버로 사용하여 외부에서 echo_nginx 서비스에 접근할 수 있도록 해보도록 한다. HAProxy 이미지는 dockercloud/haproxy 이미지를 사용한다. 이 이미지로 만든 컨테이너는 컨테이너 외부에서 서비스에 접근할 수 있게 해주는 다리 역할(ingress) 외에도 서비스가 배치된 노드에 로드 밸런싱 기능을 제공한다. stack 디렉터리에 ch03-ingress.yml을 생성한다.

HAProxy를 거쳐 서비스에 접근이 되는지 확인하기 위해 ch03-webapi.yml을 다시 이용한다. HAProxy 서비스를 찾을 수 있도록 nginx의 환경변수에 SERVICE_PORTS를 80으로 설정한다. 그리고 ch03-webapi.yml을 스택 echo로 다시 배포한다. 그리고 ch03-ingress.yml을 스택 ingress로 배포한다.

호스트의 포트 8000은 manager 컨테이너의 포트 80, 다시 말해 ingress의 HAProxy로 포트 포워딩 되므로 localhost:8000이 echo_nginx:80에 접근할 수 있게 된다.

 

이번 포스팅에서 도커 스웜 클러스터에 서비스 및 스택을 배포해 컨테이너 간 통신, 서비스 간 연동, 스케일 아웃 및 스케일 인, 로드 밸런싱 등을 수행하는 방법을 알아보았다. 다음 포스팅에서는 이러한 내용을 기반으로 실제 어플리케이션을 구축해 보려고 한다.

 

참고자료

<도커/쿠버네티스를 활용한 컨테이너 개발 실전 입문> 야마다 아키노리 저