스토리지 드라이버(storage driver)

Q·2022년 7월 26일
0

Docker

목록 보기
12/14
post-custom-banner

Contents

  • 스토리지 드라이버의 원리
  • 스토리지 드라이버 사용하기
    - AUFS 드라이버
    - Devicemapper
    - OverlayFS
    - Btrfs
    - ZFS

1. 스토리지 드라이버의 원리

이미지는 읽기 전용 파일로 사용되며, 컨테이너는 이 이미지 위에 얇은 컨테이너 레이어를 생성함으로써 컨테이너의 고유한 공간을 생성한다는 것이 도커 컨테이너의 기본적인 개념입니다.

그러나 실제로 컨테이너 내부에서 읽기와 새로운 파일 쓰기, 기존의 파일 쓰기 작업이 일어날 때는 드라이버에 따라 Copy-on-Write(CoW) 또는 Redirect-on-Write(RoW) 개념(+스냅샷)을 사용합니다.

(스토리지 드라이버에 대해서는 따로 알아보는 시간이 필요할 것 같습니다. 이번 글에서는 간단하게만 알아보겠습니다.)

공식 문서에도 Storage에 대해 overview해주고 있습니다.

https://docs.docker.com/storage/storagedriver/

keyword : 스냅샷(snapshot), CoW, RoW

스냅샷의 기본 개념은 '원본 파일은 읽기 전용으로 사용하고, 이 파일이 변경되면 새로운 공간을 할당한다' 입니다. 스토리지를 스냅샷으로 만들면 스냅샷 안에는 어느 파일이 어디에 저장되어 있는지가 목록으로 저장됩니다. 그리고 이 스냅샷을 사용하다가 스냅샷 안의 파일에 변화가 생기면 변경된 내역을 따로 관리함으로써 스냅샷을 사용합니다.

예를 들어, 위 이미지에 A,B,C 파일이 스냅샷으로 생성되었다면, 이 파일에 읽기 작업을 수행하는 어플리케이션은 단순히 파일시스템의 원본 파일에 접근해 파일 내용을 읽으면 됩니다. 그러나 어플리케이션이 스냅샷의 A 파일에 쓰기 작업을 수행해야 할 경우에는 조금 다릅니다. 원본 파일을 유지하면서도 변경된 사항을 저장할 수 있어야 하기 때문입니다. 이를 해결하는 방법에 따라 CoW와 RoW로 나누어지게 됩니다.

CoW는 스냅샷의 파일에 쓰기 작업을 수행할 때, 스냅샷 공간(pool)에 원본 파일을 복사한 뒤 쓰기 요청을 반영합니다. 이 과정에서 복사하기 위해 파일을 읽는 작업 한 번과 파일을 스냅샷 공간에 쓰고 변경된 사항을 쓰는 작업으로 총 2번의 쓰기 작업이 일어납니다. 따라서 오버헤드가 꽤 발생합니다.


CoW의 파일 쓰기 과정

RoW는 CoW와 다르게 한 번의 쓰기 작업만 일어납니다. 이는 파일을 스냅샷 공간으로 복사하는 것이 아니라 스냅샷에 기록된 원본 파일을 스냅샷 파일로 묶은 뒤 변경된 사항을 새로운 장소에 할당받아 덮어쓰는 형식입니다. 스냅샷 파일은 그대로 사용하되, 새로운 블록은 변경 사항으로 사용하는 것입니다.


RoW의 파일 쓰기 과정

이를 도커 컨테이너와 이미지에 적용하면 다음과 같습니다.

이미지 레이어는 각 스냅샷에 해당하고, 컨테이너는 이 스냅샷을 사용하는 변경점입니다. 컨테이너 레이어에는 이전 이미지에서 변경된 사항이 저장되어 있으며, 컨테이너를 이미지로 만들면 변경된 사항이 스냅샷으로 생성되고 이것이 하나의 이미지 레이어로서 존재하게 됩니다.

각 디바이스 드라이버 별로 스냅샷과 레이어를 지칭하는 용어는 조금씩 다를 수 있지만 기본적으로 이러한 개념을 따르고 있습니다.

2. 스토리지 드라이버 사용하기

