Search

Docker: Storage Driver

Created
2021/05/27
Tags
Docker
Storage Driver
AUFS
Devicemapper
OverlayFS
Btrfs
ZFS

Subject

1. Storage Driver

이전 글에서 Container에 대해 읽어본대로, ContainerStorage를 제한하기 위해선 Docker Engine 자체 기능을 이용하지 않고 Storage Driver의 도움이 필요하다고 했다. 이는 ContainerStorage Driver파일 시스템을 기반으로 동작하기 때문이다. 그렇다면 A라는 Storage Driver를 기반으로 생성한 Container가 있을 때, Docker 데몬의 기본 동작을 B라는 Storage Driver로 바꾸게 되면 기존 A를 이용하던 Container들이 동작할 수 있을까? 각 Container는 서로 다른 파일 시스템을 기반으로 하므로 각 Container들이 사용하는 파일 시스템과 다른 환경이라면 동작하지 않는다. 이는 곧 Docker 데몬에 적용된 Storage Driver에 따라서 ContainerImage가 별도로 생성됨을 의미하는데, 실제로 ContainerImage를 생성하게 되면 각 Storage Driver에 맞는 디렉토리에 ContainerImage가 위치하게 된다.
예를 들어 AUFS를 이용하는 ContainerDevicemapper를 이용하는 Container가 있다고 해보면, AUFS의 경우엔 Container/var/lib/docker/aufs에 존재하고 Devicemapper의 경우엔 Container/var/lib/docker/devicemapper에 존재한다.
위 그림과 같이 container_storage_driver를 만들었을 때, Storage DriverOverlayFSoverlay2를 이용하고 있는 것을 볼 수 있다.
docker run -i -t --rm --privileged --pid=host justincormack/nsenter1
위 명령어를 이용하여 container_storage_driverLinuxkit 내부 어디에 위치하고 있는지 확인해보자. 현재 overlay2라는 Storage Driver를 이용하고 있기 때문에, 아래 그림에서 보이는 것처럼 /var/lib/docker/overlay2가 존재하는 것을 확인할 수 있다. 하지만 내부 어디를 둘러봐도 container_storage_driver에 해당하는 Digest 값을 찾을 수 없다. 자세히 둘러보면 l이라는 디렉토리엔 심볼릭 링크가 걸린 파일들만 다수 존재하고, 그 외의 디렉토리들은 무엇인지 정확히 알 수 없다.
위 그림에서 -init이 붙은 디렉토리가 방금 생성된 Container를 의미하며, -init이 붙지 않은 디렉토리가 실제 Container파일 시스템을 담고 있는 디렉토리이다.
이와 같은 상황을 이해하기 위해선 위 그림과 같이 이해했던 ContainerImage에 대해 조금 더 자세히 알아볼 필요가 있다.
실제로 Container 내부에서 Read, 새로운 파일에 대한 Write, 기존 파일에 대한 Write와 같은 작업이 일어나면 Storage Driver에 따라서 Copy-on-Write (CoW) 혹은 Redirect-on-Write (RoW)로 동작한다. 그리고 CoWRoW에서 사용되는 개념이 SnapshotSnapshot Pool이다.
기본적으로 SnapshotImmutable한 속성으로 Read-Only이다. 그리고 Snapshot PoolSnapshotRead / Write 시에 이용하는 공간인데, CoWRoW에 따라서 사용 방법이 다르다. 사용 중인 StorageSnapshot으로 만들게 되면 내부의 파일이 어느 위치에 무엇들이 있는지 목록으로 유지되는데, 이를 통해 파일에 대한 Tracing이 가능해진다. 이에 대한 구조는 아래 그림과 같다.
위와 같은 구조를 CoWRoW 모두 유지하고 있는데 이들은 Read 작업에 대해서는 파일 시스템 상의 파일에 접근하여 내용만 읽으면 되니 문제가 없다. 하지만 각각의 이름처럼 Write 작업에 대해서는 Snapshot Pool을 이용하는 방식이 각각 다르다. Storage DriverWrite 작업에 대해 CoW를 사용하든 RoW를 사용하든 원본 파일은 그대로 유지를 하되, 변경 사항에 대해서 기록할 수 있어야 하는 것이 중요하다.

1) CoW (Copy-on-Write)

