도커 볼륨&바인드 마운트

볼륨
문제
파일 시스템이 컨테이너 내부에 있기 때문에, 컨테이너를 삭제하면 모든 데이터가 날아간다. 같은 이미지로 빌드해도 이전의 '데이터' 자체는 날아가버린다.
이건 컨테이너의 격리 개념 때문에 그런 것이지만, 만약에 컨테이너에서 생성된 데이터를 유지하고 싶은데, 컨테이너를 삭제해야한다면 곤란할 수 있다.
볼륨이란?
도커의 볼륨이라는 개념이 위 문제를 해결하는 데 도움이 된다. 볼륨은 도커 컨테이너 내부에 있는 것이 아니라, 우리의 호스트 머신(로컬)에 존재하는 폴더이다.
볼륨은 도커가 인식하는 호스트 머신의 폴더로, 도커 컨테이너 내부의 폴더에 매핑된다. 이렇게 들으면 COPY 명령과 비슷해보일 수 있지만 볼륨은 상호작용이 가능하다. 컨테이너에서 데이터를 생성해도 이것이 호스트 머신의 폴더에도 반영될 수 있다는 것이다.
그렇기 때문에 컨테이너를 삭제해도 볼륨은 존재하기 때문에 데이터 보존에 볼륨을 사용할 수 있다.
볼륨 사용하기
rename메서드-> 실패
그래서 만약
VOLUME ["/app/feedback"]
해당 부분을 추가해서 볼륨을 설정한 후 이미지를 다시 빌드하고 컨테이너를 실행하면 정상적으로 작동하는가? 그렇지 않다. 이를 실행하고 인풋을 주면

