본문 바로가기

DevOps & Infra/Docker

[Infrastructure/Docker] 튜토리얼 살펴보기#006. Bind mount 사용하기

이전 포스트에서 튜토리얼 앱을 실행하는 컨테이너가 SQLite 데이터베이스를 유지하기 위해서 named volume을 사용하는 방법에 대해서 알아봤습니다. named volume은 데이터가 저장되는 위치를 사용자가 고려하지 않아도 되므로, 단순히 데이터를 저장하고 읽기 위해 적합한 방법입니다.

Bind mount

이번 포스트에서는 또 다른 마운팅 방식에 대해서 알아보려고 합니다. bind mount는 호스트 시스템에서 마운트 지점(Mountpoint)를 제어 할 수 있는 마운트 유형입니다. 마찬가지로 컨테이너의 파일 시스템을 유지할 수 있으며, 종종 컨테이너에 새로운 파일을 제공해야 하는 경우에 사용 할 수 있습니다. 애플리케이션에서 작업 할 때, 우리의 소스 코드를 컨테이너에 마운트하여 코드의 변경 사항을 확인하는 등의 업무가 가능해집니다(튜토리얼과 같이 Node.js 기반의 애플리케이션은 nodemon을 사용하면 파일 변경을 감지하고 애플리케이션을 재시작하는데 유용합니다).

볼륨 유형 비교

named volume과 bind volume은 도커 엔진과 함께 제공되는 주요한 볼륨 유형입니다. 물론 SFTP, Ceph, NetApp, S3 등을 지원하기 위한 추가적인 볼륨 드라이버를 사용 할 수도 있습니다.

다음 표는 기본적인 두 가지 볼륨 유형에 대한 간단한 비교입니다.

구분 named volume bind volume
호스트 시스템 마운트 지점 도커에서 결정 사용자가 결정
마운트 예시(-v 태그) [볼륨 이름]:[게스트 시스템 마운트 경로] [호스트 시스템 마운트 지점]:[게스트 시스템 마운트 경로]
새 볼륨 생성 컨테이너의 컨텐츠로 채움 빈 상태로 시작
볼륨 드라이버 지원 볼륨 드라이버를 지원 지원하지 않음

개발 모드(Dev-Mode) 컨테이너 시작

개발 워크플로우를 구축하기 위해서, 컨테이너에 다음 작업이 필요합니다. 지금까지 학습한 내용과 비교했을 때 생소한 것들이지만, 하나씩 진행해보도록 하겠습니다.

  • 소스 코드를 컨테이너에 마운트합니다.
  • "dev" 종속성(Dependencies)를 포함하여 모든 종속성을 설치합니다.
  • 파일 시스템의 변경 사항을 모니터링하기 위해 노드몬(nodemon)을 시작합니다.

튜토리얼 앱(Node.js 기반의 Todo 앱)이 실행중인 컨테이너가 없는지 확인합니다. 실행중인 컨테이너는 다음 명령문을 사용하여 중지 및 삭제합니다.

$ docker rm -f [컨테이너 ID]

이어서 소스 코드가 위치한 디렉토리에서 다음 명령문을 실행합니다. 명령문에 사용된 플래그는 추가로 설명하도록 하겠습니다.

$ docker run -dp 3000:3000 \
    -w /app -v "$(pwd):/app" \
    node:12-alpine \
    sh -c "yarn install && yarn run dev"

만약, PowerShell 사용자라면 다음 명령문을 사용합니다.