다양한 스토리지 드라이버를 사용하는 방법에 대해서 알아보겠습니다.

각 스토리지 드라이버의 특징에 대해서는 공식 문서에 간단히 설명하고 있습니다.

https://docs.docker.com/storage/storagedriver/select-storage-driver/

그리고 왠만하면 overlay2를 선호하는 것 같습니다.

그리고 overlay와 devicemapper는 deprecated 될 예정이므로, 간단하게 어떤 것인지만 알아보고 넘어가도록 하겠습니다.

(aufs도 deprecated 예정으로 확인됩니다.)

2.1. AUFS 드라이버

AUFS 드라이버는 데비안 계열에서 기본적으로 사용할 수 있는 드라이버입니다. 도커에서 오랜 기간 사용해왔기 때문에 안정성 측면에서 우수하는 평가받지만, AUFS 모듈은 기본적으로 커널에 포함되어 있지 않으므로 일부 운영체제에서는 사용할 수 없으며, 사용할 수 없는 대표적인 운영체제는 RHEL, CentOS 입니다.

먼저 AUFS 드라이버를 사용할 수 있는 리눅스 배포판인지 확인하려면 아래의 명령어를 입력합니다.

grep aufs /proc/filesystems

위와 같이 출력되면 AUFS 드라이버를 사용할 수 있다는 뜻입니다.

그리고 도커 데몬을 실행할 때 '--storage-driver=aufs' 옵션을 추가해서 실행하면 AUFS 드라이버를 사용하도록 설정하면 됩니다. 저의 경우에는 docker service를 정지시키고, dockerd 명령어로 옵션을 추가해서 실행하는 방법을 사용했습니다.

1. sudo service docker stop
2. sudo dockerd --storage-driver aufs

AUFS 드라이버는 지금까지 설명한 이미지 구조와 유사합니다.

여러 개의 이미지 레이어를 유니언 마운트 지점(Union Mount Point)으로 제공하며, 컨테이너 레이어는 여기에 마운트해서 이미지를 읽기 전용으로 사용합니다. Union Mount Point는 /var/lib/docker/aufs/mnt에 존재하고, 컨테이너 레이어는 /var/lib/docker/aufs/diff에 존재합니다.

AUFS 드라이버는 컨테이너에서 읽기 전용으로 사용하는 파일을 변경해야 한다면 컨테이너 레이어로 전체 파일을 복사하고, 이 파일을 변경함으로써 변경사항을 반영합니다. 복사할 파일을 찾기 위해 이미지의 가장 위의 레이어부터 시작해 아래 레이어까지 찾기 때문에 크기가 큰 파일이 이미지의 아래쪽 레이어에 존재한다면 시간이 더 오래 걸릴 수 있습니다. 그러나 한 번 파일이 컨테이너 레이어로 복사되고 나면 그 뒤로부터 이 파일로 쓰기 작업을 수행합니다.

AUFS는 컨테이너의 실행, 삭제 등의 컨테이너 관련 수행 작업이 매우 빠르므로 PaaS(Platform As A Service)에 적합한 드라이버로 꼽히곤 합니다. 또한, 위에서 언급한 것과 같이 도커에서 지원하는 대표적인 스토리지 드라이버이기 때문에 많은 사람이 일반적으로 사용하는 드라이버 중 하나입니다.

(but... docker info로 확인해보면 aufs는 deprecated 예정이라고 합니다...)

2.2. Devicemapper 드라이버

Devicemapper 드라이버는 RedHat 계열의 리눅스 배포판을 위해 개발된 스토리지 드라이버입니다. 따라서 CentOS를 포함한 대부분의 리눅스 배포판에서 보편적으로 사용할 수 있습니다. 다만, 성능상의 이유 등으로 더 이상 사용하지 않는 것이 권장됩니다. 따라서 호스트의 커널 버전이 너무 낮거나, 여러 스토리지 드라이버를 사용할 수 없는 것이 아니라면 overlay2 스토리지 드라이버를 사용하는 것을 권장합니다.

