Docker Compose를 사용하여 여러 컨테이너를 한번에 관리해보자

웹 앱을 개발 할 때 대부분의 경우는 앱과 데이터베이스의 두가지가 필요하다.

데이터베이스 부분을 DaaS와 같이 외부 서비스로 이용하는 경우도 있겠지만 자신의 서버에 직접 돌리는 경우도 많을 것이고, 데이터베이스를 컨테이너 안에서 돌리고 싶은 경우도 있을 것이다.

이번 포스트에서는 앱과 데이터베이스의 컨테이너를 동시에 관리 할 수 있는 docker compose에 대해 알아본다. 예제로 사용 할 앱은 node.jsMongoDB를 사용하며 git 링크는 이곳.


#Docker Compose 설치

Docker Compose는 Docker를 설치하는 것 만으로 자동으로 설치가 되는 건 아니고, 따로 설치를 해 주어야 한다. 아래 커맨드를 실행하여 설치해준다.

sudo apt-get install docker-compose

#앱의 컨테이너 이미지 설정

우선 예제 앱을 git clone으로 다운 받아준다. 다운 받았다면 nodejs-with-mongodb-api-example이라는 폴더가 생성되어 있을 것이다. 이 폴더로 이동해 준 다음, 앱의 컨테이너 이미지 생성 설정을 위한 Dockerfile을 만들어 준다.

#Dockerfile

#Base Image
FROM node:14-alpine

#디렉토리 설정
WORKDIR /app

#package.json을 우선적으로 복사
COPY package*.json ./

#패키지들을 설치해주고 캐쉬를 삭제
RUN npm install && npm cache clear --force

#나머지 앱 파일들 복사
COPY . .

#ts파일로 만들었기 때문에 js로 compile
RUN npm run build

우선 위와 같은 설정으로 해 준다. CMD 부분이 빠진 이유는 아래에 설명 할 docker-compose.yml에서 설정 할 것이기 때문이다.


#docker-compose.yml 설정

여러 컨테이너에 대한 설정, 컨테이너를 동시 구동/정지 하고 싶을 때 사용하면 편리한게 Docker Compose이다. 물론 각각 개별의 컨테이너를 따로 구동 시켜도 문제 될 건 없지만, 관리의 측면에서 Docker Compose를 사용하면 더 편리하다.

Docker Compose를 사용하기 위해서는 docker-compose.yml이라는 설정 파일이 필요하다. 앱의 루트 디렉토리 안에 docker-compose.yml 파일을 만들어주자. 내용은 아래와 같다.

#Docker Compose의 syntax (문법) 버전 설정
version: '3'

#컨테이너화 할 서비스들의 리스트와 설정
services:
  #앱 부분에 관한 설정
  app:
    build: . #docker-compose.yml이 있는 디렉토리와 같은 곳에 있는 Dockerfile로 이미지 빌드
    container_name: app #컨테이너 이름 지정
    command: npm run dev:watch #개발 환경 버전의 컨테이너 실행
    ports:
      - 4000:4000
    environment:
      MONGO_URL: mongodb #mongodb에 연결 할 아이피를 지정
      PORT: 4000
    volumnes:
      - ./:/app #로컬에 있는 앱 관련 파일들과 연결되게 마운트
      - /app/node_modules #로컬에 있는 node_modules와 연결되지 않게 아무 지정 없는 마운트
    links:
      - mongodb #mongodb 서비스(컨테이너)와 서로 연결
    depends_on:
      - mongodb #mongodb 컨테이너가 먼저 구동되고 완료 될 때까지 기다림
  mongodb:
    image: mongo
    container_name: mongodb
    ports:
      - 27017:27017

이해를 돕기 위한 주석을 간단하게 추가 했지만, 중요한 부분을 따로 설명하도록 한다.

우선 맨 위의 버전은 docker-compose의 문법이 어느버전을 따를 것인가를 지정 해 주는 것이다. 자신의 환경에 설치된 Docker의 버전과 호환되는 버전을 지정해 줘야 한다. 호환성은 공식 사이트에서 확인 할 수 있는데 지금 시점에선 일단 3을 쓰면 크게 문제 될 건 없다.

services 부분에는 각각의 컨테이너에 대한 설정을 해준다. 이번 예제에서는 appmongodb 두가지 서비스를 컨테이너화 하기 때문에 그에 대한 설정을 하였다.

컨테이너를 만들기 위해선 컨테이너 이미지가 필요하다. app의 경우 직접 생성한 이미지를 사용 할 것이기 때문에 build 설정을 사용하였고, 이 설정에서는 Dockerfile의 위치를 지정해 줘야 한다. 이번 예제에서는 docker-compose.ymlDockerfile가 같은 디렉토리에 있기 때문에 .라고 설정하였다. mongodb의 경우는 이미 존재하는 공식 mongodb 컨테이너 이미지를 사용하면 되기 때문에 build가 아니고 image 설정을 사용했고, image 설정에는 이미지 파일을 지정해 줘야 한다.