CoWSnapshot으로 유지하고 있는 파일에 대해 Write 요청을 받게 되었을 때, 원본으로 유지 중인 파일을 복사하여 해당 파일에 대한 SnapshotSnapshot Pool에 두게 된다. 여기까지가 1번과 2번과정이고, 3번 과정이 곧 Write 요청을 받은 Snapshot에 대해서 변경 사항을 반영한 것이다.
이와 같은 과정은 이름 그대로 Copy를 기반으로 하기 때문에 원본 파일에 대한 복사를 수행할 때 Read / Write 각 1회가 일어나고, Write 요청에 대한 변경 사항 반영 시 Write 1회가 발생한다. 총 2회의 Write를 수행하기 때문에 이에 대한 Overhead가 있는 편이다. 또한 파일에 대한 변경은 완료가 된 상태로 유지되고 변경 사항 적용을 위한 추가적인 작업이 필요 없으므로, 작업에 대한 수행은 빠르다고 볼 수 있다. 즉, CoW는 성능에서 손해를 보지만 Container의 생성과 삭제 같은 작업은 빠르다고 볼 수 있다.

2) RoW (Redirect-on_Write)

RoWSnapshot으로 유지하고 있는 파일에 대해 Write 요청을 받게 되었을 때, 원본으로 유지 중인 파일을 변경할 수 없도록 Freeze하고 Snapshot Pool에 새로운 블록을 할당 받아 변경 사항을 기록하게 된다. Snapshot Pool에 유지하고 있는 블록에는 변경된 내역만 diff 파일로써 유지한다. RoW는 1회의 Write 작업만 이뤄지고 CoW처럼 전체 파일을 복사를 하는 것이 아니기 때문에, 성능면에서는 이득을 챙길 수 있다. 하지만 변경 사항에 대한 반영을 위해 해당 파일을 찾는데서 추가적인 시간이 요구되므로 Container의 생성과 삭제 같은 작업이 느리다고 볼 수 있다.

3) 결론

CoWRoWWrite에 대해 변경 사항을 기록하는 방식이 어떤 것이 더 좋고 나쁘다는 것이 없다. 만약 두 방법에 더 좋고 나쁜 것이 존재하면, Storage Driver는 획일화 되어 있을 것인데 실제로는 그렇지 않다. 그저 상황에 따라서 어떤 방식으로 동작하는 Storage Driver를 이용할지가 중요하다.
그리고 SnapshotImmutable이라는 점을 염두에 두고 위에서 CoWRoW에 대해서 보았듯, 원본 파일에 대한 Snapshot변경 사항에 대한 Snapshot이 각각 ImageContainer에 대응된다는 것을 눈치 챘을 것이다. Storage Driver 별로 각 Snapshot들을 지칭하는 용어가 조금 다를 수는 있지만, ImageLayer들이 원본 파일에 대한 Snapshot들이 되는 것이고, ContainerLayer변경 사항에 대한 Snapshot들이 되는 것이다. 따라서 실행 중인 ContainerImage로 만들면 기존에 유지 중인 모든 Snapshot들이 모여 하나의 Image가 생성되는 것이고, 이 때의 Snapshot들은 비로소 원본 파일에 대한 Snapshot이 되는 것이다.
ImageContainer가 어떤 식으로 만들어지는지 알아봤다. Storage Driver 마다 차이가 있겠지만 이들이 동작하는 방식은 전반적으로 Image에 해당하는 Layer들의 SnapshotContainer에 해당하는 Layer들의 Snapshot을 합쳐 Container가 실행되는 지점에 mount하는 것이다. 각 Storage Driver 별로 어떤 특성이 있고 어떤 형태로 Layer들을 유지하고 있는지 알아보자.
어떤 Storage Driver를 이용할 지는 Docker 데몬의 설정 값을 조작하여 변경할 수 있는데, 이 글에서는 Storage Driver를 변경하는 방법은 다루지 않는다. Docker 데몬 설정 값을 조작하는 방법은 Docker 글들 중에 가장 첫 번째 글에 소개 되어 있으니 이를 참고하자.

2. AUFS

1) 특징

