Introduction

컨테이너 포스트에서 OverlayFS를 "레이어들을 하나의 파일 시스템처럼 보이게 만드는 유니언 파일 시스템"이라고 소개했습니다. 이 글에서는 한 걸음 더 들어가, OverlayFS가 내부적으로 어떻게 동작하는지 — 읽기·쓰기·삭제 각각의 I/O 경로, Copy-up 메커니즘의 원자성 보장, Whiteout과 Opaque Directory로 삭제를 표현하는 방법, 그리고 Docker의 overlay2 드라이버가 이미지 레이어를 OverlayFS에 매핑하는 구조까지를 살펴봅니다.

유니언 파일 시스템이란

유니언 파일 시스템union filesystem이란 여러 디렉토리 트리를 하나의 디렉토리로 합쳐서 보여주는 파일 시스템이다. 물리적으로는 별개의 디렉토리에 저장된 파일들이, 하나의 마운트 포인트에서 단일 디렉토리 구조처럼 보인다.

이 개념은 컨테이너 이전부터 존재했다. 리눅스 LiveCD가 읽기 전용 CD-ROM 위에 RAM 기반 쓰기 레이어를 올려 "설치 없이 사용할 수 있는 데스크톱"을 제공할 때, 유니언 파일 시스템이 핵심 기술이었다. 그 역사를 간단히 살펴보면 다음과 같다.

시기기술특징
2004UnionFS최초의 리눅스 유니언 파일 시스템. 스토니브룩 대학에서 개발
2006AUFS1UnionFS의 완전 재작성. 초기 Docker의 기본 스토리지 드라이버. 그러나 코드 복잡성 때문에 리눅스 메인라인 커널에 병합되지 못함
2014OverlayFS2구조적 간결성을 목표로 설계. Linux 3.18에 메인라인 커널로 병합되어 사실상 표준이 됨

AUFS는 기능적으로 풍부했지만, 메인라인 커널 밖의 아웃오브트리out-of-tree 패치로만 유지되어 커널 업데이트마다 수동으로 패치를 적용해야 하는 부담이 있었다. OverlayFS는 설계를 대폭 단순화하여 커널 메인라인에 포함되는 데 성공했고, 이후 Docker와 containerd의 기본 스토리지 드라이버로 자리잡았다.

네 개의 디렉토리: OverlayFS의 구성 요소

OverlayFS를 마운트하려면 네 개의 디렉토리를 지정해야 한다.

mount -t overlay overlay \
    -o lowerdir=/lower1:/lower2,upperdir=/upper,workdir=/work \
    /merged
디렉토리권한역할
lowerdir읽기 전용베이스 파일 시스템 트리. 콜론(:)으로 여러 레이어를 쌓을 수 있으며, 왼쪽이 상위, 오른쪽이 하위 레이어
upperdir읽기/쓰기모든 변경(생성, 수정, 삭제)이 물리적으로 기록되는 공간
workdir내부 전용Copy-up의 원자성을 보장하기 위한 임시 작업 공간. upperdir과 같은 파일 시스템에 존재해야 함
merged통합 뷰사용자와 애플리케이션이 보는 최종 결과. upper + lower가 합쳐진 단일 디렉토리

사용자가 merged 디렉토리에서 파일을 읽거나 쓰면, OverlayFS 커널 모듈이 요청의 종류에 따라 upper 또는 lower로 I/O를 라우팅한다. 이 라우팅 규칙이 OverlayFS의 핵심이다.

I/O 연산별 동작 원리

OverlayFS에서 파일 I/O가 어떻게 처리되는지를 연산별로 살펴보자. 읽기 전용인 lowerdir의 불변성immutability을 유지하면서 사용자에게 완전한 읽기/쓰기 환경을 제공하는 것이 설계의 핵심 과제다.

읽기: 위에서 아래로 탐색

파일을 읽을 때 OverlayFS는 upperdir부터 탐색한다. upperdir에 파일이 있으면 그 파일을 반환하고, 없으면 lowerdir로 내려가서 찾는다. 여러 lowerdir이 쌓여 있으면 위쪽 레이어부터 순서대로 탐색한다.

