실습으로 배우는 Docker 입문 | 4. Docker Compose를 사용하여 여러 컨테이너 배포

지금까지는 하나의 이미지 또는 컨테이너를 취급하는 방법을 진행해봤다. 그러나 실제 개발에서는 데이터베이스와 Web 서버 등의 여러 공정을 조합하여 시스템을 구성하는 것이 일반적이다. Docker를 사용한 시스템에서는 하나의 관심사를 하나의 컨테이너에 할당한다(Each container should have only one concern) 공식 문서에서도 권장되고 필연적으로 여러 컨테이너를 취급하는 경우가 종종 있다. 본 문서는 개발 및 테스트 환경을 위한 로컬 호스트에서 여러 컨테이너를 시작하는 방법으로 Docker Compose라는 도구를 소개한다.

여러 컨테이너 관리 도구 Docker Compose 개요

Docker Compose는 여러 컨테이너로 구성된 Docker 응용 프로그램의 설정을 작성하고 실행하기 위한 도구이다.

여러 컨테이너(데이터베이스, 캐시, Web API 등)로 구성된 시스템을 docker 명령만으로 시작하면 개별 컨테이너의 설정을 매개 변수로 지정하면서 컨테이너 의존 관계의 순서대로 명령을 실행해야 한다.

한편 Docker Compose를 사용하면 여러 컨테이너 설정과 의존 관계를 설정 파일에 작성하고 하나의 명령으로 여러 컨테이너를 한 번에 실행할 수 있다.

Docker Compose이 사용되는 주요 사례

개발 환경의 표준화

팀 구성원이 규칙대로 개발 환경을 수동으로 정비하면 시간이 걸릴뿐만 아니라 수작업 실수를 유발할 가능성이 있다. Docker Compose를 사용하여 설정 파일을 전달하면, 개발 환경의 세부 사항을 걱정하지 않고 하나의 명령으로 환경을 정비 할 수 있다. 이로 개발 환경 정비 작업을 간소화 할 수 있다.

테스트 환경의 자동화

CI(Continuous intergration)와 CD(Continuous Deployment)를 할 경우, end to end 테스트(모든 구성 요소를 결합하여 테스트)를 자동화하려면 이상적으로는 테스트마다 독립적인 테스트 환경을 제공 해야 한다. Docker Compose를 사용하면 필요한 테스트 환경을 명령어 1개로 시작할 수 있으며, 검사가 완료되면 쉽게 파기할 수도 있다.

단일 호스트 배포

운영 환경이 단일 서버의 경우 Docker Compose를 사용하여 배포 할 수 있다. 자세한 내용은 문서를 참조해라.

그러나 응용 프로그램을 확장하기 위해 다중 노드에 배포하려면 일반적으로 Docker Engine의 Swarm mode 라는 기능 또는 AWS ECSGoogle Container Engine과 같은 클라우드 클러스터 매니저가 더 적합하다.

단일 컨테이너를 시작으로 Docker Compose을 이해하기

Docker Compose 설정 파일(docker-compose.yml) 작성과 움직이는 방법을 알기 위해 Docker 이미지 빌드 페이지에서 만든 이미지를 사용하여 단일 컨테이너의 시작 및 중지하는 방법을 소개한다. 사전에 실습을 하지 않았다면 먼저 실습을 하고 오길 바란다.

docker-compose.yml 작성

다음은 앞에서 만든 이미지에서 컨테이너를 시작 위한 설정 파일이다. 위의 설정 파일을 docker-compose.yml라는 이름으로 저장한다.

version: '3'
services:
  myfirstapp:
    image: myfirstapp
    ports:
    - "8888:5000"

이 설정 파일에 내용은 아래와 같다.

  • Docker-Compose 파일 포맷 버전 “3” 이다.
    • 포맷에 대한 자세한 내용은 공식 문서를 참조하여라.
    • (주의) Docker Compose 파일 포맷 버전 3은 Docker Engine 1.13.0 이상에서 동작 가능하다. 설치되어 있지 않은 경우에는 “[Docker 이미지 컨테이너 관리 메커니즘](Docker 이미지 컨테이너 관리 메커니즘)” 페이지 를 참고에 설치한다. 또한 Docker for Windows, Docker for Mac, Docker Toolbox를 설치하는 경우 Docker Compose도 자동으로 설치되기 때문에 추가 설치가 따로 필요 없다. Linux에 설치하는 방법에 대한 공식 문서를 참조하여라.
  • 서비스 이름은 “myfirstapp” 이다.
    • services 아래에 나와 있는 항목은 외부에서 인식할 수 있는 기능 단위이며, “서비스"라고 한다.
    • 그러나 서비스는 컨테이너가 연결되기 때문에 서비스의 실태는 컨테이너이다.
  • 서비스에 연결하는 컨테이너는 “myfirstapp ‘라는 이미지에서 시작된다.
  • 컨테이너의 포트 5000 번을 호스트의 8888 번에 매핑한다. 이렇게 하면 컨테이너의 Web 서버에 https://localhost:8888 에 액세스 할 수 있게된다.