1.
ubuntu에서 주로 이용했으며, Kernel에 포함되어 있지 않아 일부 환경에서는 사용할 수 없다.
2.
Docker에서 오랜 기간 사용되었고 안정성 측면에서 좋은 평가를 받는 Storage Driver이다.
3.
CoW 방식으로 Write를 수행하기 때문에 Container의 생성과 삭제에 대한 작업이 빨라 PaaS (Platform as a Service)에 적합한 Storage Driver이다.
4.
Host의 저장 공간 크기를 공유하여 Container 내부에서 사용할 수 있는 저장 공간은 Host와 동일하다.
5.
계층화된 ImageLayer들로 구성된다.

2) 구조

AUFS는 기존에 설명했던 ImageContainer의 구조와 굉장히 유사하다. Image로 존재하는 여러 Layer들은 /var/lib/docker/aufs/mnt에 존재하며, Container로 이용되는 Layer/var/lib/docker/aufs/diff에 존재한다. 이 때, Image가 존재하는 /var/lib/docker/aufs/mnt라는 디렉토리는 Union Mount Point로 이용되어 Container/var/lib/docker/aufs/diffmount되어 이용된다. mountContainerUnion Mount Point에 존재하는 Image들을 Read-Only로 사용하게 된다.
Container를 사용하면서 Read-Only로만 수행되는 파일에 대해서 변경을 해야할 때 AUFSCoW 방식으로 Write를 수행하기 때문에, 해당 파일 전체를 ContainerLayer로 복사하고 복사된 파일을 변경하는 식으로 Write를 수행한다. 파일에 대해서 변경할 때 원본 파일 전체를 ContainerLayer로 복사해야 하므로, 복사할 파일을 찾아내는 과정이 필요하다. 이 때 AUFS는 파일을 Image의 가장 위 쪽에 존재하는 Layer부터 아래 쪽으로 파일을 찾아낸다. 따라서 찾으려는 파일이 가장 아래 쪽 Layer에 존재한다면 Write에 걸리는 시간이 오래 걸릴 수 있으며, 더군다나 Write를 수행하려는 파일의 크기가 크다면 그만큼 복사하는데 시간이 더 걸릴 수 있다. 이에 대해선 파일을 변경하려고 할 때 최초 1회에 대해서만 파일을 복사하기 때문에, 해당 파일을 여러 차례 변경하여 Write할 때는 기존에 복사된 파일을 이용한다.

3. Devicemapper

1) 특징

1.
레드햇 계열의 Linux에서 사용하기 위해 만들어진 Storage Driver이다.
2.
CentOS와 같은 운영체제에서 주로 사용했지만, 성능이 좋지 않아 DeprecatedStorage Driver이다. 따라서 Docker Engine 버전 1.13.0 이전으로는 Devicemapper를, 이후로는 OverlayFS를 기본 Storage Driver로 이용한다.
3.
RoW 방식으로 Write를 수행하기 때문에 성능으로 이득을 볼 수 있지만, Container의 생성과 삭제에 대한 작업이 느린 편이다.
4.
Docker 데몬에서 ContainerStorage 제한에 대한 설정이 가능하다. 이 때 기존 Storage 제한 설정 값을 이용하고 있는 Container가 있다면 설정이 되지 않으니 Container를 삭제 후 설정해줘야 한다. 제한 값을 Container 전체적으로 적용하지 않고 개별적으로 적용하는 것도 가능한데, 이 때는 Docker 데몬 설정 값보다 옵션으로 기재하는 값이 더 커야 Container가 생성된다.
5.
계층화된 ImageLayer들로 구성된다.

2) 구조

