- 컨테이너가 중지되면 데이터는 컨테이너와 함께 제거되고, 유지되지 않습니다.
- 다른 프로세스 또는 애플리케이션에서 컨테이너 안의 데이터를 사용하기 위해 컨테이너 밖으로 데이터를 가져오는 것은 번거롭거나 어려운 작업이 될 수 있습니다.
- 컨테이너의 writable layer에 데이터를 기록하기 위해서는 파일스시템을 관리 할 storage driver 가 필요합니다. storage driver는 리눅스의 커널을 이용해서 union filesystem을 제공하기때문에 이러한 추가적인 추상화로 인해 Host 파일시스템에 직접 데이터를 기록하는 것과 비교해보면 성능측면에서 약점으로 작용합니다.
- Volume
- Bind mount
- Tmpfs mount (On Linux)
1. Volume 타입의 선택
위에서 언급한 3가지 유형의 어떤 마운트 방법을 사용하던, 컨테이너에서 보여지는 데이터는 동일하지만, 3가지 유혀의 Mount는 Host의 어떠한 영역에 마운트되느냐에 차이점이 있습니다.
- Volumes** : Docker에 의해 관리되는 Host 파일시스템 영역에 데이터를 저장합니다.
(
/var/lib/docker/volumes/on Linux). Docker에 의해 관리되고, Non-Docker process는 이 영역을 수정할 수 없으므로 Volume은 Docker에서 영구데이터를 관리하는 가장 좋은 방법입니다. - Bind mounts : Host 머신의 파일시스템 어디에나 파일을 저장할 수 있는 방법입니다. Docker외의 다른 프로세스에서 데이터의 접근과 수정이 가능합니다.
- tmpfs mounts : Host의 메모리에 데이터를 저장하는 방법입니다.
2. Volume 타입의 상세
- volume
docker volume create 명령어로 명시적으로 Volume을 생성할 수 있습니다.
root@ubuntu:/# docker volume create volume1
volume1
root@ubuntu:/# docker volume ls
DRIVER VOLUME NAME
local volume1
Volume을 생성하면 Docker Host 머신의 Docker에서 관리하는 디렉터리에 저장됩니다.
root@ubuntu:/# docker volume inspect volume1
[
{
"CreatedAt": "2019-02-22T02:25:12+09:00",
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/volume1/_data",
"Name": "volume1",
"Options": {},
"Scope": "local"
}
]
root@ubuntu:/# ls /var/lib/docker/volumes/
metadata.db volume1
Volume을 컨테이너 기동시 마운트하면 이 디렉터리가 컨테이너의 지정된 디렉터리로 마운트됩니다.
root@ubuntu:/# docker run -it --name myubuntu --mount source=volume1,target=/volumedata ubuntu
root@da5fe2af29c5:/# ls
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var volumedata
root@da5fe2af29c5:/# cd volumedata/
root@da5fe2af29c5:/volumedata# touch hellovolume
root@da5fe2af29c5:/volumedata# ls
hellovolume
root@da5fe2af29c5:/volumedata# exit
exit
root@ubuntu:/# cd /var/lib/docker/volumes/volume1/_data/
root@ubuntu:/var/lib/docker/volumes/volume1/_data# ls
hellovolume
Container 정보를 살펴보면 기본설정으로 Volume의 읽기쓰기모드가 RW(읽기쓰기) 인 것을 알 수 있습니다.
--mount 옵션에 readonly 옵션을 주어 읽기쓰기 모드를 변경 할 수 있습니다
root@ubuntu:/# docker inspect myubuntu
[
{
"Id": "f6369bab35f2c89c0e0fed76c024decc4c19707aa833f6407fc0b1f60f42c38c",
"Created": "2019-02-21T23:40:56.46535649Z",
"Path": "/bin/bash",
"Args": [],
"State": {
"Status": "running",
"Running": true,
"Paused": false,
"Restarting": false,
"OOMKilled": false,
"Dead": false,
"Pid": 29553,
"ExitCode": 0,
"Error": "",
"StartedAt": "2019-02-21T23:40:57.125309312Z",
"FinishedAt": "0001-01-01T00:00:00Z"
},
...
"Mounts": [
{
"Type": "volume",
"Name": "volume1",
"Source": "/var/lib/docker/volumes/volume1/_data",
"Destination": "/volumedata",
"Driver": "local",
"Mode": "z",
"RW": true,
"Propagation": ""
}
],
# docker run -it --name myubuntu1 --mount source=volume1,target=/volumedata,readonly ubuntu
# docker inspect myubuntu1
[
{
"Id": "47e5ba4a6746ab416e2b1985040805cc7574c368989ae9d9dbe6d2186236ba33",
"Created": "2019-02-21T23:46:07.024314426Z",
"Path": "/bin/bash",
"Args": [],
"State": {
"Status": "running",
"Running": true,
"Paused": false,
"Restarting": false,
"OOMKilled": false,
"Dead": false,
"Pid": 29663,
"ExitCode": 0,
"Error": "",
"StartedAt": "2019-02-21T23:46:07.413027114Z",
"FinishedAt": "0001-01-01T00:00:00Z"
},
...
"Mounts": [
{
"Type": "volume",
"Name": "volume1",
"Source": "/var/lib/docker/volumes/volume1/_data",
"Destination": "/volumedata",
"Driver": "local",
"Mode": "z",
"RW": false,
"Propagation": ""
}
],
...
root@47e5ba4a6746:/volumedata# touch test
touch: cannot touch 'test': Read-only file system
Volume은 동시에 여러 컨테이너에 마운트 할 수 있으며, 자동으로 Volume이 제거되지 않습니다.
Volume을 제거하기 위해서는 docker volume rm, docker volume prune 명령어로 제거 할 수 있습니다.
# docker volume rm volume1
사용하지 않은 Volume을 제거하기 docker volume prune 명령어로 정리 할 수 있습니다.
# docker volume prune
volume 관련 명령어
| Command | Description |
|---|---|
| docker volume create | Create a volume |
| docker volume inspect | Display detailed information on one or more volumes |
| docker volume ls | List volumes |
| docker volume prune | Remove all unused local volumes |
| docker volume rm | Remove one or more volumes |
- Bind mount
# docker run -it -v /volume/bindmount:/data/bindmount ubuntu
root@b1f8e57c3314:/data/bindmount# cd /data/bindmount
root@b1f8e57c3314:/data/bindmount# touch testfile
root@b1f8e57c3314:/data/bindmount# ls
testfile
root@b1f8e57c3314:/data/bindmount# exit
root@ubuntu:/# cd /volume/bindmount/
root@ubuntu:/volume/bindmount# ls
testfile
Bind mount는 Volume에 비해 아래와 같은 불리한 점이 있기 때문에 가능하다면 Volume을 사용하는 것이 권장됩니다.
- Bind mount는 Host의 디렉터리 구조에 의존적입니다.
- Docker에 의해 관리되어지지 않습니다.
- Container안의 프로세스가 Host의 파일시스템을 변경할 수 있으므로 보안상 위협이 될 수 있습니다.
- tmpfs mounts
tmpfs mount는 디스크에 데이터를 영구적으로 저장하지 않고 Host의 메모리에 저장합니다. 이러한 특징 때문에 tmfs mount Container의 생명주기와 맞춰서 데이터를 보존하고자 할 때 사용되어질 수 있습니다. 예를 들어, 내부적으로 Docker swarm service는 tmpfs mount를 사용하여 컨테이너 안으로 secret을 마운트합니다.
3. 예제
docker 데이터 볼륨으로 데이터 공유를 해보겠습니다.
다음 명령을 실행하여 컨테이너를 생성하고 데이터 볼륨을 설정합니다. 컨테이너의 Bash 셸이 실행되면 /data 디렉터리로 이동한 뒤 world라는 빈 파일을 생성합니다. 그리고 exit를 입력하여 Bash 셸에서 빠져나옵니다.
$ sudo docker run -i -t --name hello-volume1 -v /root/data:/data ubuntu /bin/bash
root@f7baf3abefee:/# cd /data
root@f7baf3abefee:/data# touch world
root@f7baf3abefee:/data# exit
데이터 볼륨 옵션은 -v <호스트 디렉터리>:<컨테이너 디렉터리> 형식입니다. 여기서는 호스트의 /root/data 디렉터리를 Docker 컨테이너의 /data 디렉터리에 연결합니다.
/root/data 디렉터리의 파일 목록을 출력합니다.
$ sudo ls /root/data
world
앞에서 생성한 world 파일이 보입니다.
이제 두 번째 컨테이너를 생성합니다. 컨테이너의 Bash 셸이 실행되면 /data 디렉터리의 파일 목록을 출력합니다.
$ sudo docker run -i -t --name hello-volume2 -v /root/data:/data ubuntu /bin/bash
root@af5a7bdb3e5a:/# ls /data
world
앞에서 생성한 world 파일이 hello-volume2 파일에서도 보입니다. /data 디렉터리에 파일을 생성하면 호스트 및 hello-volume1 컨테이너에서도 사용할 수 있습니다. 이렇게 데이터 볼륨 설정을 통해 컨테이너끼리 데이터를 공유할 수 있습니다.
디렉터리뿐만 아니라 호스트의 파일 하나만 컨테이너에 연결할 수도 있습니다.
$ sudo docker run -i -t --name hello-volume -v /root/hello.txt:/root/hello.txt \
ubuntu /bin/bash
4. 볼륨 백업과 복원
지금까지 Volume 을 만들고 마운트하는 방법을 봤는데, 실제 운영에서는 백업하고 복원하는 방법도 같이 알아둬야 합니다. Docker 공식 문서(docs.docker.com)가 안내하는 방식은 백업 전용 컨테이너를 하나 띄워 그 안에서 tar 로 압축하는 것입니다. 먼저 데이터가 들어 있는 볼륨을 가진 컨테이너를 하나 만듭니다.$ docker run -v /dbdata --name dbstore ubuntu /bin/bash
이 컨테이너는 실행만 되고 바로 멈춰도 상관없습니다. 뒤에서 --volumes-from 옵션으로 이 컨테이너가 가진 볼륨만 참조할 것이기 때문입니다.
$ docker run --rm --volumes-from dbstore -v $(pwd):/backup ubuntu tar cvf /backup/backup.tar /dbdata
--volumes-from dbstore 로 dbstore 컨테이너의 볼륨을 그대로 물려받고, 현재 디렉터리를 /backup 으로 마운트한 뒤 그 안의 데이터를 tar 로 압축합니다. 명령이 끝나면 --rm 옵션 때문에 이 컨테이너는 바로 삭제되고, 현재 디렉터리에 backup.tar 파일만 남습니다.
복원은 반대 순서로 하면 됩니다. 새 컨테이너를 하나 만들고,
$ docker run -v /dbdata --name dbstore2 ubuntu /bin/bash
그 컨테이너의 볼륨에 백업 파일 내용을 풀어 넣습니다.
$ docker run --rm --volumes-from dbstore2 -v $(pwd):/backup ubuntu bash -c "cd /dbdata && tar xvf /backup/backup.tar --strip 1"
--strip 1 옵션은 tar 안에 저장된 최상위 디렉터리 경로를 한 단계 벗겨내는 역할입니다. 이 과정을 거치면 dbstore 의 /dbdata 에 있던 파일이 dbstore2 의 /dbdata 에서도 그대로 보입니다.
직접 확인: 위 네 줄을 로컬 Docker 로 그대로 실행하면, dbstore 컨테이너의 /dbdata 에 파일 하나를 만들어 둔 뒤 dbstore2 에서 docker exec -it dbstore2 ls /dbdata 로 같은 파일이 보이는지 확인할 수 있습니다. 운영 서버를 옮기거나 재설치하기 전에 이 방식으로 볼륨만 따로 복사해 두면, 컨테이너를 전부 새로 띄워도 데이터는 그대로 이어집니다.
5. 자주 하는 실수
Volume 을 쓰다 보면 반복되는 실수가 몇 가지 있습니다.- docker compose down -v 로 데이터까지 날리는 경우:
docker compose down에-v옵션을 붙이면 compose 파일에 정의된 named volume 과 컨테이너에 붙어 있던 anonymous volume 이 함께 삭제됩니다. 컨테이너만 내리고 데이터는 남기고 싶다면-v없이docker compose down만 실행해야 합니다. - 이름 없는 볼륨(anonymous volume)이 쌓여 디스크를 먹는 경우: 이름을 지정하지 않고 컨테이너를 반복해서 재생성하면, 그때마다 새 anonymous volume 이 만들어지고 이전 것은 어떤 컨테이너에도 연결되지 않은 채 디스크에 남습니다.
docker system df -v로 볼륨이 차지한 용량을 먼저 확인하고,docker volume prune으로 어떤 컨테이너도 쓰지 않는 볼륨만 정리합니다. prune 은 사용 중이지 않은 볼륨을 전부 지우므로, 실행 전에docker volume ls로 지워도 되는 목록인지 한 번 확인하는 편이 안전합니다.
6. 마치며
이상으로 docker volume에 대해서 알아봤습니다. 필자 같은 경우는 주로, bind mount방식으로 많이 사용하고 있습니다. 정확하게 어떠한 방식이 더 좋다라고는 말씀드리기 어렵지만, 각 자의 방식에 맞춰서 유동적으로 사용하시면 될것 같습니다. 다음시간에는 Compose 구성과 사용법에 대해서 알아보겠습니다. 워니즈 블로그 워니즈 깃헙함께 보면 좋은 글
새 글이 올라오면 Threads @wonizz.ai 에도 올립니다. 팔로우해 두면 피드에서 바로 볼 수 있습니다.