Docker: Volume
[Docker 기본]: Volume을 활용한 Data 관리
Volume을 활용한 Data 관리
- Container의 Writable Layer에 Data를 저장했을 때 몇 가지 문제점 존재
- Container가 삭제되면 Data도 같이 삭제됨
- 또한, 다른 프로세스에서 Container에 저장된 Data를 사용하기 어려움
- Container의 Writable Layer에는 Container가 실행 중인 Host Machine과 밀접하게 연결됨
- 따라서 Data를 다른 곳으로 쉽게 옮길 수 없음
- Container의 Writable Layer에 Data를 저장하기 위해서는 File System을 관리하는 Storage Driver가 필요
- Storage Driver는 Linux 커널을 사용하여 공용 File System을 제공
- 이 기능은 Host File System에 직접 쓰는 data volume보다 성능이 떨어짐
- Docker는 Data를 안전하게 존속시킬 수 있는 방식으로 volume, bind mounts, tmpfs mount의 3가지 방식을 제공
- 어떤 것을 사용해야할 지 모를 때는 volume를 사용 권장
올바른 Mount 유형 선택 방법
- 어떤 유형의 Mount를 사용하든, Data는 Container 내에서 동일하게 보이며, Container File System의 폴더나 개별적인 파일들로 표시됨
- 올바른 Mount 유형을 선택할 때 기준이 될 volume, bind mounts, tmpfs mount간의 가장 큰 차이점은, Data가 Docker Host내에서 어디에 존재하는지의 여부
Data 저장을 위한 최선의 선택
- volume는 Docker(Linux에서는
/var/lib/docker/volume/
)가 관리하는 Host File System의 일부에 Data가 저장됨
- Non-Docker 프로세스들이 File System의 해당 부분을 수정해서는 안됨
- Docker에서 Data를 존속시킬 수 있는 Best한 방법
Host의 File System 원하는 곳에 저장
- bind mount는 Data가 Host System의 어디에든지 저장될 수 있음
- 저장되는 Data는 System File이거나 Directory일 수 있음
- Docker Host 또는 Docker Container의 Non-Docker 프로세서들이 언제든지 저장된 Data를 수정할 수 있음
Host System의 Memory에 저장
- tmpfs mount는 Host System의 Memory에만 Data가 저장되며, 절대로 Host의 File System에는 저장되지 않음
Mount 유형
volume
- Docker가 생성하고 관리하는 방식
docker volume create
명령을 사용하여 명시적으로 volumes를 생성하거나, Container나 Service 생성 중에 volume을 생성할 수 있음
- volume이 생성되면, Data는 Docker Host의 디렉토리에 저장됨
- 해당 volume을 Container에 Mount하면, Host의 디렉토리가 Mount가 됨
- 이는 volume이 Docker에 의해 관리되고 Host System과 분리된다는 점을 제외한다면, bind mount와 유사하게 동작함
- 동시에 volume을 여러 Container에 Mount할 수 있음
- 실행 중인 Container가 volume을 사용하지 않아도, 해당 volume은 Docker에서 계속 사용할 수 있으며, 자동으로 삭제되지 않음
docker volume prune
을 사용하여 사용하지 않는 volume을 정리할 수 있음
- volume을 Mount할 때, 이름을 명시적으로 지정하여 사용할 수 있으며, 익명으로도 사용할 수 있음
- 익명 volume은 처음 Mount될 때 명시적으로 이름이 부여되지 않기 때문에, Docker는 주어진 Docker Host내에서 고유한 임의의 이름을 해당 volume에 부여
- 이름이 지정된 방식과는 상관 없이 volume은 동일한 방식으로 작동
- 또한, volume은 원격 Host와 Cloud Provider가 Data를 저장할 수 있는 volume drivers 사용을 지원하고 있음
bind mount
- bind mount는 Docker 초기부터 사용할 수 있었던 방식으로 volume에 비해 기능이 제한적
- bind mount를 사용하면, Host System의 파일 또는 디렉토리가 Container에 Mount됨
- 파일 또는 디렉토리는 Host System의 전체 경로로 참조되지만, 미리 Docker Host에 존재할 필요는 없음
- 없을 경우, 참조된 경로로 파일 또는 디렉토리가 생성됨
- bind mount는 매우 효과적이지만, Host Machine의 File System 디렉토리 구조에 의존적
- Docker CLI 명령어로 bind mount를 관리할 수 없음
tmpfx mount
- tmpfs mount는 Docker Host 또는 Container내의 디스크에서 Data가 유지되지 않음
- 비영구적인 상태 정보나 민감 정보들 같이 Container의 생명주기와 맞춰서 Data를 보존하고자 할 때 사용할 수 있음
- 예를 들어, Docker Cluster인 Swarm Service는 내부적으로 tmps mount를 사용하여 Secret 정보를 Service의 Container에 Mount하여 사용
- bind mount 및 volume는
-v
또는 --volume
Flag를 사용하여 Container에 모두 Mount할 수 있지만, 각 구문은 약간 차이가 있음
- tmpfs mount의 경우,
--tmpfs
Flag를 사용합
- 그러나 Docker 17.06 이상에서는 bind mount, volume, tmpfs mount에 대해 Container와 Service 모두에
--mount
Flag를 사용하는 것이 구문이 더 명확이기 때문에 권장
Mount 유형별 사용 사례
volume 사용 사례
- volume은 Docker Container 및 Service에서 Data를 유지하는 기본적인 방법으로, 다음과 같은 경우에 volume을 사용
- 실행 중인 여러 Container 간에 Data 공유가 필요한 경우
- volume생성에 대해서 명시하지 않은 경우, Container에 처음 Mount될 때 생성됨
- Container가 중지되거나 제거되더라도 생성된 volume은 그대로 유지됨
- 여러 Container가 동일한 volume을 읽기/쓰기 또는 읽기 전용으로 동시에 Mount할 수 있음
- volume은 명시적으로 선언하여 제거
- Docker Host가 특정 디렉토리나 파일 구조를 가질 수 없는 경우
- volume은 Container Runtime으로부터 Docker Host의 설정을 분리하는데 도움이 됨
- 로컬 외의 원격 Host 및 Cloud Provider에 Container의 Data를 저장하려는 경우
- 한 Docker Host에서 다른 Docker Host로 Data를 백업이나 복구 또는 마이그레이션을 하는 경우 volume을 사용하면 더 좋음
- volume을 사용하여 Container를 중지한 다음 volume의 디렉토리(예시:
/var/lib/docker/volume/<volume-name>
)를 백업할 수 있음
bind mount 사용 사례
- 일반적인 경우, 가능하면 bind mount보다 volume를 사용을 권장
- 다음과 같은 경우에 bind mount를 사용
- Host Machine에서 Container로 설정 파일을 공유해야하는 경우
- Docker는 Host Machine의
/etc/resolv.conf
를 각 Container에 bind mount하여 DNS Resolution을 제공하고 있음
- Docker Host 개발 환경과 Container 간 소스코드 또는 빌드된 아티팩트를 공유
- 예를 들어, Maven의 target/ 디렉토리를 Container에 Mount하고, Docker Host에서 Maven Project를 빌드할 때마다, Container에서 재 작성된 작성된 JAR/WAR에 접근할 수 있음
- 만약 이런 방식으로 개발을 진행한다면, Production Dockerfile은 bind mount에 의존하지 않고도, Production-Ready 아티팩트를 Image에 직접 복사할 수 있음
- Docker Host의 파일 또는 디렉토리 구조가 Container가 요구하는 bind mount와 일치하는 경우
volume 또는 bind mounts 사용 시, 유의사항
- volume 또는 bind mount를 사용하는 경우, 다음의 사항에 대해 유의
- File 또는 Directory 있는 Container 내의 Directory에 비어있는 volume를 Mount하면, 해당 File 또는 Directory가 volume으로 전달(복사)됨
- 마찬가지로, 아직 존재하지 않는 특정 volume을 지정하고 Container를 시작하면, 비어있는 volume이 생성됨
- 이는 다른 Container에 필요한 Data를 미리 갖출 수 있는 좋은 방법
- File 또는 Directory가 있는 Container 내의 Directory에 bind mount 또는 비어있지 않은 volume를 Mount한다면, Linux Host에서 File을
/mnt
에 저장하고 나서 USB 드라이브가 /mnt
에 Mount되는 것처럼 기존에 존재하던 File 또는 Directory가 Mount에 의해 가려지게 됨
/mnt
의 컨텐츠들은 USB 드라이브가 Unmount될 때까지, USB 드라이브의 컨텐츠들에 의해서 가려지게 됨
- 가려진 파일들은 제거되거나 대체된 것은 아니지만, bind mount 또는 volume이 Mount되어 있는 동안은 접근할 수 없음
tmpfs mount 사용 사례
- tmpfs mount는 Host System이나 Container 내에서 Data를 유지하지 않아야할 때 가장 적합
- 이는 보안상의 이유가 될 수도 있고, Application이 많은 양의 비영구적인 상태의 Data를 작성해야 할 때, Container의 성능을 보호하기 위한 것일 수도 있음
Tip! 추가 내용
volume container란?
- 일반적으로 도커는 컨테이너 내부에 데이터를 관리하므로, 컨테이너가 파기되면 데이터가 모두 날라가게 됨
- 이는 MySQL 같은 데이터 스토리지를 사용할 경우 위험하게 되는데, 이를 방지하기 위해 따로 볼륨을 설정해서 데이터를 저장해줘야 함
- 호스트OS 디렉토리를 마운트시켜서 데이터를 관리할 수도 있지만, 호스트쪽 디렉토리에 의존이 생기고 만약 이 디렉토리의 데이터를 잘못 손대면 애플리케이션에 부정적 영향을 미칠 수 있기 때문에 이 방식은 사용하지 않는 것이 좋음
- 이에 대한 대안으로 추천되는것이 볼륨 컨테이너
- 볼륨 컨테이너는 말 그대로 데이터를 저장하는 것이 목적인 컨테이너
Dockerfile
작성 시 아래와 같이 볼륨을 설정 가능
FROM mysql
VOLUME /var/lib/mysql
- 위처럼 작성하게 되면 컨테이너 내의
/var/lib/mysql
디렉터리가 호스트 PC의 /var/lib/docker/volumes/${volume_name}/_data
에 마운트 되며 이때 볼륨 이름은 임의의 해쉬값으로 생성
- 참고로 mac이나 windows의 경우
/var/lib/docker/volumes
디렉토리가 없는데,
이는 mac이나 windows의 경우 도커를 바로 실행할 수 없으므로 VM을 하나 띄운 뒤, docker를 실행하기 때문
- 즉,
/var/lib/docker/volumes
디렉토리는 mac과 도커 사이에 띄워진 VM 내에 감춰져있음
- volume container는 볼륨의 이러한 특징을 사용한 것
- 컨테이너 자체를 볼륨을 관리하도록 만들어서 캡슐화하고, 이를 다른 컨테이너의 볼륨에 매핑해서 결합을 느슨하게 함
FROM busybox
VOLUME /var/lib/mysql
VOLUME /var/log
- 위와 같은 볼륨 컨테이너를 작성하면
/var/lib/mysql
, /var/log
디렉토리에 대한 볼륨 2개가 생성되어 각각 /var/lib/docker/volumes/${volume_name}/_data
에 마운트 됨
- 볼륨 컨테이너를 띄우면 바로 종료되는데 이렇게 종료된 컨테이너를 사용해도 상관없음
- 빌드하고 컨테이너로 띄운 뒤
--volumes-from
옵션으로 다른 컨테이너에 연결하면 아래와 같은 형태가 됨
$ docker image build -t volume_container:latest .
$ docker container run -d volume_container:latest
$ docker container run --volumes-from volume_container mysql:5.7
mysql container -> volume_container -> /var/lib/docker/volumes/${/var/lib/mysql's volume_name}/_data
-> /var/lib/docker/volumes/${/var/log's volume_name}/_data
docker-compose.yml 에 volume container 추가
- 위의 방식처럼 컨테이너를 직접 만들고 다른 컨테이너 실행 시
--volumes-from
속성으로 연결해주는 방법도 있지만, docker-compose를 사용 시 좀 더 간단한 방법을 제공
version: "3"
services:
test_database:
image: mysql:5.7
environment:
MYSQL_DATABASE: test_db
MYSQL_ROOT_PASSWORD: root
MYSQL_ROOT_HOST: '%'
ports:
- 3306:3306
volumes:
- test_volume:/var/lib/mysql
test_application:
build: .
expose:
- 8080
depends_on:
- test_database
volumes:
test_volume:
- 보다시피
test_volume
이라는 볼륨을 생성하고, 사용하는 쪽에서 ${volume_name}:${mount를 원하는 디렉토리}
의 형태로 지정해주면 딤
- docker-compose 설정 파일 v2의 형태로 보면 좀 더 직관적으로 이해 쉬움
version: "2"
services:
test_database:
image: mysql:5.7
environment:
MYSQL_DATABASE: test_db
MYSQL_ROOT_PASSWORD: root
MYSQL_ROOT_HOST: '%'
ports:
- 3306:3306
volumes:
- test_volume
test_application:
build: .
expose:
- 8080
depends_on:
- test_database
test_volume:
image: busybox
volumes:
- /var/lib/mysql
- /var/log
- v3의 경우 컨테이너를 따로 생성하지 않아도 된다는 장점이 있음
- 볼륨 컨테이너는 충분히 좋은 기능이지만, 그래도 범위가 같은 도커 호스트 안이라는 사실은 변하지 않음
Tip! 추가 내용
서로 다른 container가 같은 volume을 공유하는 방법
- Docker compose를 사용하고 있다면 이미 여러 컨테이너를 사용하고 있다는 뜻
- 이 여러 컨테이너들은 DB일 수도 있고 웹 애플리케이션일 수도 있음
- 만약 서로 다른 컨테이너들이 하나의 volume을 공유해야한다면 어떻게 해야할까?
- docker-compose.yml 설정 파일에서 volumes란 설정을 사용 가능
- 예시: app이라는 컨테이너와 db라는 컨테이너가 있다고 가정했을 때
services:
app:
build:
context: .
volumes:
- .:/usr/src/app
ports:
- “80:80”
db:
build:
context: ./db
volumes:
- .:/var/lib/mysql
ports:
- “6630:3306”
- 위의 예시를 보시면 app과 db 컨테이너 둘 다 volumes 라는 항목이 명시되어 있음
- 하지만 현재는 같은 volume을 사용하는 것이 아니면 별도의 volume으로 되어있어서 서로 다른 파일 시스템으로 이루어진 상태
- 이 두 컨테이너가 같은 volume을 사용해야 한다면 먼저 설정 파일 맨 아래에 공유할 volume의 이름을 추가
volumes:
shared-data:
- shared-data는 임의로 지정한 이름이며 원하는 이름으로 변경 가능
- 공유할 volume을 추가하고 app과 db 컨테이너에서 shared-data 볼륨을 사용하도록 지정해주면 됨
services:
app:
build:
context: .
volumes:
- shared-data:/usr/src/app
ports:
- “80:80”
db:
build:
context: ./db
volumes:
- shared-data:/usr/src/mysql
ports:
- “6630:3306”
volumes:
shared-data:
- services 부분을 보시면 app, db의 volumes 설정에 shared-data를 사용한다고 추가한 것을 확인할 수 있음
- 그리고 db 컨테이너의 volumes 경로가 이전엔
/var/lib/mysql
이였는데 /usr/src/mysql
로 변경
- volumes을 공유해서 사용할시 주의할 점은 공유할 볼륨의 이름을 컨테이너에 명시하는 것 뿐만 아니라 경로도 같아야 한다는 점
- app 컨테이너의 볼륨의 경로가 만약
shared-data:/test/app
처럼 변경된다면 db 컨테이너의 볼륨의 경로도 shared-data:/test/mysql
처럼 바꿔줘야 같은 볼륨을 두 컨테이너에서 동시에 사용할 수 있음
- docker compose up 으로 두 컨테이너를 실행한 후
docker exec -it container-id bash
명령어로 접근해서 해당 경로로 이동하면 app이라는 이름의 디렉토리와 mysql이라는 이름의 디렉토리가 /test
경로 밑에 생기는 것을 확인할 수 있음