Devicemapper를 이용하게 되면 Host의 일정 공간을 할당 받아 Storage Pool로 이용한다. data라는 디렉토리가 Storage Pool로 이용되는 공간이다. 이렇게 할당 받은 공간을 한 번에 다 쓰는 것은 아니고, ImageContainer를 이용할 때 Storage Pool로 부터 특정 크기의 공간을 블록 단위로 할당 받아 Layer를 저장하게 된다. 다른 Storage Driver들과는 달리 ImageLayerContainerLayer를 분리하여 이용하는 Driver는 아니다. 블록 단위로 할당받은 ImageContainerLayer들은 Storage Pool로부터 할당 받은 정보들만 기록되며, 이는 metadata라는 디렉토리에 저장된다.
Devicemapper가 이용하는 Storage Pool 위에는 Devicemapper의 프레임워크가 존재하고 이를 Snapshot으로써 이용한다. 초기에 생성된 해당 SnapshotImageContainerLayer들에게 공간을 할당해줄 수 있도록 만들어주고, 이를 이용하여 일정 공간을 할당 받게 되면 Image의 여러 Layer들이 계층적으로 생성된다. ImageLayer들까지 포함하여 다시 Snapshot으로 만든 뒤에는 ImageLayer에 대한 변경 사항을 만들어 낼 수 있도록 ContainerLayer를 두게 되고, 이는 ImageLayer들이 있는 곳에 mount 된다.
Write를 이용하여 변경 사항이 발생 했을 때는 RoW 방식으로 기록하기 때문에 Storage Pool로부터 필요한 만큼의 블록들을 할당 받아 변경 사항을 기록하게 된다. 이 때 변경하려는 원본 파일 전체를 복사하는 것이 아니라 수정하려는 부분에 대해서만 복사를 수행하므로 AUFS 보다 성능에서 이점을 갖기는 하지만 전체적인 동작 흐름을 보았듯이 Container 생성과 삭제가 빠른 편은 아니다.

4. OverlayFS

1) 특징

1.
레드햇 계열의 Linux에서 주로 사용되는 Storage Driver이다.
2.
CoW 방식으로 Write를 수행하기 때문에 Container의 생성과 삭제에 대한 작업이 빨라 PaaS (Platform as a Service)에 적합한 Storage Driver이다.
3.
CoW 방식을 이용한다는 점에서 AUFS와 비슷한 원리로 작동하지만 OverlayFS는 조금 더 간단한 구조로 사용되어 AUFS보다 성능이 더 좋다. (AUFS를 대체할 수 있는 차세대 파일 시스템으로 인식된다.)
4.
OverlayFS에는 overlayoverlay2로 나뉘는데, overlay2가 더 우수한 성능을 보여준다.
5.
Host의 저장 공간 크기를 공유하여 Container 내부에서 사용할 수 있는 저장 공간은 Host와 동일하다.
6.
ContainerStorage 제한에 대한 설정이 가능하다. 다만 방법이 까다로운데 간단히 요약하면, Storage DriverOverlayFS로 이용하면서 Docker에 대한 데이터가 xfs에 저장되어 있는 경우에 xfsproject quota라는 기능을 통해 ContainerStorage 제한이 가능하다.
7.
다른 Storage Driver들과 달리 단일화된 ImageLayer로 구성된다. 이 때문에 AUFS 보다 조금 더 나은 성능을 보인다.

2) 구조

/var/lib/docker/overlay 혹은 /var/lib/docker/overlay2라는 디렉토리를 살펴보면 -init이 붙은 디렉토리가 있고 그렇지 않은 디렉토리가 있는 것을 확인할 수 있다. 생성된 Container-init이 붙은 디렉토리에 해당되며, -init이 붙지 않은 디렉토리는 Container파일 시스템을 담고 있는 디렉토리이다.
파일 시스템을 담고 있는 디렉토리의 내부 구조는 lowerdir, upperdir, merged로 나뉘어 있다. lowerdirImage에 대한 Layer를 의미하고, upperdirContainerLayer를 의미한다. 이에 대해서 다른 Storage Driver와는 달리 ImageContainerLayer가 각각 단일 계층으로 존재하며, merged 디렉토리는 두 Layer에 해당하는 디렉토리의 mount 지점이 된다.
OverlayFSCoW 방식으로 Write를 수행하기 때문에 lowerdir의 파일에 대해서 변경 사항이 발생하게 되면 해당 파일을 ContainerLayer에 해당하는 upperdir로 복사하게 된다. 이 때 크기가 큰 파일을 복사한다면 그만큼 시간이 더 소요되겠지만, 다른 파일 시스템과 달리 단일 계층으로 Layer를 유지하고 있으므로 복사할 파일을 찾는데 시간 소요가 덜한 편이다. upperdir에서는 복사된 파일에 변경 사항을 기록하게 되고, upperdir의 변경된 파일들과 변경 사항이 발생하지 않은 lowerdir의 파일들이 각각 merged 디렉토리에 mount되어 동작하게 된다.

5. Btrfs

1) 특징