RedHat 계열의 리눅스는 도커 엔진 1.13.0 버전 이전에는 Devicemapper를, 1.13.0 버전 이후에는 OverlayFS를 기본으로 사용하도록 설정되어 있습니다. OverlayFS의 사용이 불가능한 일부 CentOS 배포판에서는 Devicemapper를 기본적으로 사용하도록 설정되며, 2019년 3월 기준으로 CentOS 최신 버전인 7.6은 Overlay2 스토리지 드라이버를 지원합니다.

Devicemapper는 도커 데몬 실행 옵션에 '--storage-driver=devicemapper' 를 추가하면 됩니다.

이 드라이버를 사용하도록 설정한 뒤, /var/lib/docker/devicemapper/devicemapper 디렉토리를 살펴보면 다음과 같이 2개의 파일을 볼 수 있습니다.

무려 100GB 크기의 data라는 파일이 생성되어 있습니다. 그러나 도커가 이 파일 전체를 실제로 사용하는 것은 아니며, 100GB 크기의 희소파일(sparse file)에서 공간을 할당받아 이미지와 컨테이너 레이어를 저장합니다.

이는 이미지와 컨테이너의 데이터가 분리된 디렉토리로 저장되는 것이 아닌 data 파일로 이루어진 pool에서 블록 단위로 할당받는 구조입니다. 컨테이너와 이미지 블록의 정보는 metadata 파일에 저장됩니다.

devicemapper는 컨테이너의 저장공간을 제어할 수 있고, data파일과 metadata 파일의 크기도 변경할 수 있습니다.

dockerd --storage-driver devicemapper --storage-opt dm.loopdatasize=500GB --storage-opt dm.loopmetadatasize=10GB

Devicemapper 드라이버를 사용하는 컨테이너는 allocate-on-demand 라는 원리로 컨테이너 내부에서 새로운 파일을 기록합니다. 이는 devicemapper의 pool에서 필요한 만큼의 64KB 크기의 블록 개수를 할당해서 쓰기 작업을 수행하는 것입니다. 컨테이너 내부에 이미 존재하는, 즉 이미지에 존재하던 데이터에 쓰기 작업을 수행할 때는 변경하려는 원본 파일을 블록을 컨테이너에 복사한 뒤, 컨테이너 내부에서 복사된 블록 파일을 수정합니다.

이는 AUFS 드라이버와 달리 전체 파일을 복사하지 않는다는 점에서 성능상의 이점이 있지만, devicemapper는 컨테이너를 생성하고 삭제하는 등의 작업은 빠른 편이 아닙니다.

Devicemapper의 성능을 개선하기 위해서는 기본적으로 사용하는 모드인 loop-lvm을 direct-lvm으로 변경해서 사용하는 방법이 있습니다. 현재 사용중인 모드는 docker info에서 Data file을 확인하면 됩니다.

Devicemapper 드라이버는 devicemapper라는 데이터 pool 안에 컨테이너와 이미지 데이터를 저장하기 때문에 Multitenancy 환경을 위해 각 컨테이너와 이미지를 분리해 관리하는 것이 불가능합니다. 따라서, 실제 운영 환경에서 PaaS와 같은 용도로 사용하는 것을 권장하지 않는 추세이며, 굳이 사용한다면 devicemapper의 스토리지 풀로 loop-lvm이 아닌 direct-lvm을 사용하는 것이 좋습니다.

2.3. OverlayFS 드라이버 사용하기

OverlayFS는 RedHat 계열 및 Raspbian, ubuntu 등 대부분의 운영체제에서 도커를 설치하면 자동으로 사용되도록 설정되는 드라이버입니다. 이 드라이버는 AUFS와 비슷한 원리로 동작하지만 조금 더 간단한 구조로 사용되며 성능 또한 좀 더 좋기 때문에 최신 버전의 도커는 OverlayFS를 기본적으로 사용하고 있습니다.

OverlayFS는 overlay와 overlay2 드라이버로 나누어 집니다. overlay는 커널 3.18 버전 이상부터 기본적으로 내장되어 있으며, overlay2는 4.0 버전 이상에서 사용할 수 있습니다. overlay2가 overlay보다 성능이 조금 더 우수하며, 이미지를 구성하기 위해 여러 개의 레이어 구조를 지원합니다.

