본문 바로가기

Tech/Docker

Docker Volume : 컨테이너의 데이터를 영속적으로 보관해 보자

 Docker Volume 

도커 이미지를 컨테이너로 실행하게 되면, 컨테이너는 아래 보이는 그림처럼 이미지레이어 위에 컨테이너레이어가 구성되었는 구조를 가집니다.

실행된 컨테이너의 구조

이때 이미지 레이어는 읽기전용(read only)이기에, 컨테이너로 무슨 작업을 하던 이 이미지 레이어는 어떠한 영향도 받지 않습니다. 반면, 컨테이너 레이어는 읽고 쓸수 있는 구조이기에(read & write), 컨테이너 내에서 생성되는 데이터는 해당 컨테이너 레이어에 저장되게 되죠. 리눅스의 union file system 기술이 위의 레이어들을 하나의 시스템처럼 동작할 수 있도록 해줍니다. 하지만, 여기에는 큰 위험성이 존재합니다. 해당 컨테이너를 삭제시키면, 컨테이너 레이어도 동시에 삭제되기 때문에 저장되어 있던 소중한 데이터를 잃을 수 있습니다.(게다가 컨테이너를 삭제하는 일은 아주 드물며, 실수로 진행하게 될 위험성이 충분히 있습니다.) 도커의 볼륨 기능은 이러한 위험성을 방지하고자, 컨테이너 내에서 발생하는 데이터들을 컨테이너 외부에 저장해 놓음으로써, 해당 데이터를 영속적으로 보관해 줄 수 있도록 해줍니다.

호스트와 볼륨 공유하기

호스트(내 컴퓨터)의 특정 폴더와 컨테이너의 폴더를 공유하여 데이터를 읽고 쓰는 방법입니다. 즉, 컨테이너 내에서 호스트의 볼륨을 공유받는것이죠. 이럴 경우, 컨테이너를 삭제하더라도 데이터들은 모두 호스트 내의 폴더에 유지될 것이기에 영속적으로 보관할 수 있게 됩니다.

컨테이너를 실행할때 -v 옵션을 추가해 주면 쉽게 호스트의 특정 폴더 또는 파일을 공유할 수 있습니다.

-v [host의 경로]:[컨테이너 내의 경로]
docker run -it --name volume_test -v /Users/rimo/test:/app ubuntu
  • -it : 컨테이너의 입출력을 터미널로 볼 수 있도록 하겠다.
  • --name : 컨테이너의 이름을 volume_test로 하겠다.
  • -v : 호스트의 /Users/rimo/test 디렉터리와 컨테이너의 app디렉터리와 연결(공유)하겠다.

위와 같이 명령어를 실행하면, 호스트의 test 폴더와 컨테이너의 app 디렉터리가 서로 공유됩니다. 이때 호스트 내에 test폴더가 없더라도, 컨테이너 내에 app이라는 폴더가 없더라도, 도커엔진이 알아서 해당 폴더를 생성해 줍니다. 이제 컨테이너 내에서 app폴더 내로 들어가, 아래와 같이 특정 파일을 생성해 보도록 하겠습니다.

cd app
mkdir container_folder

위의 명령에 따라, 컨테이너 내의 app폴더 내에는 container_folder가 생성되었습니다. 동시에 호스트의 test폴더에도 container_folder가 생성됨을 확인할 수 있죠. 이처럼 볼륨을 이용해 호스트의 공간을 컨테이너의 공간과 공유하여 데이터를 읽고 쓸 수 있습니다. 이때 주의해야 할 점은, 항상 호스트를 기준으로 컨테이너의 공유 공간이 덮어 써진다는 점입니다. 말이 쫌 어렵네요. 예시를 한번 들어 보겠습니다. 호스트의 test폴더에는 test1.txt파일이 존재하고, 컨테이너의 app폴더에는 test2.txt파일이 존재한다고 가정해 보겠습니다. 만약 호스트의 test폴더와 컨테이너의 app폴더를 -v옵션으로 공유하면 어떻게 될까요? 결론은, 호스트의 test폴더가 컨테이너의 app폴더를 덮어씌우면서, 컨테이너의 app폴더 내에는 기존의 test2.txt파일이 아닌, test1.txt파일이 존재하게 됩니다.

컨테이너 간에 볼륨 공유하기

