본문 바로가기

DevOps & Infra/Docker

[Infrastructure/Docker] 튜토리얼 살펴보기#007. 도커 컴포즈(Docker compose) 사용하기

도커 컴포즈(Docker compose)

이전 포스트에서 두 개의 컨테이너(Todo 애플리케이션과 MySQL 서버)를 서로 통신하게끔 하여 하나의 서비스 환경을 구축하였습니다. 이처럼 여러 개의 서비스를 하나의 서비스로 통합하는 과정에서, 각 서비스는 개별 컨테이너로 동작하는 것을 권장한다고 소개하였습니다. 하지만 도커 데스크탑에서 통합된 각 컨테이너가 분리되어 노출되고, 실제로 컨테이너를 개별적으로 실행하고 종료해야 한다는 불편함이 아직 남아있습니다.

이같은 문제를 해결하기 위해 도커는 컴포즈(Compose)를 제공합니다. 도커 컴포즈는 다중 컨테이너 애플리케이션을 정의하고 공유하기 위한 도구입니다.

컴포즈를 사용하여 YAML 파일을 생성 할 수 있습니다. YAML 파일은 여러 개의 서비스를 정의하고 단일 명령문으로 이들의 실행과 종료를 관리합니다.

  • 여러 개의 애플리케이션에 대한 애플리케이션 스택(Stack)을 파일로 정의하고, 도커 레지스트리에 한 번에 Push/Pull 할 수 있습니다.
  • 협업 단계에서 저장소를 Pull하여 컴포즈된 앱을 즉시 사용 할 수 있습니다.

만약 Windows 또는 MacOS 기반의 도커 데스크탑을 설치하였다면, 별도의 추가 설치 없이도 도커 컴포즈를 사용 할 수 있습니다(Linux 사용자의 경우 도커 컴포즈를 추가 설치해야 합니다). PWD(Play With Docker)에도 도커 컴포즈가 이미 설치되어 있으며, 별도 설정 없이도 기능을 사용 할 수 있습니다.

다음 명령문을 사용하여 도커 컴포즈의 설치 여부와 버전을 확인합니다.

$ docker compose version
Docker Compose version v2.2.3

컴포즈 파일 작성

튜토리얼 프로젝트의 루트 경로에서 docker-compose.yml 파일을 생성합니다. 지금까지 튜토리얼을 따라왔다면, 디렉토리 계층은 다음과 같습니다.

app
├── Dockerfile
├── docker-compose.yml
├── node_modules
├── package.json
├── spec
├── src
└── yarn.lock

컴포즈 파일은 스키마 버전을 정의하는 것부터 시작합니다. 대부분의 경우 지원하는 최신 버전을 사용하는 것이 가장 좋습니다. Compose file reference에서 현재 스키마 버전과 호환되는 도커 버전 매트릭스를 확인 할 수 있습니다.

version: "3.8"

다음으로 애플리케이션의 일부로 실행되는 서비스(또는 컨테이너) 목록을 정의합니다.

version: "3.8"

services:

이제 실행되는 서비스를 하나씩 컴포즈 파일로 마이그레이션하겠습니다. 우선 Todo 애플리케이션부터 시작해보도록 하겠습니다. 그 전에, Todo 컨테이너를 실행하기 위한 명령문이 기억나시나요?

$ docker run -dp 3000:3000 \
  -w /app -v "$(pwd):/app" \
  --network todo-app \
  -e MYSQL_HOST=mysql \
  -e MYSQL_USER=root \
  -e MYSQL_PASSWORD=secret \
  -e MYSQL_DB=todos \
  node:12-alpine \
  sh -c "yarn install && yarn run dev"

PowerShell 사용자라면 다음 명령문을 사용했습니다.

$ docker run -dp 3000:3000 `
  -w /app -v "$(pwd):/app" `
  --network todo-app `
  -e MYSQL_HOST=mysql `
  -e MYSQL_USER=root `
  -e MYSQL_PASSWORD=secret `
  -e MYSQL_DB=todos `
  node:12-alpine `
  sh -c "yarn install && yarn run dev"

그럼 컨테이너 실행 명령문을 바탕으로 마이그레이션을 시작해볼까요? 가장 먼저 컨테이너에 대한 서비스 이름과 이미지를 정의합니다. 서비스의 이름은 임의로 지정하면 됩니다. 이때 명명된 서비스 이름은 자동으로 네트워크 별칭으로 사용됩니다. 이미지는 명령문에서처럼 node:12-apline을 입력합니다.

version: "3.8"

services:
  app:
    image: node:12-alpine

일반적으로 command는 이미지에 대한 정의에 가깝습니다. command의 각 항목은 입력 순서가 중요하지 않으므로, 그대로 옮겨적으면 됩니다.

version: "3.8"

services:
  app:
    image: node:12-alpine
    command: sh -c "yarn install && yarn run dev"

다음으로 호스트 포트와 컨테이너 포트를 맵핑하는 플래그를 마이그레이션하겠습니다.

