Docker volume 기본 ✏️

·updated
// INDEX

Docker Volume 이란?

Docker volume 은 컨테이너 간에 데이터를 공유하거나, 컨테이너가 중지되고 삭제되더라도 데이터를 유지하기 위해 사용되는 도커에서 관리하는 데이터 스토리지이다.

Docker volume 을 사용하면 컨테이너가 중지되더라도 유지할 수 있는 데이터를 안전하게 저장할 수 있으며, 컨테이너와 호스트 간 데이터 공유도 가능하다.

도커 볼륨을 이해하기 위해선 마운트라는 개념을 이해하면 좋다.

마운트란?

mount 의 사전적인 정의는 디스크를 컴퓨터에서 사용할 수 있도록 만드는 것을 의미한다.
리눅스에서 mount 는 물리적인 장치(외부 저장소, 하드디스크, usb 등) 를 리눅스 파일 시스템의 특정 디렉토리를 연결하는 작업을 의미한다.
물리 장치를 호스트 머신의 특정한 디렉토리에 연결하여 호스트 머신의 특정 디렉토리에서 해당 물리장치를 읽을 수 있도록 하는 것이 마운트다.

컨테이너 관점에서 mount 를 바라보자.
컨테이너는 호스트 머신의 격리된 리소스를 사용하는 프로세스이다. 컨테이너는 추상화 되어 있기 때문에 컨테이너 내부는 별개의 머신과 같다.
볼륨을 마운트하는 것은 도커 컨테이너가 관리하는 데이터 스토리지를 컨테이너 환경의 특정 디렉토리에 연결하여, 컨테이너 내부에서 데이터 스토리지를 사용할 수 있도록 하는 것이다.

도커 볼륨에 대해서 본격적으로 알아보자.


Docker Volume 의 세 종류

docker volume 은 크게 익명 볼륨, 네임드 볼륨, 바인드 마운트 세 종류로 나눌 수 있다.


익명 볼륨 (Anonymous Volume)

익명 볼륨은 도커가 관리하는 임시적인 볼륨이다.

컨테이너 구동 시 새로운 익명 볼륨이 생성되며, 컨테이너를 삭제하면 해당 볼륨도 함께 삭제되기 때문에 컨테이너와 동일한 라이프 사이클을 가진다.

익명 볼륨은 주로 단일 컨테이너에서 사용한다.

docker continaer run -v /path/in/container busybox

위 명령어에서 Docker는 자동으로 익명 볼륨을 생성하여 /path/in/container 경로를 익명 볼륨으로 사용한다.



네임드 볼륨 (Named Volume)

네임드 볼륨은 사용자가 이름을 지정하여 생성한 볼륨이다.

네임드 볼륨은 컨테이너가 삭제되더라도 볼륨이 삭제되지 않기 때문에 데이터가 유지된다. 따라서 다른 컨테이너에서도 동일한 볼륨을 참조할 수 있다.

이를 통해 컨테이너간 데이터를 공유할 수 있고 지속성있는 데이터를 유지할 수 있다.

docker volume create my-volume
docker container run -v my-volume:/path/in/container busybox

위 명령어에서 my-volume이라는 이름의 볼륨이 생성되고, 컨테이너의 /path/in/container에 마운트된다.



바인드 마운트 (Bind Mount)

바인드 마운트는 호스트의 특정 디렉토리를 컨테이너에 마운트한다.

호스트 경로를 명시적으로 지정해야 하며, 호스트와 컨테이너 간의 파일 시스템의 공유가 가능하다.

바인드 마운트는 주로 개발 환경에서 호스트 파일 시스템에 대한 접근이 필요한 경우 사용된다.

docker container run -v /host/path:/container/path busybox

위 명령어에서 호스트의 /host/path 디렉토리가 컨테이너의 /container/path에 마운트된다.

바인드 마운트는 호스트의 파일 시스템을 컨테이너의 특정 경로에 마운트 시키는 것이기 때문에 호스트 파일 시스템에 해당 경로가 존재하지 않는 경우 도커 엔진은 해당 경로를 만들고 해당 경로를 마운트 시킨다.

호스트 파일 시스템에서 해당 경로를 삭제하지 않는 이상 데이터는 보존되며, 기존 파일 혹은 폴더가 존재하면 해당 파일 및 폴더를 사용할 수 있다.


바인드 마운트와 익명,네임드 볼륨의 공통점과 차이점

볼륨의 한 종류인 바인드 마운트는 익명 혹은 네임드 볼륨과 유사한점과 차이점이 존재한다.



공통점

  • 바인드 마운트, 익명 볼륨, 네임드 볼륨 모두 컨테이너와 호스트 간에 데이터 공유를 위해 특정 디렉토리를 마운트한다.

차이점

  • 바인드 마운트
    • 사용자가 정의한 특정 디렉토리를 컨테이너에 마운트한다.
    • 호스트 경로를 명시적으로 지정해야 한다.
    • 사용 예: docker container run -v /host/path:/container/path busybox
  • 익명, 네임드 볼륨
    • Docker 가 관리하는 특정 디렉토리를 마운트한다.
    • Docker 가 관리하는 디렉토리이기 때문에 사용자는 해당 경로를 알 필요가 없다.
      • 사용자가 해당 경로를 알아야 하는 경우는 바이드 마운트를 사용하면 된다.
      • 경로를 알아야 하는데 익명이나 네임드 볼륨을 사용하는 것은 절절하지 않은 사용 사례이다.
    • 사용 예: docker container run -v my-volume:/container/path busybox

특징바인드 마운트익명, 네임드 볼륨
호스트 디렉토리 지정필요필요 없음
볼륨 경로 관리 주체사용자Docker
사용 용도호스트 파일 시스템 접근, 개발 환경컨테이너 간 데이터 공유, 백업 및 복원