따라서 가능하다면 overlay2 스토리지 드라이버를 사용하는 것을 권장합니다. overlay는 다른 스토리지 드라이버와는 달리 계층화된 이미지 구조를 사용하지 않으며, lowerdir이라는 단일화된 이미지 레이어를 사용합니다.

overlay 드라이버를 사용하려면 커널 버전과 overlay 커널 모듈이 사용 가능한 상태인지 확인해야합니다. 커널 버전이 너무 낮지만 않으면 사용할 수 있습니다.

먼저 overlay를 살펴보고 overlay2에 대해서 알아보도록 하겠습니다.

overlay

overlay 드라이버는 컨테이너를 사용하기 위해 도커를 merged, upperdir, lowerdir 구조로 나눕니다. lowerdir는 도커의 이미지 레이어에 해당하고, upperdir는 컨테이너 레이어에 해당합니다.

다른 스토리지 드라이버와는 다르게 여러 계층의 이미지 레이어가 존재하는 것이 아니라, 여러 개의 이미지 레이어를 하나의 컨테이너 마운트 지점에서 통합하여 사용합니다.

overlay 드라이버를 사용하도록 도커 데몬을 실행시켜서 확인해보도록 하겠습니다.

sudo dockerd --storage-driver overlay

데몬을 실행하고, ubuntu:20.04 이미지를 받고, 컨테이너를 생성해보도록 하겠습니다. 생성하고 컨테이너 내부에서 overlayfile을 생성하고, Ctrl + P, Q 로 컨테이너를 빠져나옵니다.

이미지 레이어나 컨테이너 레이어는 /var/lib/docker/overlay 에 저장되며, 0f31953ba... 폴더가 ubuntu:20.04의 이미지 레이어입니다. 컨테이너를 생성하고 다시 해당 디렉토리를 살펴보면, 두 개의 폴더가 생성된 것을 확인할 수 있습니다.

-init이 붙은 디렉토리가 방금 생성한 컨테이너 레이어를 의미하고, -init이 붙지 않은 디렉토리는 변경점, 여기서는 overlayfile을 가지고 있는 레이어(최상위 레이어)를 의미합니다.

-init이 붙지 않은 디렉토리를 살펴보면 다음과 같이 구성되어 있습니다.

upper에는 컨테이너에서 발생한 변경 사항을 담고 있으며, 위에서 생성한 overlayfile이라는 변경 사항을 가지고 있기 때문에 upper 디렉토리에는 overlayfile 파일이 존재합니다.

그리고 lower-id는 이 레이어의 아래에 위치한 도커의 우분투 이미지 레이어의 ID를 담고 있습니다.

merged 디렉토리는 우분투 이미지의 lowerdir와 컨테이너 레이어에 해당하는 upperdir이 함께 마운트되어 최종적으로 컨테이너 내부에서 보여지는 contents를 담고 있습니다.

/var/lib/docker/overlay에 있는 다른 레이어 디렉토리들을 살펴보면, 구조를 이해하는데 도움이 될 것 같습니다. 글에서는 따로 언급하지 않았습니다 !

AUFS와 유사하게 overlay는 이미지에 존재하는 파일에 쓰기 작업을 수행할 때 컨테이너 레이어인 upperdir로 복사하여 사용합니다. 따라서 크기가 큰 파일에 쓰기 작업을 수행할 때는 upperdir로 복사하는 시간으로 인해서 작업에 지연이 생길 수 있습니다. 하지만, AUFS와는 다르게 계층화된 다중 레이어 구조가 아니기 때문에 복사할 파일을 찾는 과정이 AUFS보다 빠릅니다.

overlay2

이번에는 overlay2를 스토리지 드라이버로 설정해서 도커 데몬을 실행해보겠습니다. 보통 overlay2만 사용한다면 dockerd만 입력해도 overlay2로 설정됩니다. 하지만, overlay를 먼저 테스트했으면, /var/lib/docker에 overlay와 overlay2 폴더가 모두 존재하기 때문에 어떤 스토리지 드라이버를 선택해야될지 몰라서 데몬이 실행되지 않을 수 있습니다.