version: "3.8"

services:
  app:
    image: node:12-alpine
    command: sh -c "yarn install && yarn run dev"
    ports:
      - 3000:3000

다음으로 작업 디렉토리(-w /app)와 볼륨 맵핑(-v "$(pwd):/app")을 모두 마이그레이션합니다. 도커 컴포즈를 사용하면 볼륨 정의를 위해 현재 디렉토리 기준 상대 경로를 사용 할 수 있습니다.

version: "3.8"

services:
  app:
    image: node:12-alpine
    command: sh -c "yarn install && yarn run dev"
    ports:
      - 3000:3000
    working_dir: /app
    volumes:
      - ./:/app

마지막으로 환경 변수를 마이그레이션합니다.

version: "3.8"

services:
  app:
    image: node:12-alpine
    command: sh -c "yarn install && yarn run dev"
    ports:
      - 3000:3000
    working_dir: /app
    volumes:
      - ./:/app
    environment:
      MYSQL_HOST: mysql
      MYSQL_USER: root
      MYSQL_PASSWORD: secret
      MYSQL_DB: todos

여기까지 Todo 컨테이너에 대한 서비스를 정의하였습니다. 다음으로 함께 실행되어야 하는 MySQL 컨테이너에 대한 서비스를 정의하도록 하겠습니다. mysql이라는 서비스 이름을 입력합니다. 앞서 설명한것처럼 mysql 서비스 이름은 자동으로 네트워크 별칭이 됩니다. MySQL 이미지는 mysql:5.7을 사용합니다.

version: "3.8"

services:
  app:
    # 생략
  mysql:
    image: mysql:5.7

다음으로 볼륨 맵핑을 마이그레이션합니다. 이전에 우리가 MySQL 컨테이너를 도커로 실행하였을 때, named volume이 자동으로 생성되었습니다. 컴포즈로 구성할 때에는 최상위 볼륨에 named volume을 정의하고, 미리 정의된 볼륨과 마운트 지점을 연결해서 사용합니다.

version: "3.8"

services:
  app:
    # 생략
  mysql:
    image: mysql:5.7
    volumes:
      - todo-mysql-data:/var/lib/mysql

volumes:
  todo-mysql-data:

마지막으로 MySQL 환경 변수를 마이그레이션합니다.

version: "3.8"

services:
  app:
    # 생략
  mysql:
    image: mysql:5.7
    volumes:
      - todo-mysql-data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: secret
      MYSQL_DATABASE: todos

volumes:
  todo-mysql-data:

여기까지 진행하였다면, docker-compose.yml 파일의 내용은 다음과 같습니다.

version: "3.8"

services:
  app:
    image: node:12-alpine
    command: sh -c "yarn install && yarn run dev"
    ports:
      - 3000:3000
    working_dir: /app
    volumes:
      - ./:/app
    environment:
      MYSQL_HOST: mysql
      MYSQL_USER: root
      MYSQL_PASSWORD: secret
      MYSQL_DB: todos
  mysql:
    image: mysql:5.7
    volumes:
      - todo-mysql-data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: secret
      MYSQL_DATABASE: todos

volumes:
  todo-mysql-data:

애플리케이션 스택 실행

이전에 실행중이었던 Todo 애플리케이션 또는 MySQL 서버에 대한 컨테이너가 아직 남아있는지 확인합니다. 여전히 실행중이라면 docker rm 명령문을 사용하여 컨테이너를 중지하고 삭제합니다.

이어지는 명령문을 실행하여 애플리케이션 스택을 실행합니다. 이때 모든 것이 데몬으로 실행되기 위해서 -d 플래그를 추가합니다. 명령문은 우리가 작성한 컴포즈 파일(docker-compose.yml)이 위치한 경로에서 실행되어야 합니다.

$ docker-compose up -d

명령문이 실행되면, 콘솔 또는 터미널에서 다음과 같은 출력을 확인 할 수 있습니다.

$ docker-compose up -d
[+] Running 4/4
 ⠿ Network app_default           Created                                                                                                                                                             0.0s
 ⠿ Volume "app_todo-mysql-data"  Created                                                                                                                                                             0.0s
 ⠿ Container app-app-1           Started                                                                                                                                                             0.9s
 ⠿ Container app-mysql-1         Started

네트워크와 볼륨이 생성되었습니다. 기본적으로 도커 컴포즈는 애플리케이션 스택 전용의 네트워크를 자동으로 만듭니다(컴포즈 파일에서 네트워크 생성에 대한 정의가 없는 이유입니다).

도커 컴포즈의 로그 출력 명령문을 실행합니다. 마찬가지로 컴포즈 파일이 위치한 경로에서 실행되어야 합니다. -f 플래그는 로그를 "follow"하라는 의미로 추가적으로 기록되는 로그를 실시간으로 출력합니다.

docker-compose logs -f

출력되는 로그를 살펴보면, 각 서비스의 로그가 단일 스트림으로 인터리빙(Interleaving)되는 것을 확인 할 수 있습니다.