Docker Compose으로 컨테이너의 시작

docker-compose.yml에 있는 디렉터리에서 다음 명령을 실행하면 컨테이너가 시작된다.

$ docker-compose up
.. 중간 생략 ...
Creating network "myfirstapp_default" with the default driver
Creating myfirstapp_1
Attaching to myfirstapp_1

Docker 이미지 빌드에서 보여준 바와 같이 https://localhost:8888/ 으로 접속하면 Web 응용 프로그램이 표시된다.

이는 다음 docker 명령을 실행하는 것과 같은 의미이다.

% docker run -p 8888:5000 myfirstapp

docker 명령과 비교하여 docker-compose 좋은 점은 설정을 대충 설정 파일에 기술하는 점에 있다. 주요 유스 케이스 절에서 소개 한대로, Docker Engine 및 Docker Compose을 설치하는 환경 만 있으면 docker-compose up에서 동일한 환경을 구축 할 수 있다.

Docker Compose가 지원하는 명령어

docker-compose updocker-compose.yml에 적힌 컨테이너의 작성, 시작 등을 한꺼번에 처리 할 수 있다.

옵션을 붙이지 않고 실행하면 포 그라운드에서 실행되고 로그가 표준 출력에 표시된다. 다음과 같이 -d옵션을 붙이면 백그라운드에서 실행된다.

% docker compose up -d

컨테이너를 중지하려면 다음 명령을 실행한다.

% docker compose stop

컨테이너를 다시 시작하려면 다음 명령을 실행한다.

% docker compose start

특정 컨테이너를 지정하여 시작할 수도 있다.

% docker compose start myfirstapp

컨테이너의 정지 및 취소를 한꺼번에하려면 다음 명령을 실행한다.

% docker compose down

기타 명령에 대한 자세한 내용은 공식 문서를 참조해라.

여러 컨테이너를 시작 해보자

Docker Compose의 사용법은 알게 되었다면, 좀 더 실용적인 예로 Docker 공식 샘플 voting app (투표 앱)을 소개한다.

이 어플리케이션은 5개의 서비스로 구성되어 있다.

voting app 구성

voting app의 구성 ( github.com/dockersamples/example-voting-app 에서 인용)

(주의) 여기에서는 Docker Compose 사용법이나 docker-compose.yml 작성법에 초점을 맞추고 설명하는 샘플의 개별 컨테이너의 구현 자세한 내용은 Github 저장소 를 참조해라.

서비스 이름 내용 보충
voting-app 사용자에게 투표를 촉구하는 Web 페이지를 표시하는 응용 프로그램. 데이터는 redis에 저장한다. Python과 Flask에 따르면 Web 응용 프로그램
redis 투표 결과를 임시로 저장 캐시. Redis
worker 투표 결과를 가져오고 Postgres 데이터베이스에 저장하는 작업자. .NET
db 투표 결과를 저장하는 데이터베이스. 데이터는 Docker volume 보관한다. Postgres
result-app 실시간 투표 결과를 표시하는 Web 응용 프로그램. Node.js에 따르면 Web 응용 프로그램

voting app 시작하기

응용 프로그램의 동작을 확인하기 위해 다음과 같이 Github 저장소에서 소스 코드를 받아와서 docker-compose up 명령 컨테이너를 시작한다. 처음에는 이미지 빌드가 달리는 시간이 걸리는 점에 주의하자.

% git clone https://github.com/dockersamples/example-voting-app.git
% cd example-voting-app
% docker-compose up

콘솔에 출력이 멈추고 컨테이너가 기동되면 브라우저에서 “https://localhost:5000"에 접속한다. 다음과 같은 투표 화면이 표시된다. CATS와 DOGS 중 하나를 선택한다.

투표 화면

브라우저에서 “https://localhost:5001"에 접속한다. 투표 결과 화면이 표시된다. 투표 화면에서 선택한 분에 투표가 들어있는 것을 확인할 수 있다.

