docker run -dp 127.0.0.1:3000:3000 jjrk/docker_tutorial
이를 사용해서 docker hub에 있는 이미지를 받아와서 앱을 실행할 수 있었다.
그런데 docker stop으로 앱을 종료하고 다시 시작하면 db가 초기화된다. 이는 컨테이너가 여러 레이어를 가지고 있어 빌드할 때마다 독립적인 파일시스템 scratch space를 가지기 때문이다.
결국 앱을 껏다키면 데이터가 없어진다. 한 마디로 이전에 데이터 저장 상태를 기억하는 수단이 없는 것이다!
그래서 나온 아이디어는 그 데이터의 마지막 상태를 호스트 파일시스템에 기록해두고, 컨테이너를 실행할 때 마다 호스트에 데이터가 저장된 부분을 참조하여 데이터를 받아오는 것이다.
컨테이너 자체에 데이터가 저장되는 것은 아니지만 앱을 다시 시작할 때 데이터가 남아있으니 이전의 데이터를 계속 불러오는 persistence가 가능하다.
docker는 이미지의 여러 scratch space 중 특정 주소를 호스트에 연결하는 volume 기능을 두 가지로 제공한다. 하나는 Volume mount이고 하나는 Volumbe bind이다.
volume 실습에 들어가기전에 먼저 기본적으로 현재의 앱이 어떻게 데이터를 저장하는지 살펴보자
현재 todo 앱의 sqlite는 컨테이너의 /etc/todos/todo.db 라는 파일에 데이터를 저장하도록 만들어놨다.
그니까 컨테이너가 실행하면서 유저가 만든 정보는 컨테이너 내의 파일시스템의 /etc/todos/todo.db 이 부분에 남겨지는 것이다
만약 컨테이너화하지 않고 로컬 호스트에서 실행한다면 로컬 호스트 파일에 저장되는 것이다.
실제로 경로를 찾아서 들어가보았고 db 파일을 열어보았다. SQLite fomat으로 되어있는데 특정 확장자가 필요한 것인지 데이터는 깨져보였다. 특정 방식의 디코딩이 필요해보인다.
이러한 점들을 생각하고 실습에 들어가보도록 하자!
먼저 로컬에 볼륨을 생성한다. 도커앱이 자동으로 로컬에 저장 공간을 하나 생성한다.
docker volume create todo-db
이전에 빌드된 이미지를 도커에서 실행할 때
docker run -dp IP:host_port:container_port
형식으로 했엇다. 여기에 volume mount 옵션을 추가하기만 하면 된다.
docker run -dp 127.0.0.1:3000:3000 --mount type=volume,src=todo-db,target=/etc/todos getting-started
이렇게 하면 앱을 껏다켜도 투두 리스트 데이터는 그대로 남아 있다.
왜냐면 컨테이너의 /etc/todos 디렉토리의 todo.db 파일에 데이터가 저장되는데, 컨테이너가 종료되는 시점에 그 상태를 캡처해서 로컬 호스트에 저장해놓기 때문이다.
다시 앱을 껏다 켤 때는 마운트 경로를 명시해야한다.
이런 방식으로 테스트를 하면 데이터가 저장되고 불러와지는 것을 확인할 수 있다. docker stop후, 마운트 방식을 명시해서 다시 앱을 돌려보자!
근데 데이터는 과연 최종적으로 어디 저장되는 걸까??
→ 실제로 disk에 저장되는 포인트는 MountPoint라는 주소값이다.
docker volume inspect todo-db
[
{
"CreatedAt": "2019-09-26T02:18:36Z",
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/todo-db/_data",
"Name": "todo-db",
"Options": {},
"Scope": "local"
}
]
volume mount가 로컬에 컨테이너의 데이터 상태를 저장하는 volume을 만드는 거였다면 bind mounts는 그 반대이다. 컨테이너가 로컬의 변화를 관찰하여 바뀐 상태가 저장되는 즉시 변화를 반영한다. 따라서 개발 단계에서 소스코드를 변경할 때 아주 좋은 방법이다!
아래 명령어로 우분투 컨테이너를 다운받아 실행시키고, 현재 디렉토리에서 src 공간을 마운트한다
docker run -it --mount type=bind,src="$(pwd)",target=/src ubuntu bash
실행이 끝나면 우분투에 ssh로 접속한것처럼 된다. 그리고 src 디렉토리로 넘어가서 ls로 파일을 확인하면! 놀랍게도 로컬의 파일들이 모두 공유되고 있음을 볼 수 있다.
root@ac1237fad8db:/# cd src
root@ac1237fad8db:/src# ls
Dockerfile node_modules package.json spec src yarn.lock
위 명령어에서 src를 현재 경로 - $pwd로 설정했기 때문에 getting-started-app에 있는 파일들이 모두 복사되었다.
이 상태에서는 파일의 생성 및 삭제 상태도 공유된다. 아래를 따라 우분투에서 파일을 만들면
root@ac1237fad8db:/src# touch myfile.txt
root@ac1237fad8db:/src# ls
Dockerfile myfile.txt node_modules package.json spec src yarn.lock
상당히 놀랍다… 물론 삭제후 우분투에서 확인해도 없어진걸 확인할 수 있다
bind mounts는 로컬 개발 환경 셋업에 많이 사용된다. development container를 사용하면 개발 패키지 등에 영향을 받지 않기 때문에 로컬 환경에서 개발하기에 용이하기 때문이다.
A라는 사람에게 파일을 넘겨받아 개발한다고 해보자.. 그 사람은 개발할 때 a,b,c 라이브러리를 설치했다. 그래서 B가 받아서 자신의 로컬에서 개발하려니 a,b,c를 다운해야하는 것!! 기기마다, os마다 천차만별이라 개발환경 세팅에도 많은 버그가 발생한다.
따라서 이렇게 개발 컨테이너를 잡아두면 환경에 종속되지않고 개발할 수 있다는 장점이 있다!
일단 진행되던 앱의 컨테이너를 종료하고 다음을 입력한다.
docker run -dp 127.0.0.1:3000:3000 \
-w /app --mount type=bind,src="$(pwd)",target=/app \
node:18-alpine \
sh -c "yarn install && yarn run dev"
-mount type=bind,src="$(pwd)",target=/app
node:18-alpine
- 컨테이너 실행에 사용할 베이스 이미지. 운영 체제 및 실행 환경 등이 담겨있음sh -c "yarn install && yarn run dev"
- the command. sh 명령어로 쉘을 시작하고 yarn install로 필요한 패키지 설치 그리고 yarn run dev로 개발 서버를 실행한다이렇게 하고 로컬 3000으로 들어가면 앱이 구동되는 것을 볼 수 있다.
그리고 로컬에서 실행되던 앱 파일에 들어가서 프론트 코드를 하나 바꿔보자
바로 바뀐 코드가 반영되는것을 볼 수 있다.
바뀐 코드가 반영된 것을 테스트하기 위해 이미지를 빌드하고 다시 실행하는 번거로운 작업이 필요없게 된 것이다!!
마치 iOS에서 Preview를 통해 실시간으로 UI 변경을 볼 수 있었던 것 처럼 편리하긴하다. 특히 프론트 단에서 UI를 짤 때 효율적으로 사용할 수 있을 것 같다.
Volume mount
voluem 마운트는 컨테이너 볼륨을 호스트에 연동한다. 위 예시에서는 todo 정보가 컨테이너에 저장되었는데 로컬의 volume에 저장되고 있는것도 확인할 수 있다. 특이한것은, 호스트에서 볼륨을 만들때 경로를 지정할 필요가 없다. 자기가 알아서 docker에 만들어진다.
Bind mount
bind mount는 호스트 디렉토리를 컨테이너에 연동한다. 일반적으로 현재 코드파일이 있는 src 위치를 연동한다. 그러면 소스코드를 바꾸면 컨테이너 소스코드도 바로 바꿔지기 때문에 개발환경 세팅에 좋다.