입력이 정상적으로 처리되지 못하고 무한 버퍼링에 빠져버린다.
로그를 조회하면
docker logs feedback-app ─╯
(node:1) UnhandledPromiseRejectionWarning: Error: EXDEV: cross-device link not permitted, rename '/app/temp/lgtm.txt' -> '/app/feedback/lgtm.txt'
(Use `node --trace-warnings ...` to show where the warning was created)
(node:1) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
(node:1) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
cross-device link not permitted라고 나온다. 사실 이건 도커의 문제는 아니고 rename 메서드가 어째선지 작동하지 않아서 생기는 문제이다.
if (exists) {
res.redirect('/exists');
} else {
await fs.copyFile(tempFilePath, finalFilePath);
await fs.unlink(tempFilePath);
res.redirect('/');
rename메서드를 위와 같이 고쳐주고 다시 빌드 후 실행한다.
그러면 무한 버퍼링이 걸리는 문제는 해결했지만 여전히 컨테이너를 삭제하면 같이 데이터가 날아가버리는 문제가 존재한다. 볼륨을 설정했는데도 말이다.
명명된 볼륨과 익명 볼륨
익명 볼륨
이 문제는 사실 놀랍게도 볼륨의 이름과 관련이 있다.
아까 볼륨을 추가할 때 도커파일에 VOLUME ["/app/feedback"]이라고만 했지, 볼륨에 딱히 이름을 지어주지 않았다. 이런 것을 익명 볼륨(Anonymous Volumes)라고 한다. 이름을 만들지 않으면 자동생성되기 때문에
docker volume ls ─╯
DRIVER VOLUME NAME
local ed26b82f96792adffb87c2913ba73f5bf75d9c853a31c35bac643ecd947d79a4
이런 식의 괴상한 이름이 만들어진다.
그런데 이런 식의 익명 볼륨이 어떤 문제가 있냐 하면, 익명 볼륨은 컨테이너가 존재하는 동안에만 실제로 존재한다. 따라서 위의 문제는 컨테이너를 삭제할 때 볼륨도 같이 삭제되면서 데이터가 남지 않게 되는 것이었다.
그럳데 사실 이것은 틀렸습니다
사실 익명 볼륨은 죄가 없다. 사실 진짜 문제는 컨테이너를 생성할 때 --rm옵션을 추가했었는데 익명 볼륨의 경우에는 컨테이너를 삭제하면 볼륨이 이 옵션 때문에 같이 날아가게 된다. 그래서 사실 --rm옵션을 추가하지 않으면 컨테이너를 날려도 볼륨은 유지된다.
명명된 볼륨
그럼 이제 명명된 볼륨(named volume)을 지어볼 차례다. 명명된 볼륨은 도커파일에 적는 것이 아니라
docker run -d -p 3000:80 --rm --name feedback-app -v feedback:/app/feedback feedback-node:volumes2
위와 같이 -v 볼륨이름:경로옵션을 추가하여 컨테이너를 만들어주면 된다. -v옵션에 이름을 주지 않으면 익명 볼륨을 만들 수 있다.
이런 식으로 명명 볼륨을 만들어주면 컨테이너를 삭제했다가 다시 만들어도 -v 볼륨이름:경로 옵션이 같다면 여전히 이전 컨테이너에서 줬던 데이터가 살아있게 된다.
바인드 마운트
바인드 마운트란?
바인드 마운트는 볼륨과 비슷하게 호스트 머신의 폴더를 이용하는 것은 맞지만, 볼륨과 달리 호스트 머신에서 경로를 지정할 수 있다. 볼륨의 경우 이 작업을 도커가 자동적으로 수행하게 된다.
바인드 마운트 없이 수정사항을 반영하려면 이미지를 다시 빌드하고, 컨테이너를 다시 실행해야하는데, 바인드 마운트를 통해 수정사항을 즉각 반영시킬 수 있다.
바인드 마운트도 명명된 볼륨처럼 터미널 명령으로 수행한다.
-v "호스트 머신 절대 경로":/app
과 같이 -v 옵션을 사용해서 추가해주면 되는데 호스트 머신의 절대경로이기 때문에
-v $(pwd):/app
으로 해도 된다.
따라서 아래 명령을 실행해보면
docker run -d -p 3000:80 --name feedback-app -v feedback:/app/feedback -v "/home/max/devops/study_docker/section3:/app" feedback-node:volumes2
docker ps ─╯
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
실행중인 컨테이너에 없다. 로그를 조회해보면 종속성 문제임을 알 수 있는데, 이 옵션을 추가하기 전까지는 정상 작동하였다.
이는 바인드 마운트가 적용되면서 컨테이너 내부의 /app내용이 호스트의 내용으로 덮어씌워졌기 때문에, 호스트의 종속성이 실행하고자 하는 컨테이너와 맞지 않아 실행 직후 종료되는 것이다.
다른 볼륨 결합 &병합하기
위 문제는
docker run -d -p 3000:80 --rm --name feedback-app -v feedback:/app/feedback -v "작업폴더전체경로":/app" -v /app/node_modules feedback-node:volume
와 같이 익명 볼륨을 하나 추가해줌으로써 해결할 수 있다. 이 익명 볼륨이 컨테이너의 /app/node_modules에 마운트되면서 호스트의 /app이 덮어씌워지더라도 /app/node_modules는 호스트와 분리된 환경이 되어, 의존성 문제가 해결되는 것이다.
바인드 마운트가 성공적으로 적용되면 html을 수정한 것이 다시 빌드하지 않아도 컨테이너에 즉각 반영된다.
그러나 이것은 html 수정 같은 정적 코드 수정에 한정되지 JS코드를 수정하는 등의 작업은 위 방법으로 즉각 적용시킬 수 없다.
Node한정 nodemon을 활용하여 동적 코드 변경을 적용시켜야 한다. 이를 이용하면 서버가 자동으로 재시작하기 때문에 JS코드의 변경도 컨테이너에 적용시킬 수 있다.
COPY vs 바인트 마운트
테스트 시에 바인드 마운트를 통해 COPY가 도커파일에 없어도 작동 자체는 할 수 있지만, 바인드 마운트를 이를 대체하기 위한 것은 아니다.
바인드 마운트는 수정사항을 즉각 반영하고 싶을 때 사용하는(개발 도중) 용도이지, COPY와는 용도가 다르다.
바인드 마운트는 호스트 파일에 의존하기 때문에 배포 시에 COPY를 대체할 수 없다.
dockerignore
도커도 git처럼 도커가 관리하지 않기를 원하는 파일을 .dockerignore파일로 제명시킬 수 있다. 예를 들어 .dockerignore에 추가한 파일은 COPY . .명령어에서 이미지로 복사되지 않는다.
환경 변수 관리하기
도커에는 2가지 타입의 변수가 있는데 빌드 타임 인수와 런타임 환경 변수이다.
빌드 타임 환경 변수는 이미지를 빌드하는 동안 사용되는 변수로 도커파일에서 ARG를 사용하여 설정한다.
런타임 환경 변수는 컨테이너 실행(docker run) 시 설정되는 환경 변수로 도커파일 내부에서 docker run옵션에 --env옵션을 붙여 사용할 수 있다. 환경변수가 많은 경우 .env파일에서 관리하고 docker run --env-file 로 옵션을 붙여줄 수 있다.
ARG 빌드 인수
ARG는 도커 이미지 빌드 시에만 사용되는 변수로 주로 빌드 시점에 필요한 설정 값을 전달하기 위해 사용된다.
ENV는 이미지와 컨테이너 모두에서 사용할 수 있지만 ARG는 이미지에만 사용된다.