sudo dockerd --storage-driver overlay2

실행 후, 컨테이너를 생성하고 overlay2file을 만든 후에 Ctrl + P,Q로 컨테이너를 빠져나옵니다.

이번에는 /var/lib/docker/overlay2 디렉토리 내용을 살펴보겠습니다.

4개의 디렉토리가 존재하는 것을 볼 수 있습니다.

a6ad5982293e... 디렉토리는 ubuntu:20.04의 이미지 레이어이고, 5cfdc96...에서 -init이 붙은 디렉토리는 방금 생성한 컨테이너이고, 붙지 않은 디렉토리는 변경점(overlay2file)이 포함된 최상위 레이어입니다.

마지막으로 l 디렉토리는 심볼릭 링크로 레이어의 식별자를 포함하고 있습니다.

간단하게 ubuntu:20.04 이미지 레이어 디렉토리의 구성을 살펴보겠습니다.

committed, link 파일과 diff 폴더가 존재하고 있습니다. diff는 레이어의 contents(파일시스템)을 담고 있고, link는 이 레이어의 심볼링 링크를 담고 있습니다. l 디렉토리의 66WBM5K... 은 결국 이 레이어 디렉토리를 가리키고 있음을 볼 수 있습니다.

(committed의 정체는 아직 알아내지 못했습니다...)

다음으로 나머지 두 레이어 디렉토리 중에 -init을 포함하는 디렉토리를 살펴보면 다음과 같이 5개의 파일이 존재합니다.

이 디렉토리가 컨테이너를 생성할 때 생성된 컨테이너 레이어입니다.

lower 파일은 이 레이어의 바로 아래쪽에 위치하는 레이어의 식별자를 담고있습니다. l/66WBM5KPS... 을 출력하고 있으며, l 디렉토리를 참조해보면 이 링크는 a6ad5982293e... , 즉 우분투 이미지 레이어의 diff를 가리키고 있습니다.

diff는 마찬가지로 파일시스템(contents)를 포함합니다.

link는 현재 레이어 디렉토리의 심볼릭 링크 주소를 포함합니다. l 디렉토리를 참조하면 이 링크가 현재 레이어 디렉토리의 diff 폴더를 가리키고 있음을 확인할 수 있습니다.

work 디렉토리는 OverlayFS가 내부적으로 사용하는 디렉토리라고 합니다.(공식문서 참조)

이제 -init이 붙지 않은 디렉토리, 변경점을 포함하는 최상위 레이어를 확인해보겠습니다. 위와 마찬가지로 동일한 파일과 폴더가 존재합니다.

diff는 동일하게 현재 레이어의 파일시스템을 담고 있으며, 내부에는 컨테이너를 생성하고 만든 overlay2file이 있습니다.

link도 마찬가지로 이 레이어의 심볼릭 링크를 담고 있습니다.

lower는 이 레이어의 부모 레이어들의 심볼릭 링크 위치를 담고 있습니다. 이 레이어 아래에는 컨테이너 레이어와 이미지 레이어 두개가 존재하기 때문에 2개의 링크가 포함되어 있음을 알 수 있습니다.

2.4. Btrfs 드라이버

공식문서 : https://docs.docker.com/storage/storagedriver/btrfs-driver/

Btrfs는 리눅스 파일시스템 중의 하나로, SSD 최적화, 데이터 압축 등 다양한 기능을 제공합니다. Btrfs 드라이버는 Devicemapper나 AUFS, OverlayFS 와는 다르게 파일시스템을 별도로 구성하지 않으면 도커에서 사용할 수 없고, /var/lib/docker 디렉토리가 btrfs 파일시스템을 사용하는 공간에 마운트되어 있어야만 도커가 Btrfs를 스토리지 드라이버로 인식합니다. Btrfs는 리눅스 커널에 포함되어 있으므로 대부분의 리눅스 배포판에서 사용할 수 있습니다.

