ZFS 스토리지 드라이버
ZFS 스토리지 드라이버
ZFS는 차세대 파일시스템으로, 볼륨 관리, 스냅샷, 체크섬, 압축, 중복 제거, 복제 등 다양한 고급 스토리지 기술을 지원합니다. 원래 Sun Microsystems(현재 Oracle Corporation)에서 만들었고 CDDL 라이선스로 오픈소스화되었는데, CDDL과 GPL의 라이선스 비호환 문제 때문에 ZFS를 리눅스 메인라인 커널에 포함할 수는 없었어요. 다행히 ZFS On Linux(ZoL) 프로젝트가 아웃오브트리(out-of-tree) 커널 모듈과 사용자 공간 도구를 별도로 설치할 수 있게 제공하고 있습니다.
ZFS on Linux(ZoL) 포팅은 안정적이고 성숙해지고 있지만, 아직은 ZFS on Linux에 대한 충분한 경험이 없다면 zfs Docker 스토리지 드라이버를 프로덕션 환경에서 사용하는 것은 권장하지 않아요.
참고: Linux 플랫폼에는 ZFS의 FUSE 구현도 있지만 권장하지 않습니다. 네이티브 ZFS 드라이버(ZoL)가 더 많은 테스트를 거쳤고 성능도 좋으며 더 널리 사용됩니다. 이 문서의 나머지 내용은 네이티브 ZoL 포팅을 기준으로 합니다.
출처: 공식문서
본문
사전 요구 사항
- ZFS는 전용 블록 디바이스가 하나 이상 필요하며, 가능하면 SSD(solid-state drive)를 사용하는 것이 좋아요.
/var/lib/docker/디렉터리가 ZFS 포맷 파일시스템에 마운트되어 있어야 합니다.- 스토리지 드라이버를 변경하면 기존에 만든 모든 컨테이너를 로컬 시스템에서 접근할 수 없게 돼요.
docker save로 컨테이너를 저장하고, 기존 이미지를 Docker Hub나 프라이빗 레지스트리에 푸시해 두면 나중에 다시 만들 필요가 없겠죠.
참고:
dockerd와containerd가 서로 다른 마운트 네임스페이스에 있으므로MountFlags=slave를 사용할 필요가 없습니다.
Docker에 zfs 스토리지 드라이버 구성하기
-
Docker를 중지합니다.
-
/var/lib/docker/의 내용을/var/lib/docker.bk로 복사하고,/var/lib/docker/의 내용을 비웁니다.
$ sudo cp -au /var/lib/docker /var/lib/docker.bk
$ sudo rm -rf /var/lib/docker/*
- 전용 블록 디바이스에 새
zpool을 만들고/var/lib/docker/에 마운트합니다. 이 작업은 파괴적인 작업이므로 올바른 디바이스를 지정했는지 꼭 확인하세요. 아래 예시는 풀에 디바이스 두 개를 추가합니다.
$ sudo zpool create -f zpool-docker -m /var/lib/docker /dev/xvdf /dev/xvdg
이 명령은 zpool을 생성하고 이름을 zpool-docker로 지정해요. 이름은 표시용일 뿐이니 다른 이름을 써도 됩니다. zfs list로 풀이 제대로 생성되고 마운트되었는지 확인해 보세요.
$ sudo zfs list
NAME USED AVAIL REFER MOUNTPOINT
zpool-docker 55K 96.4G 19K /var/lib/docker
- Docker가
zfs를 사용하도록 구성합니다./etc/docker/daemon.json을 편집하고storage-driver를zfs로 설정하세요. 파일이 원래 비어 있었다면 이제 다음과 같은 모습이어야 합니다.
{
"storage-driver": "zfs"
}
저장하고 파일을 닫습니다.
- Docker를 시작하고
docker info로 스토리지 드라이버가zfs인지 확인합니다.
$ sudo docker info
Containers: 0
Running: 0
Paused: 0
Stopped: 0
Images: 0
Server Version: 17.03.1-ce
Storage Driver: zfs
Zpool: zpool-docker
Zpool Health: ONLINE
Parent Dataset: zpool-docker
Space Used By Parent: 249856
Space Available: 103498395648
Parent Quota: no
Compression: off
<...>
zfs 관리하기
실행 중인 디바이스의 용량 늘리기
zpool의 크기를 늘리려면 Docker 호스트에 전용 블록 디바이스를 추가한 다음, zpool add 명령으로 그 디바이스를 zpool에 추가하면 됩니다.
$ sudo zpool add zpool-docker /dev/xvdh
컨테이너 쓰기 가능 스토리지 할당량 제한하기
이미지/데이터셋 단위로 할당량을 적용하고 싶다면 size 스토리지 옵션을 설정해서 단일 컨테이너가 쓰기 가능 레이어에 사용할 수 있는 공간을 제한할 수 있어요.
/etc/docker/daemon.json을 편집하고 다음 내용을 추가하세요.
{
"storage-driver": "zfs",
"storage-opts": ["size=256M"]
}
각 스토리지 드라이버의 모든 스토리지 옵션은 daemon 참조 문서에서 확인할 수 있습니다.
저장하고 파일을 닫은 다음 Docker를 재시작하세요.
zfs 스토리지 드라이버의 작동 방식
ZFS는 다음 객체들을 사용해요.
- filesystems: 씬 프로비저닝(thin provisioning) 방식이며,
zpool에서 필요할 때 공간을 할당받습니다. - snapshots: 파일시스템의 특정 시점 복사본으로, 읽기 전용이고 공간 효율적입니다.
- clones: 스냅샷의 읽기-쓰기 복사본입니다. 이전 레이어와의 차이점을 저장하는 데 사용돼요.
클론을 만드는 과정은 다음과 같아요.
- 파일시스템에서 읽기 전용 스냅샷을 생성합니다.
- 스냅샷에서 쓰기 가능한 클론을 생성합니다. 이 클론에는 상위 레이어와의 모든 차이점이 포함됩니다.
filesystems, snapshots, clones 모두 기본 zpool에서 공간을 할당받습니다.
디스크 상의 이미지 및 컨테이너 레이어
실행 중인 각 컨테이너의 통합 파일시스템은 /var/lib/docker/zfs/graph/의 마운트 지점에 마운트됩니다. 통합 파일시스템이 어떻게 구성되는지 계속해서 설명할게요.
이미지 레이어링과 공유
이미지의 베이스 레이어는 ZFS 파일시스템이에요. 각 하위 레이어는 바로 아래 레이어의 ZFS 스냅샷을 기반으로 한 ZFS 클론입니다. 컨테이너는 자신이 생성된 이미지의 최상위 레이어에 대한 ZFS 스냅샷을 기반으로 한 ZFS 클론이에요.
아래 다이어그램은 두 개의 레이어로 구성된 이미지를 기반으로 실행 중인 컨테이너가 어떻게 구성되는지 보여줍니다.
컨테이너를 시작하면 다음 단계가 순서대로 진행됩니다.
-
이미지의 베이스 레이어는 Docker 호스트에 ZFS 파일시스템으로 존재합니다.
-
추가 이미지 레이어는 바로 아래에 있는 이미지 레이어를 호스팅하는 데이터셋의 클론입니다.
다이어그램에서 "Layer 1"은 베이스 레이어의 ZFS 스냅샷을 찍은 다음 그 스냅샷에서 클론을 생성하여 추가됩니다. 클론은 쓰기 가능하고 zpool에서 필요에 따라 공간을 소비합니다. 스냅샷은 읽기 전용이므로 베이스 레이어를 불변 객체로 유지해 주죠.
- 컨테이너가 시작되면 이미지 위에 쓰기 가능한 레이어가 추가됩니다.
다이어그램에서 컨테이너의 읽기-쓰기 레이어는 이미지의 최상위 레이어(Layer 1)의 스냅샷을 만들고 그 스냅샷에서 클론을 생성하여 만들어집니다.
- 컨테이너가 쓰기 가능한 레이어의 내용을 수정하면 변경된 블록에 대해 공간이 할당됩니다. 기본적으로 이 블록 크기는 128k입니다.
zfs에서 컨테이너 읽기 및 쓰기 작동 방식
파일 읽기
각 컨테이너의 쓰기 가능 레이어는 ZFS 클론이며, 생성된 원본 데이터셋(상위 레이어의 스냅샷들)과 모든 데이터를 공유합니다. 읽기 작업은 데이터가 깊은 레이어에 있더라도 빠르게 수행돼요. 아래 다이어그램은 블록 공유가 어떻게 작동하는지 보여줍니다.
파일 쓰기
새 파일 쓰기: 기본 zpool에서 필요에 따라 공간이 할당되고 블록이 컨테이너의 쓰기 가능 레이어에 직접 기록됩니다.
기존 파일 수정: 변경된 블록에 대해서만 공간이 할당되며, 해당 블록은 copy-on-write(CoW) 전략으로 컨테이너의 쓰기 가능 레이어에 기록됩니다. 이 방식은 레이어 크기를 최소화하고 쓰기 성능을 높여줘요.
파일 또는 디렉터리 삭제:
- 하위 레이어에 존재하는 파일이나 디렉터리를 삭제하면, ZFS 드라이버는 해당 파일이나 디렉터리가 하위 읽기 전용 레이어에 여전히 존재하더라도 컨테이너의 쓰기 가능 레이어에서 그 존재를 가려버립니다(masking).
- 컨테이너의 쓰기 가능 레이어 내에서 파일이나 디렉터리를 생성한 다음 삭제하면, 해당 블록은
zpool에 반환됩니다.
ZFS와 Docker 성능
zfs 스토리지 드라이버를 사용하는 Docker의 성능에 영향을 주는 요인은 여러 가지가 있어요.
-
메모리: 메모리는 ZFS 성능에 큰 영향을 줍니다. ZFS는 원래 대용량 메모리를 갖춘 대규모 엔터프라이즈급 서버를 위해 설계되었어요.
-
ZFS 기능: ZFS에는 중복 제거(de-duplication) 기능이 포함되어 있어요. 이 기능을 사용하면 디스크 공간을 절약할 수 있지만 메모리를 많이 소비합니다. SAN, NAS 또는 기타 하드웨어 RAID 기술을 사용하지 않는 한, Docker와 함께 사용하는
zpool에서는 이 기능을 비활성화하는 것이 좋습니다. -
ZFS 캐싱: ZFS는 ARC(adaptive replacement cache)라는 메모리 구조에 디스크 블록을 캐시합니다. ZFS의 Single Copy ARC 기능은 캐시된 블록 하나를 여러 클론이 공유할 수 있게 해줘요. 이 기능 덕분에 여러 실행 중인 컨테이너가 캐시된 블록의 단일 복사본을 공유할 수 있습니다. 그래서 ZFS는 PaaS 및 기타 고밀도 사용 사례에 좋은 선택이 됩니다.
-
단편화(Fragmentation): 단편화는 ZFS와 같은 copy-on-write 파일시스템에서 자연스럽게 발생하는 부산물이에요. ZFS는 128k의 작은 블록 크기를 사용하여 이를 완화합니다. ZFS 인텐트 로그(ZIL)와 쓰기 병합(지연 쓰기)도 단편화를 줄이는 데 도움이 됩니다.
zpool status로 단편화를 모니터링할 수 있지만, 파일시스템을 다시 포맷하고 복원하지 않고는 ZFS를 조각 모음할 방법이 없어요. -
Linux용 네이티브 ZFS 드라이버 사용: ZFS FUSE 구현은 성능이 좋지 않아 권장하지 않습니다.
성능 모범 사례
-
빠른 스토리지 사용: SSD는 회전식 디스크보다 읽기와 쓰기가 훨씬 빠릅니다.
-
쓰기 집약적 워크로드에는 볼륨 사용: 볼륨은 쓰기 집약적 워크로드에서 가장 좋고 예측 가능한 성능을 제공해요. 스토리지 드라이버를 우회하기 때문에 씬 프로비저닝과 copy-on-write가 도입하는 잠재적 오버헤드가 없기 때문이죠. 볼륨은 컨테이너 간 데이터 공유, 실행 중인 컨테이너가 없어도 데이터 유지 등 다른 이점도 있습니다.