$ docker-compose logs -f
app-app-1    | yarn install v1.22.17
app-app-1    | [1/4] Resolving packages...
app-app-1    | warning Resolution field "ansi-regex@5.0.1" is incompatible with requested version "ansi-regex@^2.0.0"
app-app-1    | warning Resolution field "ansi-regex@5.0.1" is incompatible with requested version "ansi-regex@^3.0.0"
app-app-1    | success Already up-to-date.
app-app-1    | Done in 0.85s.
app-app-1    | yarn run v1.22.17
app-app-1    | $ nodemon src/index.js
app-app-1    | [nodemon] 2.0.13
app-app-1    | [nodemon] to restart at any time, enter `rs`
app-app-1    | [nodemon] watching path(s): *.*
app-app-1    | [nodemon] watching extensions: js,mjs,json
app-app-1    | [nodemon] starting `node src/index.js`
app-app-1    | Waiting for mysql:3306.......
app-app-1    | Connected!
app-app-1    | Connected to mysql db at host mysql
app-app-1    | Listening on port 3000
app-mysql-1  | 2022-02-28 09:56:34+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 5.7.37-1debian10 started.
app-mysql-1  | 2022-02-28 09:56:34+00:00 [Note] [Entrypoint]: Switching to dedicated user 'mysql'
app-mysql-1  | 2022-02-28 09:56:34+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 5.7.37-1debian10 started.
app-mysql-1  | 2022-02-28 09:56:34+00:00 [Note] [Entrypoint]: Initializing database files
(생략)

각 서비스의 이름이 라인의 시작 부분에 명시됩니다. 만약 특정 서비스에 대한 로그만 출력하고자 한다면 명령문의 마지막 부분에 서비스 이름을 지정 할 수 있습니다.

docker-compose logs -f [서비스 이름]

Todo 애플리케이션이 실행되면 MySQL이 가동되어 준비될 때까지 기다렸다가 연결을 시도합니다. 이 부분에 대한 내용도 로그에서 살펴볼 수 있습니다(Waiting for mysql:3306 이하).

기본적으로 도커 컴포즈는 각 컨테이너가 다른 컨테이너의 실행과 준비를 감지할 수 있는 기능을 제공하지는 않습니다. 그럼에도 불구하고 Todo 애플리케이션이 MySQL 가동을 기다리는 이유는 무엇일까요? Node.js 기반 프로젝트에서 지원하는 wait-port 종속성 때문입니다. Node.js 외의 다른 언어/프레임워크에서도 이와 같은 기능을 제공하는 경우가 있습니다.

도커 데스크탑에서 확인

우리는 도커 컴포즈 파일을 작성하고, 애플리케이션 스택을 한 번의 명령문으로 모두 실행하는 것까지 진행하였습니다. 그렇다면 도커 데스크탑에서 컴포즈의 실행 결과는 어떻게 출력될까요?

app 이름의 컨테이너가 실행 중이고, 하위 항목으로는 app-mysql-1과 app-app-1이 실행 중인것을 확인 할 수 있습니다. app은 도커 컴포즈의 프로젝트 이름에 해당합니다. 프로젝트 이름은 하위 컨테이너를 그룹화하는 목적으로 사용되며, 단순히 도커 컴포즈 파일(docker-compose.yml)이 위치한 디렉토리의 이름을 따와서 사용합니다.

app 컨테이너의 하위 항목은 다음 규칙과 같이 명명됩니다.

[프로젝트 이름]_[서비스 이름]_[레플리카 번호]

프로젝트 이름은 컨테이너의 이름과 동일합니다. 서비스 이름은 우리가 작성한 도커 컴포즈 파일에서 정의하였습니다. 컴포즈 내에서 각 서비스(프로세스)는 1개씩 실행되므로 서비스 별 레플리카 번호는 항상 1입니다.

중단하기

도커 데스크탑에서 컨테이너를 중단하거나, 또는 터미널에서 다음 명령문을 사용합니다. 실행 결과에서 볼 수 있듯이, 애플리케이션 스택을 구성하는 모든 컨테이너와 자동으로 생성되었던 네트워크가 제거됩니다.

$ docker-compose down
[+] Running 3/3
 ⠿ Container app-app-1    Removed                                                                                                                                                                    0.2s
 ⠿ Container app-mysql-1  Removed                                                                                                                                                                    3.8s
 ⠿ Network app_default    Removed

이때 컴포즈 파일에서 정의된 볼륨은 제거되지 않음을 확인 할 수 있습니다(도커 데스크탑에서도 여전히 볼륨이 남아있음을 확인 할 수 있습니다). 볼륨이 남아있어야 애플리케이션의 데이터가 보존되므로, 어찌보면 당연한 것일 수 있습니다.

만약, 볼륨을 함께 삭제하고자 한다면 중지 명령문에서 --volumes 태그를 추가할 수 있습니다.

$ docker-compose down --volumes
[+] Running 1/0
 ⠿ Volume app_todo-mysql-data  Removed