merged에서 읽기 요청upperdir에 있나?{있음upperdir에서 읽기없음lowerdir에서 순차 탐색\text{merged에서 읽기 요청} \xrightarrow{\text{upperdir에 있나?}} \begin{cases} \text{있음} \rightarrow \text{upperdir에서 읽기} \\ \text{없음} \rightarrow \text{lowerdir에서 순차 탐색} \end{cases}

upperdir에 파일이 존재하면 lowerdir에 동일한 이름의 원본이 있더라도 완전히 가려진다. 이것이 "overlay(덮어씌우기)"라는 이름의 유래다.

생성: 항상 upperdir에 직접 기록

새 파일을 만들 때는 lowerdir을 거치지 않고 upperdir에 바로 생성한다. lowerdir의 존재 여부와 무관하게 동작하므로 가장 단순한 경로다.

수정: Copy-up

lowerdir에만 존재하는 파일을 수정하려 하면 OverlayFS는 Copy-up3을 수행한다. 원본 파일을 lowerdir에서 upperdir로 통째로 복사한 뒤, upperdir의 복사본에 수정을 적용하는 것이다.

Copy-up 과정에서 주목할 점은 workdir의 역할이다. 파일을 lowerdir에서 upperdir로 직접 복사하면, 복사 도중 시스템이 중단되었을 때 불완전한 파일이 upperdir에 남을 수 있다. 이를 방지하기 위해 OverlayFS는 먼저 workdir에 임시 파일을 생성하고, 복사가 완료된 후 rename()4 시스템 호출로 원자적으로 upperdir에 이동시킨다. rename()은 같은 파일 시스템 내에서 원자적 연산이 보장되므로, Copy-up 도중 시스템이 중단되더라도 upperdir에는 완전한 파일만 존재하게 된다.

Copy-up은 최초 1회만 발생한다. 이후의 모든 읽기와 쓰기는 upperdir의 파일에 직접 접근하므로, 네이티브 파일 시스템과 동일한 성능을 달성한다. 다만 최초 쓰기 시 파일 전체를 복사해야 하므로, 대용량 파일의 경우 첫 번째 쓰기에 상당한 지연이 발생할 수 있다.

metacopy: 대용량 파일의 Copy-up 최적화

Linux 4.19부터 도입된 metacopy=on 마운트 옵션을 사용하면, chmod이나 chown 같은 메타데이터 변경 시 파일 데이터를 복사하지 않고 메타데이터만 Copy-up한다. 실제 데이터 복사는 파일에 쓰기가 발생하는 시점까지 지연된다.

삭제: Whiteout 파일

읽기 전용인 lowerdir의 파일은 물리적으로 삭제할 수 없다. 그렇다면 사용자가 rm file_c를 실행했을 때 OverlayFS는 어떻게 "삭제된 것처럼" 보이게 할까?

답은 Whiteout5 파일이다. lowerdir에 있는 파일을 삭제하면, OverlayFS는 upperdir에 .wh.<파일명> 형태의 특수 파일을 생성한다. 디렉토리를 탐색할 때 이 Whiteout 파일이 발견되면, lowerdir에 동일한 이름의 원본이 있어도 사용자에게 보이지 않도록 마스킹한다.

Whiteout 파일의 구현은 전통적으로 디바이스 번호가 0/0인 문자 디바이스 노드로 이루어졌다. 최근 커널에서는 크기가 0인 일반 파일에 trusted.overlay.whiteout 확장 속성을 부여하는 대안적 방식도 지원한다. 이 방식은 특수 디바이스 노드 생성에 root 권한이 필요하지 않아, rootless 컨테이너 환경에서 유용하다.

디렉토리 삭제: Opaque Directory

Whiteout이 개별 파일의 삭제를 표현한다면, Opaque Directory6는 디렉토리 전체의 삭제를 표현한다. lowerdir에 있는 디렉토리를 삭제하고 같은 이름으로 새 디렉토리를 만들면, upperdir의 새 디렉토리에 trusted.overlay.opaque 확장 속성이 "y"로 설정된다. 이 속성이 감지되면 커널은 해당 디렉토리에 대해 lowerdir로의 탐색을 차단하고 병합을 수행하지 않는다.

Opaque Directory는 rm -rf mydir && mkdir mydir 같은 패턴에서 자동으로 적용된다. lowerdir의 디렉토리 안에 수천 개의 파일이 있더라도, Whiteout을 하나하나 생성하는 대신 디렉토리 하나에 속성 하나를 설정하는 것으로 충분하다.

I/O 연산 요약

연산upperdir에 있을 때lowerdir에만 있을 때
읽기upperdir에서 직접 읽기lowerdir에서 읽기
쓰기upperdir에 직접 쓰기Copy-up 후 upperdir에 쓰기
생성upperdir에 직접 생성
삭제upperdir에서 직접 삭제Whiteout 생성
디렉토리 삭제 + 재생성upperdir에서 직접 처리Opaque Directory 생성

Docker overlay2 드라이버: 이미지 레이어와 OverlayFS의 매핑

컨테이너에서 살펴본 이미지 레이어 구조가 실제로 OverlayFS에 어떻게 매핑되는지를 살펴보자. Docker는 overlay27 스토리지 드라이버를 통해 이미지의 각 레이어를 OverlayFS의 lowerdir로 매핑한다.

디렉토리 구조

Docker가 이미지를 풀하면 /var/lib/docker/overlay2/ 아래에 레이어별 디렉토리가 생성된다.

컨테이너 실행 시의 마운트

docker run nginx를 실행하면 Docker는 다음과 같은 OverlayFS 마운트를 수행한다.

mount -t overlay overlay \
    -o lowerdir=l/GHIJKL:l/ABCDEF,upperdir=ccc.../diff,workdir=ccc.../work \
    ccc.../merged

여기서 l/ 디렉토리의 심볼릭 링크는 중요한 실용적 이유가 있다. mount 명령의 옵션 문자열에는 길이 제한(페이지 크기, 보통 4096바이트)이 있는데, 이미지 레이어가 많아지면 lowerdir 경로가 이 제한을 초과할 수 있다. 짧은 심볼릭 링크로 경로를 축약하여 이 문제를 회피한다.

레이어 공유의 실제

동일한 이미지를 사용하는 컨테이너 여러 개를 실행하면, 이미지 레이어(lowerdir)는 공유되고 각 컨테이너마다 별도의 upperdir만 생성된다. 이것이 컨테이너에서 설명한 "레이어 공유"의 실체다.

nginx 이미지가 150MB라고 하면, 컨테이너 100개를 실행해도 디스크에 저장되는 이미지 데이터는 150MB 하나뿐이다. 각 컨테이너의 upperdir에는 런타임에 변경된 파일만 저장되므로, 실질적 추가 디스크 사용량은 미미하다.

성능 특성

OverlayFS의 성능은 I/O 패턴에 따라 크게 달라진다.

읽기 성능: upperdir에 있는 파일을 읽을 때는 네이티브 파일 시스템과 동일한 성능이다. lowerdir에서 읽을 때도 한 번의 추가 경로 탐색만 필요하므로 오버헤드가 매우 작다.

쓰기 성능: Copy-up이 발생하는 최초 쓰기는 파일 크기에 비례하는 지연이 발생한다. 그러나 Copy-up 이후에는 네이티브 성능과 동일하다. 이를 "최초 1회 페널티first-write penalty"라 부른다.

디렉토리 탐색: lsreaddir() 시 OverlayFS는 upperdir과 lowerdir의 내용을 병합해야 한다. 중복 항목을 제거하고 Whiteout을 처리하는 과정에서 오버헤드가 발생하며, 항목이 많은 디렉토리일수록 비용이 커진다.

다른 스토리지 드라이버와의 비교

OverlayFS (overlay2)AUFSdevicemapperBtrfs
커널 메인라인Linux 3.18+아웃오브트리Linux 2.6+Linux 3.10+
Copy-up 단위파일 전체파일 전체블록 (64KB)블록 (4KB)
레이어 수 제한128 (커널 제한)127제한 없음제한 없음
메모리 사용낮음높음 (inode 캐시)중간중간
순차 읽기우수우수우수우수
최초 쓰기파일 크기에 비례파일 크기에 비례블록 크기에 비례블록 크기에 비례
Docker 기본Ubuntu, RHEL 8+구 UbuntuRHEL 7 (구)

devicemapper와 Btrfs는 블록 단위로 Copy-on-Write를 수행하므로 대용량 파일의 일부만 수정할 때 유리하지만, 설정이 복잡하고 메모리 사용량이 높다. OverlayFS는 파일 단위 Copy-up이라는 제약이 있지만, 커널 메인라인 지원, 낮은 메모리 사용량, 단순한 구조 덕분에 컨테이너 환경의 사실상 표준으로 자리잡았다.

고급 마운트 옵션

OverlayFS의 초기 구현에는 몇 가지 POSIX 비호환 동작이 있었다. 커널 커뮤니티는 이를 해결하기 위해 다양한 마운트 옵션을 추가해왔다.

redirect_dir: 디렉토리 이름 변경

기본 OverlayFS에서는 lowerdir에 있는 디렉토리의 이름을 변경(rename())하면 EXDEV 에러가 발생한다. 디렉토리 내부의 모든 파일을 재귀적으로 Copy-up해야 하기 때문이다. redirect_dir=on 옵션을 사용하면, upperdir의 새 경로에 빈 디렉토리를 만들고 trusted.overlay.redirect 확장 속성으로 원본 lowerdir 경로를 기록하여, 실제 복사 없이 즉각적인 이름 변경을 지원한다.

index: 하드 링크 보존

lowerdir에서 하드 링크로 연결된 두 파일(file_a, file_b)이 있을 때, file_a에 쓰기가 발생하면 Copy-up으로 새 inode가 생성되어 하드 링크가 끊어진다. index=on 옵션은 Copy-up 시 원본 inode의 식별자를 인덱스에 기록하여, file_b의 Copy-up 때 기존 상위 파일에 하드 링크를 연결함으로써 하드 링크 관계를 보존한다.

metacopy: 메타데이터 전용 Copy-up

앞서 소개한 대로, chmod이나 chown 같은 메타데이터 변경 시 파일 데이터를 복사하지 않고 메타데이터만 Copy-up한다. 수 GB짜리 파일의 소유자만 변경하는 경우 극적인 성능 향상을 제공한다.


출처

  • Szeredi, M. Linux kernel documentation, Documentation/filesystems/overlayfs.rst.
  • Merkel, D. (2014) 'Docker: Lightweight Linux Containers for Consistent Development and Deployment', Linux Journal, 2014(239).
  • Docker Documentation, 'Use the OverlayFS storage driver' — https://docs.docker.com/storage/storagedriver/overlayfs-driver/.
  • Arch Linux Wiki, 'Overlay filesystem' — https://wiki.archlinux.org/title/Overlay_filesystem.
  • Linux kernel source v6.19 — fs/overlayfs/.
  • Silberschatz, A., Galvin, P. B., and Gagne, G. (2018) Operating System Concepts. 10th ed. Hoboken, NJ: Wiley.

Footnotes

  1. AUFS(Advanced Multi-Layered Unification Filesystem)는 Junjiro Okajima가 UnionFS를 완전히 재작성하여 2006년에 발표한 유니언 파일 시스템이다. 초기 Docker(2013년)의 우분투/데비안 환경에서 기본 스토리지 드라이버로 사용되었으나, 코드 복잡성으로 리눅스 메인라인 커널 병합이 거절되어 아웃오브트리 패치로만 유지되었다.

  2. OverlayFS는 Miklos Szeredi(Novell/Red Hat)가 개발하여 2014년 Linux 3.18에 메인라인 커널로 병합된 유니언 파일 시스템이다. AUFS 대비 구조가 단순하여 커널 메인라인 병합에 성공했으며, fs/overlayfs/ 디렉토리에 구현되어 있다. — Documentation/filesystems/overlayfs.rst.

  3. Copy-up은 lowerdir의 파일을 upperdir로 복사하는 OverlayFS의 핵심 메커니즘이다. 파일의 데이터, 메타데이터(소유자, 권한, 타임스탬프), 확장 속성(xattr)이 모두 복사된다. workdir을 활용한 원자적 rename()으로 중간 상태가 노출되지 않도록 보장한다. — fs/overlayfs/copy_up.c.

  4. 같은 파일 시스템 내에서의 rename() 시스템 호출은 POSIX 표준에 의해 원자적 연산이 보장된다. 즉, 시스템 관점에서 rename은 "이전 경로에 있다"와 "새 경로에 있다" 두 상태만 존재하며, 중간 상태는 관측되지 않는다. OverlayFS는 이 속성을 활용하여 Copy-up의 원자성을 보장한다. — POSIX.1-2017, §rename().

  5. Whiteout은 OverlayFS에서 lowerdir 파일의 삭제를 표현하는 특수 마커 객체다. 전통적 구현은 디바이스 번호 0/0의 문자 디바이스 노드이며, 최신 커널에서는 trusted.overlay.whiteout 확장 속성을 가진 일반 파일도 지원한다. Whiteout 자체는 사용자에게 보이지 않는다. — Documentation/filesystems/overlayfs.rst, §Whiteouts and opaque directories.

  6. Opaque Directory는 upperdir의 디렉토리에 trusted.overlay.opaque 확장 속성을 "y"로 설정하여, 해당 디렉토리에 대한 lowerdir 탐색을 차단하는 메커니즘이다. 디렉토리 삭제 후 재생성 시 자동으로 적용되며, 개별 Whiteout을 대량 생성하는 것보다 효율적이다. — Documentation/filesystems/overlayfs.rst, §Whiteouts and opaque directories.

  7. Docker의 overlay2 스토리지 드라이버는 OverlayFS의 다중 lowerdir 기능(Linux 4.0+)을 활용하여, 각 이미지 레이어를 별도의 lowerdir로 매핑한다. 이전의 overlay 드라이버는 lowerdir을 하나만 지원하여 하드 링크로 레이어를 연결해야 했다. Docker 18.09+에서 overlay2가 기본이다. — Docker Documentation, 'Use the OverlayFS storage driver'.