container_name은 본인이 나중에 알기 쉽게 각 서비스 컨테이너의 이름을 지정해주는 설정이다. 따로 설정하지 않으면 Docker가 알아서 디렉토리이름과 서비스 이름을 조합해서 이름을 만들어준다.

command는 컨테이너를 구동했을 때 실행 할 커맨드를 지정해준다. 예제 앱 같은 경우 개발 환경은 npm run dev:watch, 실제 라이브 환경은 npm start를 이용한다. package.jsonscripts 부분을 보면 알 수 있다. 개발 환경에서는 nodemon을 이용해서 앱의 코드가 업데이트 되었을 때 자동으로 tsjs로 컴파일 해주고 앱을 다시 구동해주는 일반적인 개발환경이 사용되어있다. 이번 예제에서는 개발환경을 컨테이너에 옮길 수 있다는 것도 보여주기 위해 npm run dev:watch를 사용하였다.

appenvironment는 환경 변수의 값을 설정 해 줄 수 있는 설정이다. node.js에서는 일반적으로 process.env.xxxx 이런식으로 불러 올 수 있는 변수의 값을 설정해주는 것이다. 여기서 사용된 MONGO_URL이란 변수는 index.ts의 22번째 줄을 보면 몽고디비의 호스트 설정하는 부분에서 MONGO_URL 변수를 사용하기 때문이다. 컨테이너가 아니고 로컬 환경에서 개발을 하고 mongodb를 사용한다면 일반적으로는 mongodb://localhost로 연결하는게 일반적인데, 앱이 컨테이너에서 구동되고 있고, 또 다른 컨테이너에서 구동되고 있는 mongodb에 연결하기 위해서는 컨테이너안에 있는 mongodb에 접속 할 수 있는 주소를 사용해야한다. 컨테이너 안에 있는 mongodb는 localhost가 아니기 때문에 제대로 설정을 해주지 않으면 앱이 mongodb에 연결을 할 수 없다. 여기서 environment에서 MONGO_URL을 설정해주면 docker가 알아서 mongodb의 컨테이너 호스트 주소를 설정해주는 것이다.

volumes부분에서 /app/node_modules를 로컬파일과 마운트 하지 않은 이유는, node_modules의 환경설정이 OS에 따라 다르기 때문이다. 컨테이너안은 linux설정을 따르게 되는데, 본인의 로컬 개발환경이 MacOS 일 경우, 로컬과 컨테이너 안의 node_modules를 마운트해버리면 호환성에 문제가 발생 할 수 있다. 이 문제를 방지하기 위해서 "Volume Trick"을 이용해서 익명 볼륨을 적용하면 로컬과 연결되지 않고 컨테이너의 node_modules를 유지 할 수 있다. 컨테이너를 중단/파기하고 다시 시작해도 캐쉬된 node_modules를 사용하는 것이 가능하게 된다.

links는 컨테이너를 서로 연결해줌으로써 서로간의 통신이 가능하게 해 주는 설정이고, depends_onapp 컨테이너는 mongodb 컨테이너에 의존한다는 뜻이며, mongodb 컨테이너가 먼저 구동되야한다는 설정이다. 데이터베이스가 먼저 준비 되어 있지 않으면 앱이 데이터에 접속을 할 수 없기 때문에 당연한 설정이 된다.


#Docker Compose실행

docker-compose.yml 파일에 대한 설정이 끝났다면 이제 실행하기만 하면 된다.

docker-compose up --d

--d 플래그를 넣어주면 백그라운드에서 실행된다.

Starting mongodb ... done
Starting app     ... done

이런식으로 mongodb 컨테이너가 먼저 준비되고 그 다음에 app 컨테이너가 구동되는 걸 알 수 있다. 4000번 포트로 앱이 잘 구동되는지 확인해보자.

마지막으로 컨테이너 안에 개발 환경이 구동되어 있는게 제대로 작동하는지 확인을 해보자. 로컬 환경에 있는 index.ts 파일을 열어서 38번째 줄의 title 값을 변경해보자. 값을 변경한 다음 앱 페이지를 새로고침 해보자.

로컬 파일과 컨테이너 파일이 서로 마운트 되어있기 때문에 로컬의 index.ts를 수정하면 컨테이너 안에서도 수정된 index.ts를 인식하고, 컨테이너 안에서 실행 중인 nodemon이 파일 변경을 인식한 후 자동으로 컴파일 & 앱 재시작을 해주기 때문에 바로 변경사항이 반영되는 것이다.

컨테이너를 중단하고 싶을 땐 아래 커맨드로 한꺼번에 중단시킬 수 있다.

docker-compose down

Docker Compose의 사용법과 앱 개발환경을 컨테이너안에서 실행시키는 법에 대해 알아보았다. 실제 앱의 개발 환경을 컨테이너화 시킬 땐 환경 설정 변수를 알맞게 미리 코드안에 적용시켜 두는 것이 중요하다. 개발 환경까지 컨테이너화 시킬 수 있다면, 다른 개발자들과 협업 할 때 각자의 로컬 환경이 중요하지 않게 되기 때문에 매우 유용하게 사용 될 수 있다. 로컬 환경을 깨끗하게 관리 할 수 있는 건 덤 :)