투표 결과 화면

여러 컨테이너를 처리하는 docker-compose.yml 작성

여기에서 Docker Compose의 기능과 설정 파일 작성에 대해 설명하고 있다. 자세한 설정 파일의 작성 내용은 공식 문서를 참조해라.

voting-app의 설정 파일은 다음과 같다. 설정 파일에는 다음이 정의되어 있다.

  • 서비스(services)가 5 개(vote, result, worker, redis, db)
  • volume 1개 (db-data)
  • network가 2개 (front-tier, back-tier)
version: "3"

services:
  vote:
    build: ./vote
    command: python app.py
    volumes:
     - ./vote:/app
    ports:
      - "5000:80"
    networks:
      - front-tier
      - back-tier

  result:
    build: ./result
    command: nodemon --debug server.js
    volumes:
      - ./result:/app
    ports:
      - "5001:80"
      - "5858:5858"
    networks:
      - front-tier
      - back-tier

  worker:
    build:
      context: ./worker
    networks:
      - back-tier

  redis:
    image: redis:alpine
    container_name: redis
    ports: ["6379"]
    networks:
      - back-tier

  db:
    image: postgres:9.4
    container_name: db
    volumes:
      - "db-data:/var/lib/postgresql/data"
    networks:
      - back-tier

volumes:
  db-data:

networks:
  front-tier:
  back-tier:

services - 서비스 정의

단일 컨테이너의 예에서 설명한 것처럼 외부에서 인식할 수 있는 기능 단위를 “서비스"라고 한다. 서비스는 컨테이너가 연결되기 때문에 서비스의 실태는 컨테이너이다.

volumes - volume(데이터 지속성 영역)의 정의

volume 은 컨테이너의 라이프 사이클이 종료 된 후에도 데이터를 보관할 수 데이터 영역이다. 특징은 다음과 같다.

  • 데이터 지속성을 목적으로 한 기능을 위해 컨테이너가 삭제 되어도 volume이 명시 적으로 폐기되지 않는 한, volume에 있는 데이터는 유지된다.
  • volume은 특정 컨테이너 전용 volume뿐만 아니라 여러 컨테이너 사이에서 볼 수 volume도 만들 수 있다.
  • 호스트 측의 디렉터리를 volume으로 컨테이너에 탑재 할 수 있다.
    • 이 기능은 호스트와 컨테이너 사이에서 파일을 전달할 때 유효하다.

각 서비스의 volumes에는 서비스 별 volume 설정을 설명하고 있다. 예를 들어, vote 서비스는 호스트 측의 디렉터리 ./vote을 자동 컨테이너 전용 volume으로 컨테이너 디렉터리 /app에 마운트한다. 이렇게 하면 응용 프로그램 실행에 필요한 파일을 컨테이너로 받아 전달한다.

services:
  (생략)
  vote:
  (생략)
    volumes:
     - ./vote:/app

한편, db 서비스는 docker-compose.yml 최상위 volumes에 이름을 붙여 정의된 db-data라는 이름의 volume을 컨테이너의 디렉터리 /var/lib/postgresql/data에 마운트한다. db-data처럼 docker-compose.yml 최상위에 이름을 붙여 선언된 volume을 “네임드 volume(named volume)“라고 부른다.

services:
  (생략)
  db:
  (생략)
    volumes:
      - "db-data:/var/lib/postgresql/data"
volumes:
  db-data:
  • vote 서비스 volume와 같이, 서비스의 정의에 호스트 및 컨테이너 디렉터리 경로만 지정 volume 선언한 서비스 전용 volume 된다.
  • 한편 db-data 같은 네임드 volume은 여러 컨테이너에서 볼 수 있다.

networks - 네트워크 설정

network 는 서비스가 속한 네트워크이다.

voting-app에는 호스트 측에서 직접 데이터베이스에 액세스 의도하지 않은 데이터 변경되는 것을 방지하기 위해 데이터베이스 호스트에서 직접 액세스 할 수 없는 네트워크 back-tier에 배치하고, Web 서버는 호스트에서 액세스하는 네트워크 front- tier에 배치하고 있다.

이와 같이 각 서비스에 소속된 네트워크를 지정하기 위해서는 먼저 docker-compose.yml의 최상위에 네트워크를 선언한다.

networks:
  front-tier:
  back-tier:

그리고 각 서비스 정의 networks 서비스가 참여하는 네트워크를 지정한다. 다음의 경우 vote 서비스는 back-tierfront-tier에 속하고 redis 서비스는 back-tier에만 속하게 된다.

  vote:
  (생략)
    networks:
      - front-tier
      - back-tier
  (생략)

  redis:
  (생략)
    networks:
      - back-tier

동일한 네트워크에 속한 서비스 끼리는 호스트 이름으로 서비스 이름을 지정하여 상대 서비스에 연결할 수 있다. 예를 들어, vote 서비스와 redis 서비스는 모두 back-tier에 속하므로, vote 서비스에서 호스트 이름 redis에서 redis 서비스에 액세스 할 수 있다. 실제로 vote 서비스 구현(app.py)을 보면, 호스트 이름에 redis를 지정하고 있는 것을 알 수 있다 (포트는 생략되어 기본 포트 6379이 사용된다).

def get_redis():
    if not hasattr(g, 'redis'):
        g.redis = Redis(host="redis", db=0, socket_timeout=5)
    return g.redis

또한 Docker Compose는 처음 docker-compose up할시에 기본 네트워크를 하나 만들고 각 서비스를 기본 네트워크에 소속시킨다. 예를 들어, voting-app의 예라면 voting_default이 작성되어 있다.

$ docker-compose up
  (생략)
Creating network "voting_default" with the default driver

이 밖에 네트워크 설정에 대한 자세한 내용은 아래 공식 문서를 참조해라.

ports - 호스트, 컨테이너 사이의 포트 매핑

ports는 호스트, 컨테이너 사이의 포트 매핑을 지정한다. 컨테이너 포트 80번을 호스트의 포트 5000번에 매핑하여 브라우저에서 https://localhost:5000/ 으로 접속하면 vote 서비스 Web 응용 프로그램에 액세스 할 수 있다.

    ports:
      - "5000:80"

image - 사용하는 이미지의 지정

image에서 사용하는 이미지를 지정한다. 다음의 경우 redis 서비스는 기존의 이미지 redis:alpine을 사용하고 있는 것을 알 수 있다.

  redis:
    image: redis:alpine

build - 이미지 빌드

기존의 이미지에서 컨테이너의 요구 사항이 충족되지 않는 경우, build에서 이미지 빌드 설정을 지정한다. build를 지정하면 로컬 캐시에 이미지가 없는 경우 컨테이너 시작 전에 이미지 빌드가 달리고 이미지를 만들어 준다.

다음의 경우 vote 서비스는 ./vote 디렉터리에 빌드에 필요한 구성 파일이있는 것을 지정하고 있다.

services:
  vote:
    build: ./vote

실제로 example-voting-app/vote/를 열어 보면 Web 응용 프로그램에 필요한 여러 파일이 있고 그 안에 Docker 이미지를 구축하기 위한 Dockerfile 있다(Docker 이미지 빌드에 대해서는 “이미지 컨테이너 관리 메커니즘” 를 참조해라).

또한, image 및 build를 모두 지정하면 빌드 한 결과 이미지 이름은 image에서 지정한 이미지 이름이다.

command - 컨테이너 시작 명령

command는 컨테이너 시작 명령을 지정한다. vote 서비스는 Flask 는 Python용 Web 프레임워크로 구현되어 있다. 여기에서는 Web 어플리케이션의 진입 점인 app.py 을 실행하고 있다.

    command: python app.py

Docker Compose의 설명은 여기까지이다. 여기에서는 Docker Engine과 Docker Compose 기능의 일부만 다뤘다. 꼭 샘플 코드 및 공식 문서를 보고, 내용을 파악할 수 있으면 자신의 서비스에 활용 해보자.

결론

본 기사에서는 Docker 공식 견본인 voting-app 를 예로 들어 Docker Compose 설정 파일 작성 및 명령의 실행 방법에 대해 소개하였다.

미들웨어를 제공 할 때 이용자에게 Docker 이미지와 Docker Compose 설정 파일을 제공하면, 환경 세트를 쉽게 구축 할 수있는 점은 소프트웨어의 출처에 있어서도 이용자에게 매우 유용할 것이다.

다만 앞서 언급했듯이 Docker Compose는 단일 노드에 배포에 사용할 도구를 위한 스케일아웃을 할 수 없고, 프로덕션 환경에는 적합하지 않다. 다음은 프로덕션 환경에 적합한 클라우드 클러스터 매니저를 소개하도록 하겠다.




최종 수정 : 2021-07-01