Btrfs 드라이버는 이미지와 컨테이너를 서브 볼륨과 스냅샷 단위로 관리합니다. 구조 자체는 AUFS, Devicemapper 등 다른 스토리지 드라이버와 유사합니다. 이미지에서 가장 아래에 있는 베이스 레이어가 서브 볼륨이 되고, 그 위에 쌓이는 레이어가 베이스 레이어에 대한 스냅샷으로 생성됩니다. 새로운 컨테이너가 생성되면 컨테이너 레이어가 이미지의 맨 위에 있는 스냅샷으로 생성됩니다. Btrfs는 Devicemapper와 달리 블록 단위가 아닌 파일 단위로 각 레이어를 저장하기 때문에 /var/lib/docker/btrfs 디렉토리에서 이를 확인해볼 수 있습니다.

Btffs 드라이버를 사용하는 컨테이너 내부에서 새로운 파일을 생성하는 것은 devicemapper와 마찬가지로 allocate-on-demand 작업에 의해서 일어납니다. 이미 존재하던 파일에 쓰기 작업을 수행할 때는 원본 파일을 보존하고 스냅샷에 새로운 공간을 할당하는 RoW 방식을 사용합니다.

Btrfs는 자체적으로 SSD에 최적화되어 있으며, 대체적으로 우수한 성능을 나타냅니다. 또한 리눅스에서 파일시스템이 제공하지 않는 여러 기능을 제공한다는 장점도 있습니다.

따로 직접 테스트해보지는 않았습니다 !

2.5. ZFS 드라이버

공식문서 : https://docs.docker.com/storage/storagedriver/zfs-driver/

ZFS는 Sun Microsystems(현재 Oracle)에서 개발했으며, Btrfs처럼 압축, 레플리카, 데이터 중복 제거 등 다양한 기능을 제공합니다. 그러나 ZFS는 라이센스 문제로 리눅스 커널에 기본적으로 탑재되어 있지 않기 때문에 별도의 설치 과정이 필요합니다. 이를 위해서 ZFS on Linux(ZoL)라는 프로젝트가 ZFS를 독립적으로 설치할 수 있도록 모듈을 제공합니다.

ZFS도 용어의 차이만 있을 뿐 다른 스토리지 드라이버와 유사한 구조를 갖고 있습니다. ZFS 드라이버를 사용하면 이미지와 컨테이너 레이어는 ZFS 파일시스템, ZFS 클론, ZFS 스냅샷으로 구분되어 관리됩니다. 이미지의 베이스 레이어가 ZFS 파일시스템이 되고, 이로부터 ZFS 스냅샷을 생성해 하나의 레이어를 구성합니다. 이 ZFS 스냅샷에서 ZFS 클론을 생성해 이를 컨테이너 레이어를 위한 공간으로 사용하고, ZFS 클론을 다시 스냅샷으로 만들면 다시 이미지 레이어로서 생성되고, 이전과 동일한 과정을 거쳐 하나의 이미지로 사용됩니다.

ZFS의 쓰기와 읽기 작업은 Btrfs와 유사하게 RoW를 사용합니다. 컨테이너 레이어에서 새로운 데이터를 쓸 때는 allocate-on-demand 작업에 의해 zpool로부터 새로운 블록을 할당받고, 쓰기 작업을 수행합니다. 이미 존재하던 파일에 쓰기 작업을 수행할 때는 컨테이너 레이어인 ZFS 클론에 zpool로부터 128KB 크기의 새로운 블록을 여러 개 할당받고 이 블록에 쓰기 작업을 수행합니다.

ZFS는 성능뿐 아니라 안정성에 초점의 두었으며, 앞서 언급한 것처럼 압축과 데이터 중복 제거 등 여러 기능을 제공합니다. 또한 ZFS는 ARC(Adaptive Replacement Cache)라고 하는 메모리 구조로 디스크 블록을 캐시하기 때문에 PaaS 환경에도 나쁘지 않은 스토리지입니다.

다만, ZFS는 결코 가벼운 파일시스템이 아닙니다. ZFS는 메모리를 상당히 소모하는 파일시스템이기 때문에 이것을 스토리지 드라이버로 사용하는 도커에서 많은 수의 컨테이너를 동시에 사용한다면 호스트의 리소스 사용량을 수시로 확인하는 것이 좋습니다.

profile
Data Engineer
post-custom-banner

0개의 댓글