여기서 컨테이너와 호스트 간에 데이터를 공유한다는 것은 어떤 의미인지 알아보자.



컨테이너와 호스트 간 데이터 공유

익명 볼륨이든 네임드 볼륨이든 그 경로에 대한 관리 주체는 docker 이다.

docker container run -v named-volume:/etc/named -v /etc/anonymous busybox

네임드 볼륨과 익명 볼륨을 마운트 설정하여 컨테이너를 실행한다.

컨테이너를 실행하면 docker 엔진은 자동으로 named-volume 을 생성하고 익명의 볼륨을 하나 생성한다.

생성된 볼륨들을 확인하기 위해선 다음과 같은 명령어를 사용한다.

docker volume ls

각각의 볼륨이 어떻게 정의되어 있는지 확인하기 위해 inspect 명령어로 확인해보자.

docker volume inspect named-volume <anonymous-volume-name>

docker volume inspect

네임드 볼륨과 익명 볼륨 둘다 바인드마운트와 같이 호스트의 특정 경로를 컨테이너에 마운트한다.

다만 네임드 볼륨과 익명 볼륨은 특정 경로에 대한 선택을 도커 엔진이 하며, 바인드 마운트는 사용자가 하는것이다.


볼륨을 사용해보자

고로 작성된 간단한 웹 애플리케이션이 있다.

🔽 sample-web.tar

해당 파일을 다운 받고 압축을 풀자.

tar -xvf sample-web.tar

해당 애플리케이션은 사용자에게 파일 이름을 받아서 해당 파일 이름을 특정 경로에서 찾은 후 해당 파일의 내용을 읽어서 사용자에게 보여주는 기능을 하는 /files?path=xxx 엔드포인트를 포함하고 있다.

해당 프로젝트 경로로 가서 docker container 를 실행해보자.

cd sample-web
docker image build --tag sample-web .

도커 파일을 통해서 sample-web 을 실행 가능한 docker 이미지를 빌드했다.

이제 해당 도커 이미지를 바인드 마운트 설정 없이 컨테이너로 실행해보자.

docker container run -p 8080:8080 -d --rm sample-web

http://localhost:8080/files?path=test.txt 해당 경로에 요청을 보내면 적절한 응답이 오지 않고 에러가 발생한다.

func fileHandler(w http.ResponseWriter, r *http.Request) {
	// Get the 'path' query parameter
	queryPath := r.URL.Query().Get("path")
	if queryPath == "" {
		http.Error(w, "Path query parameter is missing", http.StatusBadRequest)
		return
	}

	p := "/app/data"
	filePath := path.Join(p, queryPath)

	file, err := os.ReadFile(filePath)

	if err != nil {
		http.Error(w, fmt.Sprintf("Failed to read file: %v", err), http.StatusInternalServerError)
		return
	}
	fmt.Fprintln(w, string(file))

}

/app/data 경로를 기반으로 file 을 찾아오는데 해당 경로에 test.txt 로 요청한 파일이 존재하지 않아서 발생하는 에러다.

바인드 마운트를 사용하면 이를 간단하게 해결할 수 있다.

실행중인 컨테이너를 중지하고 이번엔 바인드 마운트 설정을 추가한 후 컨테이너를 실행해보자.

docker container run -p 8080:8080 -d --rm -v ./data:/app/data:ro sample-web

바인드 마운트 설정 없이 실행한 컨테이너의 애플리케이션은 에러가 발생했지만 바인드 마운트를 설정한 컨테이너 애플리케이션에 동일한 경로로 요청을 보내면 파일의 내용을 잘 읽어온다.

이번엔 프로젝트 루트의 data 폴더에 원하는 내용의 파일을 추가해보자.

echo "hello my name sehyeong" >> ./data/sehyeong.txt

해당 파일을 추가한 후 http://localhost:8080/files?path=sehyeong.txt 로 요청을 보내면 다음과 같은 응답이 잘 출력된다.

docker volume saved response


Docker CLI 를 사용한 볼륨 관리

  • 볼륨 생성:
docker volume create my-volume  # 네임드 볼륨 생성
  • 볼륨 목록 보기:
docker volume ls  # 네임드 볼륨, 익명 볼륨을 볼 수 있다.
  • 볼륨 삭제:
docker volume rm my-volume
  • 볼륨을 사용한 컨테이너 실행:
# 익명 볼륨
docker container run -d --name my-container -v /data busybox
# 네임드 볼륨
docker container run -d --name my-container -v name:/data busybox
# 바인드 마운트
docker container run -d --name my-container -v ./path:/data busybox

Docker Compose 를 사용한 볼륨 선언

앞서 수행한 바인드 마운트를 사용하여 웹 애플리케이션을 실행하는 docker command 를 docker compose 에 설정해보자.

version: '3.9'

services:
  web:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "8080:8080"
    volumes:
      - ./data:/app/data:ro

만약 네임드 볼륨을 사용하려면 volume 이름에 대해서도 선언해줘야 한다.

version: '3.9'
services:
  web:
    image: nginx
    volumes:
      - my-volume:/var/lib/nginx
  db:
    image: postgres
    volumes:
      - db-data:/var/lib/postgresql/data

volumes:
  my-volume:
  db-data:

다음 명령어를 docker-compose.yaml 파일이 있는 경로에서 실행하자.

  • 애플리케이션 시작:
docker compose up -d  # detach mode 에서 compose 를 실행한다.
  • 애플리케이션 중지 및 볼륨 제거:
docker compose down -v  # compose 중지 시 docker volume 도 삭제한다