컨테이너와 컨테이너 간에 볼륨을 공유하는 방법이 있습니다. A라는 컨테이너가 호스트와 공유되어 있을 때, B컨테이너가 해당 A 컨테이너와 볼륨을 공유를 하게 되면, B 또한 호스트와 공유되게 됩니다. 이때 특정 컨테이너의 볼륨을 공유받기 위해 --volumes-from 옵션을 사용할 수 있습니다. 위에서 volume_test라는 컨테이너는 app이라는 폴더와 호스트의 test폴더가 공유되어 있습니다. 이제는 volumes-from_test라는 컨테이너를 생성하고, 해당 컨테이너와 volume_test라는 컨테이너를 공유하도록 해보겠습니다.

--volumes-from [공유받을 컨테이너 이름]

 

 

docker run -it --name volumes-from_test --volumes-from volume_test ubuntu

이제 volumes-from_test라는 컨테이너는 volume_test의 컨테이너와 공유되어 있기에, app이라는 폴더를 사용할 수 있으며, 해당 app폴더는 호스트의 test파일과 마운트 되어 있을 것입니다. 이처럼 volumes-from 옵션을 사용하면, 호스트와 직접적으로 공유를 요청하지 않더라도, 간접적으로 공유를 받을 수 있게 됩니다. 이러한 구조를 활용하여, 아래 그림처럼, 호스트와의 볼륨을 공유하는 “볼륨 컨테이너”를 하나 구성하고(-v 옵션을 활용하여), 다른 컨테이너들이 해당 “볼륨 컨테이너”와 연결해(--volumes-from 옵션을 활용하여) 간접적으로 호스트와 연결하곤 합니다.

도커 자체의 볼륨 생성하기

호스트에 존재하는 특정 디렉토리를 볼륨으로 설정했던 기존 방법과는 달리, 도커 내에서 관리되는 도커 자체의 볼륨을 생성하여 컨테이너와 연결할 수 있습니다.

[도커 볼륨 생성]

docker volume create --name docker_volume

[도커 볼륨 확인]

docker volume ls

[컨테이너와 연결]

호스트의 디렉토리를 공유받기 위해 사용했던 -v옵션과 동일합니다.

-v [도커 볼륨 이름]:[컨테이너 내의 경로]
docker run -it --name test2 -v docker_volume:/root/ ubuntu

도커 볼륨은 그럼 어디에 저장되어 있는 걸까요? 아래 inspect 명령어를 통해 볼륨에 대한 세부 정보를 확인할 수 있습니다.

docker inspect docker_volume

이때 docker inspect 명령어는 이미지, 컨테이너, 볼륨에 대한 통합적인 명령어이기에, 볼륨에 대한 정보만 추출하기 위해 아래와 같이 명령어를 구성할 수 있겠습니다.

docker inspect --type volume docker_volume
[
    {
        "CreatedAt": "2023-04-13T04:51:48Z",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/docker_volume/_data",
        "Name": "docker_volume",
        "Options": {},
        "Scope": "local"
    }
]

위의 결과물에서 Mountpoint가 해당 볼륨이 호스트에 저장된 위치를 알려줍니다. 이처럼, 도커 볼륨은 앞서 알아봤던 호스트의 디렉터리와 공유를 하는 행위와 별반 다를 게 없습니다. 하지만, 호스트의 구체적인 경로를 알 필요 없이(작성해 줄 필요 없이), 단순히 볼륨을 생성할 수 있고, 도커 내에서 쉽게 관리할 수 있다는 장점이 있습니다.

마무리

컨테이너는 단지 특정 작업만을 처리할 뿐, 처리된 결과는 볼륨을 활용해 외부 보관 창고에 저장하는 것이 바람직합니다. 이처럼 컨테이너 그 자체로는 특정한 상태를 가지지 않는다고 하여, 이러한 관점을 stateless 라고 합니다. 반면, 작업된 데이터들이 컨테이너 그 자체에 저장되는 방식을 stateful 하다고 말합니다. stateful 한 방식은 컨테이너 설계 시 위험성이 존재하기에 지양하는 것이 좋습니다. 이처럼 컨테이너를 공부하다 보면 애플리케이션을 어떻게 구성해야 할지에 대한 철학을 엿볼 수 있는 재미가 있는 것 같습니다.

 

Reference