$ docker run -dp 3000:3000 `
    -w /app -v "$(pwd):/app" `
    node:12-alpine `
    sh -c "yarn install && yarn run dev"

또는 애플 Silicon Mac, ARM64 디바이스에서는 다음 명령문을 사용합니다.

$ docker run -dp 3000:3000 \
    -w /app -v "$(pwd):/app" \
    node:12-alpine \
    sh -c "apk add --no-cache python2 g++ make && yarn install && yarn run dev"

명령문에 사용된 플래그는 다음과 같습니다.

  • -dp 3000:3000
    이전 명령문과 동일합니다. 컨테이너를 데몬(백그라운드)로 실행(-d)하며, 호스트 시스템의 3000번 포트와 컨테이너의 3000번 포트를 맵핑(-p)합니다.
  • -w /app
    명령을 실행할 컨테이너의 현재 작업 디렉토리(/app)를 지정합니다.
  • -v "$(pwd):/app"
    호스트 시스템 상의 현재 작업 디렉토리를 컨테이너의 /app 디렉토리에 바인딩(링크)합니다.
  • node:12-alpine
    사용할 이미지를 지정합니다. node:12-alpine은 우리가 작성한 도커파일(Dockerfile)에서 지정한 앱의 기본 이미지입니다.
  • sh -c "yarn install && yarn run dev"
    sh를 사용하여 셸을 실행(alphine은 bash를 지원하지 않습니다)합니다. 셸에서는 모든 종속성 설치를 위해 yarn을 설치(yarn install)하고, 개발 모드로 실행(yarn run dev)합니다. 프로젝트 내 package.json 파일의 내용을 확인해보면, dev 스크립트가 nodemon으로 시작하는 것을 볼 수 있습니다("dev": "nodemon src/index.js").

이제 도커 로그 명령문으로 로그를 출력합니다(Ctrl+C를 눌러 로그 출력을 종료할 수 있습니다).

$ docker logs -f [컨테이너 ID]

docker ps 명령문을 사용하여 node:12-alpine 이미지로 동작하는 컨테이너의 ID를 확인합니다.

$ docker ps
CONTAINER ID   IMAGE                    COMMAND                  CREATED          STATUS          PORTS                    NAMES
2546cd6b1552   node:12-alpine           "docker-entrypoint.s…"   13 minutes ago   Up 13 minutes   0.0.0.0:3000->3000/tcp   bold_liskov
2878508bb6d4   docker/getting-started   "/docker-entrypoint.…"   4 days ago       Up 4 days       0.0.0.0:80->80/tcp       jovial_banach
$ docker logs -f 2546cd6b1552
yarn install v1.22.17
[1/4] Resolving packages...
warning Resolution field "ansi-regex@5.0.1" is incompatible with requested version "ansi-regex@^2.0.0"
warning Resolution field "ansi-regex@5.0.1" is incompatible with requested version "ansi-regex@^3.0.0"
warning sqlite3 > node-gyp > request@2.88.2: request has been deprecated, see https://github.com/request/request/issues/3142
warning sqlite3 > node-gyp > request > har-validator@5.1.5: this library is no longer supported
warning sqlite3 > node-gyp > request > uuid@3.4.0: Please upgrade  to version 7 or higher.  Older versions may use Math.random() in certain circumstances, which is known to be problematic.  See https://v8.dev/blog/math-random for details.
warning sqlite3 > node-gyp > tar@2.2.2: This version of tar is no longer supported, and will not receive security updates. Please upgrade asap.
[2/4] Fetching packages...
[3/4] Linking dependencies...
[4/4] Building fresh packages...
success Saved lockfile.
Done in 64.54s.
yarn run v1.22.17
$ nodemon src/index.js
[nodemon] 2.0.13
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node src/index.js`
Using sqlite database at /etc/todos/todo.db
Listening on port 3000

파일 변경 및 실시간 적용 확인

지금까지 node:12-alpine 이미지로 동작하는 컨테이너를 실행했습니다. 이때 컨테이너가 bind mount 유형으로 마운팅되었으며, 프로젝트 내 작업 디렉토리 /app을 마운트포인트로 지정하였습니다.

이제 앱을 변경해볼까요? /src/static/js/app.js 파일을 열고 "Add Item" 버튼 내용을 "Add"로 간단하게 바꿔보도록 하겠습니다(소스 코드 109번 라인). 소스 코드를 다음과 같이 수정하고, 저장합니다.

{/*{submitting ? 'Adding...' : 'Add'}*/}
{submitting ? 'Adding...' : 'Add'}

우리가 앱을 수정하면, 도커 로그 상에서 노드몬이 앱의 변경을 감지하는 것을 확인 할 수 있습니다. 앱이 변경되면 자동으로 Node.js 애플리케이션을 재시작합니다.

[nodemon] restarting due to changes...
[nodemon] starting `node src/index.js`
Using sqlite database at /etc/todos/todo.db
Listening on port 3000

이제 웹 브라우저에서 앱(http://127.0.0.1:3000/)을 새로고침 해볼까요? 프로젝트 볼륨에 따라서 다르겠지만, 튜토리얼 앱은 수 초 내에 앱이 재시작되어 변경사항이 즉각 반영됩니다
앱의 변경 사항을 테스트하기 위해 이제 번거로운 작업들이 줄어들었습니다(지금까지는 앱이 변경되면 이미지를 새로 빌드하고, 컨테이너를 재시작해야 했습니다). 이제는 모든 수정 작업이 완료되면 최종 변경 사항을 이미지로 빌드하기만 하면 됩니다. 

bind mount는 로컬 개발 환경에서 굉장히 일반적으로 사용됩니다. 개발용 PC에서 모든 빌드 도구와 환경을 설정하고 설치할 필요가 없기 때문입니다. 그럼에도 앱의 변경 사항(소스 코드의 수정) 등을 즉각적으로 적용하고 테스트 할 수 있습니다.