이번 포스팅에서는 도커 컴포즈에 대해 간단히 다뤄보겠습니다.
이번에 한 머신에서 여러 서비스를 같이 돌릴 일이 있어 사용하게 됐습니다. 처음에는 그냥 각 서버 프로세스의 포트 번호만 다르게 해서 사용하다가, 갈수록 관리가 귀찮아지기에 아예 도커라이즈를 해 버렸는데요. 해 보니 너무나 편리해서 이걸 글로 잘 정리해두고자 포스팅을 해봅니다.
도커 컴포즈란?
Compose is a tool for defining and running multi-container Docker applications. With Compose, you use a YAML file to configure your application’s services. Then, with a single command, you create and start all the services from your configuration. To learn more about all the features of Compose, see the list of features.
도커 공식 설명에 따르면, 도커 컴포즈란 multi-container docker application, 즉 컨테이너 여러 개를 사용하는 어플리케이션을 구동하기 위한 솔루션입니다. 예를 들어 어떤 서비스가 frontend서버와 api서버로 나뉘어 구동되고 있는 경우가 해당될 수 있겠습니다. 이러한 도커 컴포즈는 YAML형식의 파일을 통해 정의할 수 있으며, 서비스 전체를 docker-compose up이라는 커맨드 하나만으로 구동할 수 있습니다.
사용 예시
백문이 불여일견이라고, 직접 예시를 보면 더욱 좋을 것 같습니다. 아래는 제가 어떤 서비스를 돌리기 위해 사용하고 있는 docker-compose.yml파일입니다.
version: '3'
services:
nginx:
image: jwilder/nginx-proxy
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
ports:
- 80:80
- 443:443
service1:
image: halverneus/static-file-server:latest
environment:
- VIRTUAL_HOST=service1.mydomain.com
- FOLDER=/app
volumes:
- /srv/server-service1:/app
expose:
- 8080
service2:
image: unknownpgr/service2:latest
environment:
- VIRTUAL_HOST=service2.mydomain.com
- FOLDER=/app
volumes:
- /srv/server-service2:/app
- /var/log:/app/log
expose:
- 80
entrypoint: ['node','/app/index.js']
먼저 제일 위에 있는 version:'3'은 그냥 도커 컴포즈의 버전입니다. 도커 컴포즈는 2021년 1월 현재 3.8버전까지 출시되어 있는데, 버전별로 사용가능한 기능이 약간씩 차이가 있어 명시해줄 필요가 있습니다.
옵션들
그 아래 services 항목부터가 본격적으로 컨테이너를 정의하는 부분입니다. services의 각 요소 nginx, service1, service2는 각각이 하나의 컨테이너입니다. 그 아래 세부 요소들이 각 컨테이너의 속성을 결정합니다.
- 각 컨테이너의
image는 어떤 도커 이미지를 사용할 것인지를 지정하며,docker run을 할 때와 동일하게 먼저 로컬에서 이미지를 찾아본 후 docker hub를 탐색합니다. environment는 각 도커 이미지에 주어질 환경 변수입니다. 제가 설정한 환경 변수에 대해서는 아래에서 설명하겠습니다.volumes는docker run의-v옵션과 동일한 것으로, 호스트 디렉토리를 컨테이너 내부 디렉토리에 마운트합니다.host:container순서이므로,service1의 경우 호스트의/srv/server-service1디렉토리를 컨테이너 내부의/app디렉토리에 마운트하겠다는 의미가 됩니다.ports는 컨테이너 내부의 포트와 컨테이너 외부의 포트를 연결합니다. 이것 역시host:container순서로, 예를 들어80:123이라고 하면 컨테이너 내부의123번 포트를 호스트의80번 포트에 연결한다는 의미입니다.expose는 컨테이너 내부의 포트를 네트워크에 공개하겠다는 의미입니다. 그런데 docker compose를 사용하면 기본적으로 모든 컨테이너가 같은 네트워크에 연결되므로 이 명령어는 실제로는 아무런 의미가 없습니다. 그러나 이 명령어는 일종의 flag로 유용하게 사용됩니다. 아래에서 설명합니다.entrypoint는 도커 이미지가 띄워졌을 경우 수행될 딱 하나의 명령어입니다. 만약 여러 개의 명령어를 실행하고 싶다면 쉘 스크립트를 실행하도록 해야 합니다.- 위 파일에서는 보이지 않지만,
command라는 항목도 자주 사용됩니다.command는entrypoint에 지정된 명령어에 따라 붙는 인자같은 것입니다. 예를 들어entrypoint가echo이고command가hello world라면 컨테이너가 실행될 경우echo hello world가 실행됩니다.command와entrypoint는 기능적으로는 차이가 없습니다만,command의 경우 덮어쓰는 것이 가능합니다. 그래서 보통entrypoint로 명령어를 지정하고,command로 기본 옵션을 줍니다. 그러면 실행 시command옵션만 주면 명령어는 그대로 두고command부분만 덮어쓸 수 있어 편리합니다.
설명
위 이미지에서는 nginx컨테이너가 제일 흥미롭습니다. 이 서비스를 구동하는 서버는 wildcard domain을 사용하는데, 이는 할당받은 도메인 앞에 어떤 서브도메인이 오더라도 전부 해당 서버로 라우팅해주는 기능입니다. 예를 들어 위 서비스의 경우 mydomain.com을 할당받은 경우로 service1.mydomain.com, service2.mydomain.com, asdf.fdsa.mydomain.com, helloworld.mydomain.com등 그 어떤 서브도메인을 입력하더라도 반드시 이 서버로 라우팅됩니다. 그러면 서버에서는 비록 같은 포트로 요청이 들어왔을지라도 요청한 도메인을 보고 서로 다른 서비스로 연결해줄 수 있습니다. 이러한 기술을 reverse proxy라 합니다.
nginx는 이런 reverse proxy를 쉽게 구현할 수 있는 기능을 제공합니다. 그리고 nginx-proxy 컨테이너는 이 쉬운 구현을 더 쉽게 구현할 수 있는 방법을 제공합니다. 이 컨테이너는, 간략히 설명하자면, 같은 네트워크에 연결된 도커 컨테이너들을 훑어봅니다. 그리고 그중 expose를 설정했으면서 VIRTUAL_HOST 환경 변수를 가진 컨테이너를 찾습니다. 그리고 그 컨테이너의 VIRTUAL_HOST에 해당하는 도메인으로 요청이 들어올 경우 그 컨테이너로 포워딩해줍니다. 위 예시에서 아래의 두 컨테이너가 VIRTUAL_HOST환경변수와 쓸데없어 보이는 expose를 설정한 이유가 여기에 있습니다.
결과
결과적으로 각종 서비스를 한 서버에서 부담없이 돌릴 수 있게 되었습니다. 포트 번호도 신경쓸 필요가 없고, 좋은 점이 한두 가지가 아니네요. 특히 서비스를 재시작할 필요가 있을 때 고민없이 그냥 하면 된다는 점이 참 큰 장점인 것 같습니다.