데이터 관리 및 볼륨
시작
컨테이너의 데이터
이미지는 읽기 전용입니다. 변경사항이 있다면 다시 빌드해야 할겁니다. 실행 중인 애플리케이션은 실행 중엔 변경하지 않죠. 그렇다면 애플리케이션을 수행하면서 로그 데이터등은 어떻게 확인할까요? 이때 Volume
설정이 필요합니다.
볼륨 설정을 하지 않는다면 컨테이너 종료와 동시에 데이터도 삭제됩니다. 볼륨 설정은 호스트의 공간과 컨테이너로 실행되는 애플리케이션 내부 공간을 동기화 합니다. 그 안에서 데이터를 읽고 쓰기가 가능하죠. 즉, 이미지는 읽기 전용이지만 컨테이너의 경우 읽고 쓰기가 가능합니다.
컨테이너의 변경 사항을 추적 파생 시키고 변경 사항을 결합시킵니다.
컨테이너에서 파일을 추가할 수도 변경할 수도 있게됩니다. 컨테이너가 중지되더라도 공간은 동기화되어 데이터는 공유 공간에 남아있게 됩니다.
우선 간단하게 제목과 설명을 입력받아 특정 폴더에 파일로 저장해주는 API가 있다고 가정하겠습니다. 도커라이징을 시작해봅시다.
FROM node:14
WORKDIR /app
# COPY package.json /app
COPY package.json .
# 종속성 설치
RUN npm install
# 나머지 코드들도 복사
COPY . .
# 호스트에 80으로 노출
EXPOSE 80
# 컨테이너로 실행시 작동
CMD [ "node", "server.js" ]
빌드해봅시다.
docker build -f Dockerfile -t feedback-node . --no-cache
컨테이너를 실행합니다.
# 컨테이너 내부 포트 80을 호스트의 3000 포트로 매핑
docker run -p 3000:80 -d --name feedback-app feedback-node
이제 API를 호출해 텍스트를 불러와 봅시다. 하지만 이 파일을 확인할 방법은 컨테이너 내부로 접속해 볼 수 밖에 없습니다. 잠시 중단해봅시다.
docker stop feedback-app
만일 docker run
당시 --rm
옵션을 사용했다면 중단과 동시에 컨테이너도 제거됐을겁니다. 현재는 남이있으므로 다시 시작하면 이전 데이터는 그대로 컨테이너에 남아있게 됩니다.
하지만 컨테이너를 제거하면 데이터는 삭제됩니다.
볼륨
볼륨은 호스트 머신의 폴더입니다. 도커 컨테이너 내부의 폴더와 매핑하여 사용하게 됩니다. 두 폴더의 변경사항은 동기화되며 컨테이너의 변경사항을 외부에서도 확인할 수 있죠.
볼륨을 추가하는 가장 쉬운 방법은 Dockerfile
에 VOLUME
레이어를 추가하는겁니다.
FROM node:14
WORKDIR /app
COPY package.json .
RUN npm install
COPY . .
EXPOSE 80
# 컨테이너 내부 /app/feedback
VOLUME [ "/app/feedback" ]
CMD [ "node", "server.js" ]
빌드합니다.
docker build -t feedback-node:volumes .
Anonymous Volumes방식
이번에는 --rm
옵션을 이용해 컨테이너 중지시 볼륨 공간이 남아있는지 확인해봅시다.
docker run -d -p 3000:80 --rm --name feedback-app feedback-node:volume
아직은 정상적으로 작동하지 않습니다. 이번에는 중지 이후 다시 시작해도 파일이 존재하지 않을 겁니다. 왜냐하면 이 방식은 Anonymous Volumes
방식입니다. 이미지에 익명의 볼륨을 추가하고 컨테이너에 이 볼륨에 데이터를 저장하기 때문이죠.
이와 다른 Named Volumes
있습니다. 두 경우 모두 도커는 일부 폴더와 경로를 호스트 머신에 설정합니다. 단, Anonymous Volumes의 경우 경로를 알지 못합니다.
볼륨 공간은 다음 명령을 통해 조회할 수 있습니다.
docker volume ls
DRIVER VOLUME NAME
local 9e5ae86e585d164d352e1f19ed93054709edc9fdas952ce969f6256ad7e2aec8195efc
볼륨 네임이 무작위로 정해집니다. 이후 컨테이너를 중지하고 다시 명령을 수행하면 볼륨이 조회되지 않을겁니다. 컨테이너는 --rm
옵션을 사용했기 때문에 삭제됩니다. 그런데 볼륨도 삭제됩니다. 성납되지 않는 조건이죠. 이는 Anonymous Volumes
가 호스트가 아닌 도커에서 관리되기 때문입니다.
컨테이너에 정의된 경로는 생성된 어떤 볼륨에 매핑되고 호스트 머신 상에 생성된 경로로 연결됩니다. 컨테이너의 /app/feedback
공간은 로컬 컴퓨터의 특정 공간과 매핑됩니다. 하지만 그 공간은 도커에서 관리하므로 정확한 경로는 알 수 없죠. 또한 찾더라도 접근이 불가능합니다.
Named Volumes방식
명명된 볼륨 방식을 사용하면 위 문제를 해결할 수 있습니다. 로컬의 특정 경로를 명명하여 영구적으로 데이터를 저장할 수 있게됩니다. 관리 주체 또한 도커가 아닌 주인에게 주어집니다.
다시 도커라이징합니다. 익명 볼륨을 제거합니다.
FROM node:14
WORKDIR /app
COPY package.json .
RUN npm install
COPY . .
EXPOSE 80
CMD [ "node", "server.js" ]
이제 다시 빌드하고 실행해줍니다. 기존에 있던 컨테이너는 중지 및 제거하고 이미지도 제거해줍니다.
이제 run
에 옵션을 추가해 명명된 볼륨 방식을 적용해봅시다.
# 로컬의 feedback과 컨테이너의 /app/feedback 경로를 동기화한다.
docker run -d -p 3000:80 --rm --name feedback-app -v feedback:/app/feedback feedback-node:volumes
이제 API를 호출해 파일을 생서하고 다시 컨테이너를 중지해봅시다. 이후 볼륨을 조회해봅시다.
docker volume ls
DRIVER VOLUME NAME
local feedback
이번에는 컨테이너를 종료했고 제거했음에도 볼륨이 남아있습니다. 이후 컨테이너를 실행해도 볼륨내 데이터는 남아있게 됩니다.
참고로 익명 볼륨의 경우 run
당시 --rm
옵션을 부여하지 않았다면 이후 docker rm
명령으로 컨테이너를 제거해도 볼륨을 남아있게 됩니다. 이후 컨테이너를 시작하면 익명의 볼륨이 추가로 더 생성되기 때문에 공간을 차지하게 됩니다. docker volume rm 볼륨네임
혹은 docker volume prune
명령으로 익명 볼륨을 제거해줘야 합니다.
바인드 마운트
이제까지 이미지를 빌드하게 되면 당시 스냅샷으로만 빌드되기 때문에 코드의 변경은 이루어질 수 없다고 전달했습니다. 하지만 개발 과정에서 이 과정은 수행하기 매우 힘들겁니다. 코드 변경사항이 일어날 때마다 이미지를 다시 빌드하고 컨테이너로 올려야하는 번거로운 작업이 있기 때문이죠.
이때 도움을 주는 기능이 바인드 마운트
입니다. 볼륨과 비슷한 점이 있습니다. 볼륨의 경우 호스트에 데이터를 저장하긴 하지만 그 경로는 알지 못합니다. 하지만 바인드 마운트의 경우 해당 경로를 알 수 있습니다. 즉 해당 경로에 가서 컨테이너에 저장된 데이터를 읽을 수 있는거죠.
또한 쓰기도 가능하기 때문에 컨테이너에서 이를 읽어 빌드 과정없이도 반영할 수 있게됩니다. 다만 이는 개발에 특화된것이지 운영에 있어서는 그다지 좋은 방법은 아닙니다.
요약해서 바인드 마운트
는 영구적이고 편집이 가능하죠 이미지가 아닌 컨테이너에만 영향이 갑니다.
명령을 수행하기전 호스트의 지정 폴더 경로에 도커가 액세스할 수 있는지 확인해야 합니다. 도커 기본 설정-리소스-파일 공유
에 호스트의 지정 폴더 혹은 상위 폴더가 설정되어 있는지 확인이 필요합니다.
이제 다시 컨테이너를 실행합니다.
# /Users/wooglim/dev/feedback-node:/app 호스트 경로는 절대경로여야 함.
docker run -d -p 3000:80 --rm --name feedback-app -v feedback:/app/feedback -v "/Users/wooglim/dev/feedback-node:/app" feedback-node:volumes
단 위 명령은 실행 직후 에러가 발생할 겁니다. 바인드 마운트
의 경우 로컬 경로의 폴더를 컨테이너 지정 경로에 덮어씌우게 됩니다. 즉, node_module
등의 폴더가 없다면 그대로 덮어씌어 없어지겠죠. 이는 곧 이미지 빌드시 도커라이징 과정이 무의미 해졌다는것을 의미합니다.
이미지 기반으로 컨테이너를 생성했지만 이후 로컬의 경로로 모두 덮어씌어지게 되는 문제가 발생한거죠.
볼륨 이해하기
-v 옵션을 이용해 볼륨, 바인드 마운트가 가능합니다. 컨테이너 내부 어떤 폴더가 호스트의 특정 폴더와 마운트 혹은 연결됩니다. 컨테이너 내부에 이미 파일이 있는 경우, 외부 볼륨에 데이터가 있는 경우(볼륨) 컨테이너 내부에 데이터가 존재하지 않는 경우 볼륨에서 데이터를 찾습니다.
아직 내부에 파일이 없어 파일을 로드하게되는데, 이를 위해 바인드 마운트를 사용합니다.
도커는 호스트의 로컬 파일은 덮어쓰지 않습니다. 대신 호스트 지정 폴더의 데이터를 컨테이너에 덮어씌웁니다.
이전으로 문제로 돌아가보면 로컬의 빈 폴더로 덮어씌어지기 때문에 node 애플리케이션을 실행하는데 필요한 node_module
도 사라지게 됩니다. 이제 run
옵션에 익명 볼륨을 할당해 봅시다. 이전과 같은 문제가 발생해도 익명 볼륨에 해당 데이터가 존재하므로 똑같은 에러는 발생하지 않을겁니다.
docker run -d -p 3000:80 --rm --name feedback-app -v feedback:/app/feedback -v "/Users/wooglim/dev/dockerPractice/1/feedback-node:/app" -v /app/node_modules feedback-node:volumes
익명 볼륨의 데이터는 컨테이너로 실행되기 전 지정 폴더의 데이터를 익명 볼륨으로 로컬에 저장하게 됩니다.
컨테이너를 중지 및 제거 하고 다시 실행하면 앱은 정상적으로 실행될것입니다.
이제 추가된 바인드 마운트로 인해 코드 변경 사항이 즉시 컨테이너에도 반영됩니다. 만일 서버 파일을 실시간으로 감지하는 라이브러리(nodemon)등을 이용하지 않는다면 컨테이너를 중지하고 재실행 해줘야 합니다.
윈도우의 경우 파일시스템 환경이 다르기 때문에 잘 적용이 되지 않을수도 있습니다.
추가로 바인드 마운트로 지정된 로컬 폴더는 도커로 제어할 수 없습니다. 삭제할 경우 직접 삭제해야만 합니다.
익명 볼륨의 경우 외부 경로보다 컨테이너 내부 경로의 우선 순위를 두어 데이터를 저장하고자 할때 유용하게 사용할 수 있습니다.
읽기 전용 볼륨
바인드 마운트의 경우 변경사항이 컨테이너 내부에 반영됩니다. 호스트에서 변경한 파일은 컨테이너에서도 확인할 수 있는거죠. 반대는 어떨까요? 컨테이너에서 파일을 수정하는 경우 호스트의 데이터도 변경됩니다. 이 경우 읽기 전용으로만 하여 컨테이너를 수정을 시도해도 호스트의 데이터는 변경되지 않도록 하는 옵션이 있습니다. read only
를 의미하는 ro
를 바인드 마운트 끝에 붙여주면 됩니다.
docker run ... -v "/Users/wooglim/dev/dockerPractice/1/feedback-node:/app:ro" -v /app/node_modules feedback-node:volumes
이때 주의할 것은 해당 -v
옵션에 설정한 read-only
옵션이 다른 바인드 마운트 설정에도 영향이 있는지 확인하는 겁니다. 컨테이너에서 /app
내부 파일에 쓰기를 시도하면 이를 방지합니다. 다른 바인드 마운트 옵션에서는 일부 쓰기를 진행하고자 하는데, /app
하위 폴더라면 쓰기 권한이 방어가 되는 문제가 발생합니다.
볼륨 관리하기
docker volume --help
Usage: docker volume COMMAND
Manage volumes
Commands:
create Create a volume
inspect Display detailed information on one or more volumes
ls List volumes
prune Remove all unused local volumes
rm Remove one or more volumes
Run 'docker volume COMMAND --help' for more information on a command.
ls 명령을 사용해봅시다. 익명 볼륨, 네이밍 볼륨을 확인할 수 있습니다. 바인드 마운트는 도커에서 관리하지 않으므로 조회되지 않습니다.
docker volume ls
local ac5a364073299d7cf559758f30b92cfa3defc46d2a32a593a96fc52f096cad52
local e18515efd770cc2ee0ec196f1df2d888b4facd69bc62dae162e769e0c5cfb97b
local feedback
create 옵션은 네이밍 볼륨을 생성할 수 있는 명령입니다.
docker volume create --help
Usage: docker volume create [OPTIONS] [VOLUME]
Create a volume
Options:
-d, --driver string Specify volume driver name (default "local")
--label list Set metadata for a volume
-o, --opt map Set driver specific options (default map[])
inspect는 볼륨에 대한 정보를 확인할 수 있는 명령입니다. 생성일, 드라이버, 라벨, 마운트 포인트, 이름, 옵션, 스코프 등을 확인할 수 있죠.
docker inspect --help
Usage: docker inspect [OPTIONS] NAME|ID [NAME|ID...]
Return low-level information on Docker objects
Options:
-f, --format string Format the output using the given Go template
-s, --size Display total file sizes if the type is container
--type string Return JSON for specified type
rm 옵션을 볼륨을 삭제하는 명령입니다. 제거 당시 컨테이너와 볼륨이 연결되어 있다면 컨테이너는 중지 및 제거해야 합니다.
prune 옵션은 사용하지 않는 볼륨을 모두 제거하는 명령입니다. (컨테이너에서 사용하지 않는)
COPY와 dockerignore
.dockerignore
에 특정 파일을 추가하면 COPY 명령으로 복사해서는 안 되는 요소를 제외할 수 있습니다
예로, 호스트에 설치된 node_modules
를 제외하고 싶다면 아래와 같이 추가하면 됩니다.
node_modules
환경변수 설정
Dockerfile
은 매개변수 즉 인자($환경변수
)를 받을 수 있습니다. 또한 Dockerfile
파일 내부에 ENV
명령 레이어를 이용해 환경변수를 설정할 수 있습니다. 이로 인해 보다 유연한 이미지를 생성할 수 있게됩니다.
# Dockerfile
# ENV 환경변수명 값 -> 이 변수를 전역 환경변수로 적용됨
ENV PORT 80
EXPOSE $PORT
또는 run
명령에 환경변수 옵션을 추가해 적용할 수 있습니다.
# docker run -env PORT=80 ... 동일하게 작동
docker run --env PORT=80 ...
또 다른 방법으로 환경변수 파일을 별도로 생성한 뒤 run
시 적용해주는 옵션도 있습니다. --env-file
옵션입니다.
# --env-file 환경변수 경로
docker run --env-file ./.env
매개변수로 유용하게 사용하기
# Dockerfile
ARG DEFAULT_PORT = 80
ENV PORT ${DEFAULT_PORT}
EXPOSE $PORT
이미지 빌드시 ARG
로 설정한 인자의 값을 변경할 수 있습니다. 변경하지 않는다면 기본 초기화값으로 적용됩니다. --build-arg
옵션을 이용합니다.
docker build -t feedback-node --build-arg DEFAULT_PORT=8000 .