▶︎ 도커 볼륨
컨테이너는 스테이트리스하기 때문에 컨테이너가 삭제되거나 재시작돠면 모든 데이터가 이미지의 상태로 초기화가 되버린다.
그런데 서버를 운영하다보면 데이터를 유지해야 하는 경우가 생긴다.
여기서 유지한다는 것을 IT 환경에서는 영속성(persistence)이 있다고 표현한다.
특히 데이터베이스 서버를 컨테이너로 띄우는 경우에는 이 저장되는 데이터들이 DB 서버의 특정 디렉토리에 저장되는데,
이런 데이터들이 컨테이너가 삭제되거나 재생성될 때마다 초기화되면 서비스 운영에 큰 차질이 생길 것이다.
정리하자면 컨테이너는 상태가 없기 때문에 재생성되면 데이터가 모두 삭제되고 그래서 영속성이 필요한 데이터를 저장할 공간이 필요하다.
그래서 도커에서는 이렇게 영속성이 필요한 데이터를 위해서 도커 볼륨(Docker volume)이라는 기능을 제공하고 있다.
‣ 작동 원리
컨테이너들은 이 도커 볼륨을 컨테이너의 특정 경로에 마운트하여 사용한다.
예를 들어서 데이터베이스 컨테이너를 실행하면 컨테이너 안에 /var/lib/postgresql/data
라는 폴더에 실제 데이터가 저장되는데,
이 경로를 도커의 볼륨에 마운트한다고 생각해보면
이제부터 컨테이너가 실행된 다음에 이 경로(/var/lib/postgresql/data
)에 저장하는 파일들은 컨테이너 레이어에 저장되는 것이 아니라
마운트되어 있는 외부 볼륨에 저장되어 있는 것이다.
위 사진을 기준으로 볼륨은 1개이고 컨테이너는 3개인데 1개의 볼륨을 3개의 컨테이너가 모두 동일한 경로에 마운트 했기 때문에
모든 컨테이너는 동일한 데이터를 제공할 수 있게된다.
‣ 볼륨 생성
Docker에서 권장하는 방법인 볼륨에 대해서 먼저 알아보자.
docker volume create
커맨드를 이용하여 볼륨 새로 생성
docker volume create my-vol
my-vol
‣ 볼륨 조회
볼륨이 생성이 정상적으로 되었으면 docker volume ls
커맨드를 실행하여 생성한 볼륨 리스트를 확인할 수 있다.
docker volume ls
DRIVER VOLUME NAME
local my-vol
docker volume inspect
커맨드를 통해 해당 볼륨의 좀 더 상세한 정보를 확인 가능하다.
docker volume inspect my-vol
[
{
"CreatedAt": "2023-04-18T13:11:08Z",
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/my-vol/_data",
"Name": "my-vol",
"Options": {},
"Scope": "local"
}
]
Mountpoint
항목을 보면 해당 볼륨이 컴퓨터의 어느 경로에 생성되었는지도 체크 가능하다.
‣ 볼륨 마운트
컨테이너가 볼륨을 사용하기 위해서는 볼륨을 컨테이너에 마운트 해주어야 한다.
docker run
커맨드로 컨테이너를 실행할 때 -v
옵션을 사용해주면 된다.
콜론(:
)을 구분자로 해서
앞에는 마운트할 볼륨명
뒤에는 컨테이너 내의 경로
를 명시해주면 된다.
예를 들면 my-vol
볼륨을 one
컨테이너의 /app
경로에 마운트 해본다 해보자.
docker run -d --name one -v my-vol:/app wtt/test-con:latest touch /app/test.txt
touch /app/test.txt
커맨드를 실행하였기 때문에, test.txt
파일이 my-vol
볼륨의 경로에도 남아있을 것이다.
test.txt 파일이 존재하는지 확인
sudo find / -name "my-vol" 2>/dev/null | xargs -I / sudo tree -f /
/var/lib/docker/volumes/my-vol
└── /var/lib/docker/volumes/my-vol/_data
└── /var/lib/docker/volumes/my-vol/_data/test.txt
1 directory, 1 file
docker inspect
커맨드로 컨테이너의 상세 정보 확인해보면 my-vol
볼륨이 volume
타입으로 마운트되어 있는 것을 확인할 수 있다.
docker inspect one # 컨테이너 이름 'one'
(...생략...)
"Mounts": [
{
"Type": "volume",
"Name": "my-vol",
"Source": "/var/lib/docker/volumes/my-vol/_data",
"Destination": "/app",
"Driver": "local",
"Mode": "z",
"RW": true,
"Propagation": ""
}
],
(...생략...)
A. 다른 컨테이너에 볼륨 마운트
이번에는 같은 my-vol
볼륨을 two
컨테이너의 /app
경로에 마운트를 해보자.
docker run -d --name two -v my-vol:/app busybox ls /app
docker logs two
test.txt
ls /app
커맨드를 실행해보니, one
컨테이너가 볼륨에 생성해놓은 파일이 그대로 보이는 것을 알 수 있었다!
이렇게 여러 개의 컨테이너가 하나의 볼륨에 접근할 수 있기 때문에 컨테이너 간 데이터 공유가 가능하다.
다시 말해, 어떤 볼륨에 데이터를 저장해두고 여러 컨테이너에서 마운트만 해주면 해당 데이터를 모든 컨테이너에서 접근할 수 있게 되는 것이다
‣ 볼륨 삭제
마지막으로 docker volume rm
커맨드를 사용하여 my-vol
볼륨을 제거해보자
docker volume rm my-vol
Error response from daemon: remove my-vol: volume is in use - [ceb7ba76f3b0debc361348ebd36cdf38984bfcf30998f75aad90abf9b49e9b4b, 5e6f43a56753b757d235919691567ee61c07c80b85e17aaac19def257b9487b9]
위와 같이 제거하려는 볼륨이 마운트되어 있는 컨테이너가 있을 때는 해당 볼륨이 제거되지 않는다.
그럴때는 해당 볼륨이 마운트되어 있는 모든 컨테이너를 먼저 삭제 후, 볼륨을 삭제하면 정상적으로 해당 볼륨이 삭제된다.
docker rm $(docker ps -a -q)
docker volume rm my-vol
my-vol
‣ 볼륨 청소
docker volume prune
커맨드를 사용하여 마운트되어 있지 않은 모든 볼륨을 한번에 제거할 수 있다.
docker volume prune <<< y
‣ 볼륨 저장 경로
그렇다면 도커 볼륨은 호스트의 어느 경로에 저장되는 것일까?
위 장표는 도커 볼륨과 컨테이너 관계를 나타내는 도식도이다.
먼저 도커 볼륨 4개를 생성하면 호스트 OS의 역할을 하는 실습 PC의 특정 공간에
데이터를 저장하는 볼륨이 생성된다.
여기서 도커의 root directory를 확인하는 명령어는 아래와 같다.
docker info | grep -i 'docker root dir'
Docker Root Dir: /var/lib/docker
즉, 특정 공간이란 /var/lib/docker/volumes
하위에 생성되는 볼륨 디렉토리를 의미한다.
예를 들어, testVolume
이라는 새로운 도커 볼륨을 생성한다고 가정해보자.
docker volume create testVolume
그럼 새로 생성된 testVolume
은 아래 경로로 생성되게 된다.
docker volume inspect testVolume
▶︎ 바인드 마운트
위에서 도커 볼륨이라는 것을 통해 컨테이너 데이터를 영속화하는 것에 대해 학습하였다.
하지만 실제로는 도커가 경로를 자동으로 관리하고 도커가 실행되는 가상 머신 안에서 저장되기 때문에
이 볼륨을 저장하는 경로에 일반 사용자가 직접 접근하기는 쉽지 않다.
그래서 HostOS에서 데이터를 직접 관리하고 싶은 경우에는 Bind Mount
기능을 사용하면 된다.
해당 기능을 사용하면 호스트 파일 시스템의 특정 경로를 컨테이너로 바로 마운트 가능하다.
사진에서 보이는 것처럼 -v
옵션을 사용하면서 왼쪽에 볼륨 이름이 아닌 호스트 OS의 디렉토리의 경로를 지정하면
HostOS의 Bind Mount를 사용할 수 있다.
이 Bind Mount 기능을 사용하면 도커 볼륨이 별도로 만들어지지 않고,
호스트에서 데이터를 관리하는 것이 조금 더 직관적이므로
이런 바인드 마운트 기능은 직접 디렉토리 내용을 관리해야 하는 디버깅 같은 상황에서 매우 유용하게 활용될 수 있다.
바인드 마운트를 사용하는 방법은 docker run
커맨드를 실행할 때 , -v
옵션의 콜론(:
) 앞 부분에 마운트명 대신 호스트의 경로를 지정해주면 된다.
예를 들어, 현재 경로에 test.txt
파일을 생성하고 해당 호스트 경로를 컨테이너의 /app
경로에 마운트해보자
touch ./test.txt
docker run -v $(pwd):/app -it --name one busybox ls /app
test.txt
ls /app
커맨드를 실행해보면 test.txt
파일이 컨테이너의 /app
경로에도 존재하고 있는 것을 확인할 수 있었다.
반대로 컨테이너의 /app
경로 상에서 test2.txt
파일을 만들면 어떨까?
호스트의 현재 경로에도 test2.txt
파일이 존재하는지 테스트해보자
$ docker exec -it one sh
/ \# : touch test2.txt
/ \# : exit
$ ls -lh
test.txt test2.txt
docker inspect
커맨드로 해당 컨테이너의 상세 정보를 확인해보면 현재 경로 (/root
)가 bind 타입으로 마운트되어 있는 것을 확인할 수 있었다!
docker inspect one
(...생략...)
"Mounts": [
{
"Type": "bind",
"Source": "/home/ncloud",
"Destination": "/app",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
}
],
(...생략...)
▶︎ Volume vs bind
volume
, bind mount
둘의 차이점은 docker가 해당 마운트 포인트를 관리해주느냐 안해주느냐의 차이인 것 같다.
볼륨을 사용할 때는 우리가 스스로 볼륨을 생성하거나 삭제해야하는 불편함이 존재하지만,
해당 볼륨은 docker 상에서 이미지나 컨테이너, 네트워크와 비슷한 방식으로 관리가 되는 이점이 존재한다.
그래서 대부분의 상황에서는 볼륨 사용을 권장하지만 컨테이너화된 로컬 개발 환경을 구성할때는 바인드 마운트가 더 유용할지 모른다.
▶︎ 도커 볼륨 연습
‣ 연습 (1)
- docker volume & bind mount 응용
현재 yshrim12/feedback-node:latest
이미지가 있다.
해당 이미지는 node.js 로 간단한 form을 구성했고 데이터를 전송하면 feedback/{title.txt} 형식으로 파일로 데이터가 저장되는 형식이다
사진으로 설명하자면 아래와 같다.
해당 이미지를 기반으로 컨테이너를 돌려보자
$ docker run -d -it --name feedback-app -p 80:80 yshrim12/feedback-node:latest
정상적으로 컨테이너가 실행 되었고, feedback을 새로 생성하고 해당 feedback에 접근해보자.
feedback-app
컨테이너로 들어가서 feedback 디렉토리에 데이터가 쌓여있는지 확인해보자
$ docker exec -it feedback-app ls -lh feedback/
-rw-r--r-- 1 root root 5 Apr 20 06:48 hello.txt
-rw-r--r-- 1 root root 7 Apr 20 09:09 'new feedback.txt'
이제 위 컨테이너를 종료하고 다시 실행하면 feedback 디렉토리에 데이터는 그대로 남아있을까?
컨테이너 종료
$ docker kill feedback-app && docker rm $(docker ps -a -q)
컨테이너 재배포 및 컨테이너 접속 후 feedback 디렉토리 확인
$ docker run -d -it --name feedback-app -p 80:80 yshrim12/feedback-node:latest
$ docker exec -it feedback-app ls -lh feedback/
Shit .. 아무것도 없다
해결할 수 있는 방법이 뭐가 있을까?
아래 명령어를 사용해보자
$ docker run -d -it --name feedback-app -v feedback:/app/feedback -v "$(pwd)":/app -v /app/node_modules -p 80:80 yshrim12/feedback-node:latest
feedback:/app/feddback
: feedback 도커 볼륨을 컨테이너의/app/feedback
폴더와 마운트"$(pwd)":/app
: 로컬의 현재 경로를 컨테이너의/app
폴더와 마운트/app/node_modules
: 컨테이너의/app/node_modules
폴더를 마운트 제외시킴
‣ 연습 2 (호스트 볼륨)
- bind-mount 응용
- 호스트의 디렉토리를 컨테이너의 특정 경로에 마운트
host-volume.sh
#!/bin/bash
docker run -d -it --name nginx --rm -p 80:80 \
-v $(pwd)/html:/usr/share/nginx/html
‣ 연습 3 (도커 볼륨)
- 도커가 제공하는 볼륨 관리 기능을 활용하여 데이터를 보존함
- 기본적으로
/var/lib/docker/volumes/${volume-name}/_data
에 데이터가 저장된다
docker-volume.sh
#!/bin/bash
# 새로운 커스텀 볼륨 생성
docker volume create --name db
# 볼륨 목록 출력
docker volume ls
docker run -d -it --name mysql \
-e MYSQL_ROOT_PASSWORD=secret \
-e MYSQL_DATABASE=devops \
-v db:/var/lib/mysql \
-p 3306:3306 \
mysql:5.7
‣ 연습 4 (:ro)
- 읽기 전용(readonly) 볼륨 생성
- 볼륨 연결 설정에
:ro
옵션을 통해 읽기 전용 마운트 옵션을 설정할 수 있다
readonly-volume.sh
#!/bin/bash
docker run -d -it --name nginx -p 80:80 \
-v $(pwd)/html:/usr/share/nginx/html:ro \
nginx:latest
컨테이너에서는 html 디렉토리에서 파일을 추가하거나 삭제,수정이 불가능하다
$ docker exec touch /usr/share/nginx/htmk/hello.txt
touch: cannot touch '/usr/share/nginx/html/hello': Read-only file system
‣ 연습 5 (--volumes-from)
--volumes-from
: 특정 컨테이너의 볼륨 마운트를 공유할 수 있음- 이번 연습에 대해서는
nginx-1
,nginx-2
컨테이너가web-volume
컨테이너의 볼륨 마운트를 공유함- 호스트의
$(pwd):html
폴더가nginx-1
,nginx-2
컨테이너의/usr/share/nginx/html
와 공유함
- 호스트의
volume-container.sh
#!/bin/bash
docker run -d -it --name web-volume \
-v $(pwd)/html:/usr/share/nginx/html \
ubuntu:focal
docker run -d -it --name nginx-1 -p 80:80 \
--volumes-from web-volume \
nginx:latest
docker run -d -it --name nginx-2 -p 81:80 \
--volumes-from web-volume \
nginx:latest