Profile picture

[Docker] 도커 볼륨(Volume)

JaehyoJJAng2023년 04월 06일

▶︎ 도커 볼륨

image
컨테이너는 스테이트리스하기 때문에 컨테이너가 삭제되거나 재시작돠면 모든 데이터가 이미지의 상태로 초기화가 되버린다.

그런데 서버를 운영하다보면 데이터를 유지해야 하는 경우가 생긴다.

여기서 유지한다는 것을 IT 환경에서는 영속성(persistence)이 있다고 표현한다.

특히 데이터베이스 서버를 컨테이너로 띄우는 경우에는 이 저장되는 데이터들이 DB 서버의 특정 디렉토리에 저장되는데,

이런 데이터들이 컨테이너가 삭제되거나 재생성될 때마다 초기화되면 서비스 운영에 큰 차질이 생길 것이다.

정리하자면 컨테이너는 상태가 없기 때문에 재생성되면 데이터가 모두 삭제되고 그래서 영속성이 필요한 데이터를 저장할 공간이 필요하다.


image
그래서 도커에서는 이렇게 영속성이 필요한 데이터를 위해서 도커 볼륨(Docker volume)이라는 기능을 제공하고 있다.


‣ 작동 원리

컨테이너들은 이 도커 볼륨을 컨테이너의 특정 경로에 마운트하여 사용한다.

image
예를 들어서 데이터베이스 컨테이너를 실행하면 컨테이너 안에 /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

‣ 볼륨 저장 경로

그렇다면 도커 볼륨은 호스트의 어느 경로에 저장되는 것일까?

image
위 장표는 도커 볼륨과 컨테이너 관계를 나타내는 도식도이다.

먼저 도커 볼륨 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

image


▶︎ 바인드 마운트

위에서 도커 볼륨이라는 것을 통해 컨테이너 데이터를 영속화하는 것에 대해 학습하였다.

하지만 실제로는 도커가 경로를 자동으로 관리하고 도커가 실행되는 가상 머신 안에서 저장되기 때문에

이 볼륨을 저장하는 경로에 일반 사용자가 직접 접근하기는 쉽지 않다.

그래서 HostOS에서 데이터를 직접 관리하고 싶은 경우에는 Bind Mount 기능을 사용하면 된다.

해당 기능을 사용하면 호스트 파일 시스템의 특정 경로를 컨테이너로 바로 마운트 가능하다.
image
사진에서 보이는 것처럼 -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} 형식으로 파일로 데이터가 저장되는 형식이다

사진으로 설명하자면 아래와 같다.

image


해당 이미지를 기반으로 컨테이너를 돌려보자

$ docker run -d -it --name feedback-app -p 80:80 yshrim12/feedback-node:latest

정상적으로 컨테이너가 실행 되었고, feedback을 새로 생성하고 해당 feedback에 접근해보자.

image

image


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

Loading script...