1.
Linux에서 사용할 수 있는 Storage DriverSSD 최적화, 데이터 압축 등 다양한 기능을 제공한다.
2.
Docker에서 기본적으로 지원하지 않기 때문에 별도로 파일 시스템을 구성해줘야 이용할 수 있다.
3.
파일 시스템이 구성되어 있더라도, /var/lib/docker 디렉토리가 Btrfs를 이용하는 공간에 mount 되어야 Storage Driver로 인식된다.
4.
RoW 방식으로 Write를 수행하기 때문에 성능으로 이득을 볼 수 있지만, Container의 생성과 삭제에 대한 작업이 느린 편이다.
5.
RoW 방식과 더불어, SSD에 최적화된 Storage Driver이므로 우수한 성능을 보인다.
6.
계층화된 ImageLayer들로 구성된다.

2) 구조

Btrfs라는 파일 시스템을 구성한 뒤 이를 /var/lib/dockermount하는 과정이 먼저 필요하다. /var/lib/dockerBtrfsmount되면, Storage DriverBtrfs를 이용할 수 있게 된다. BtrfsImageContainerLayer들을 SubvolumeSnapshot 단위로 관리한다.
ImageBase Layer가 곧 Btrfs파일 시스템이며, 이는 Subvolume으로 유지된다. ImageLayer들은 하위 Layer들을 이용하여 Snapshot의 형태로 유지된다. ContainerLayer 역시 하위 ImageLayer들을 이용하여 Snapshot 형태로 유지된다. 즉, ContainerLayerImage의 가장 상위에 있는 Layer에 대한 Snapshot으로 유지된다.
Btrfs 역시 RoW 방식으로 Write를 수행하므로 변경 사항을 기록할 때는 Snapshot 내부에 새로운 공간을 할당 받아 수행된다. RoW 방식이기 때문에 전체 파일이 복사되어 기록되는 것이 아니라, 변경 사항이 발생한 부분에 대한 복사만 이뤄지게 된다.

6. ZFS

1) 특징

1.
Sun Microsystems에서 개발하였으며, Btrfs처럼 다양한 기능을 제공하는 Storage Driver이다.
2.
라이센스 문제 때문에 Linux에 기본적으로 포함되어 있지 않아 별도로 구성해줘야 한다. (ZFS on Linux (ZoL)이라는 프로젝트를 이용하여 ZFS 설치할 때 도움을 받을 수 있다.)
3.
RoW 방식으로 Write를 수행하기 때문에 성능으로 이득을 볼 수 있지만, Container의 생성과 삭제에 대한 작업이 느린 편이다.
4.
비록 RoW 방식이라서 Container의 생성과 삭제에 대해 느릴 수 있지만, 성능 및 안정성에 초점을 두었다는 점과 Adaptive Replacement Cache라는 구조로 Disk의 블록을 Caching 한다는 점 덕분에 PaaS에서도 나쁘지 않은 Storage Driver이다. 하지만 그만큼 가벼운 Storage Driver가 아니므로 ZFS를 이용하여 다수의 Container를 유지할 때는 Container가 사용하는 Host 자원 관리에 굉장히 유의해야 한다.
5.
계층화된 ImageLayer들로 구성된다.

2) 구조

ZFS라는 파일 시스템을 구성한 뒤 이를 /var/lib/dockermount하는 과정이 먼저 필요하다. /var/lib/dockerZFSmount되면, zpool이라고 하는 ZFSStorage Pool을 이용할 수 있게 된다. ImageBase LayerZFS파일 시스템이 되고, 이를 동일 Layer에는 ZFS Snapshot이라는 Snapshot을 생성하게 된다. 상위 Layer를 기록할 때는 하위 LayerSnapshot을 복제하여 ZFS Clone을 생성하게 되고, 이에 대한 기록을 마치면 다시 ZFS Snapshot을 만드는 식으로 반복된다.
ZFS 역시 RoW 방식으로 Write를 수행하기 때문에 zpool이라는 Storage Pool로부터 변경 사항 기록을 위한 특정 크기의 공간을 블록 단위로 할당받게 된다. 블록 단위의 공간에는 전체 파일 복사되어 기록되는 것이 아니라 변경 사항이 발생한 부분에 대한 복사만 이뤄지므로 비교적 성능이 좋은 편이다.

7. Reference