Theme:

Docker 이미지는 여러 레이어로 구성된다고 합니다. 같은 이미지로 10개의 컨테이너를 실행하면, 이미지 데이터가 10번 복사되는 걸까요? 그렇다면 디스크가 금방 차야 하는데, 왜 괜찮을까요?

Union Filesystem이란

Union Filesystem(UnionFS)은 여러 디렉토리를 하나로 합쳐서 보여주는 파일 시스템입니다. Docker는 이 기술을 활용하여 이미지 레이어를 효율적으로 관리합니다.

핵심 아이디어:

  • 여러 층(layer)의 파일을 겹쳐서 하나의 파일 시스템처럼 보이게 합니다
  • 아래 층(lower)은 읽기 전용, 위 층(upper)은 읽기/쓰기 가능
  • 수정할 때는 원본을 건드리지 않고 위 층에 복사본을 만듭니다 (Copy-on-Write)

OverlayFS의 구조

Docker의 기본 스토리지 드라이버인 overlay2는 Linux 커널의 OverlayFS를 사용합니다.

PLAINTEXT
┌─────────────────────────────┐
│        merged (통합 뷰)      │ ← 컨테이너가 보는 파일 시스템
├─────────────────────────────┤
│        upper (쓰기 레이어)    │ ← 컨테이너의 변경사항
├─────────────────────────────┤
│        lower 3 (읽기 전용)   │ ← RUN npm run build
├─────────────────────────────┤
│        lower 2 (읽기 전용)   │ ← RUN npm ci
├─────────────────────────────┤
│        lower 1 (읽기 전용)   │ ← FROM node:20-alpine
└─────────────────────────────┘

각 층의 역할

  • lower layer: 이미지 레이어. 읽기 전용. 여러 컨테이너가 공유합니다.
  • upper layer: 컨테이너의 쓰기 레이어. 컨테이너 삭제 시 사라집니다.
  • merged: lower + upper를 합친 통합 뷰. 컨테이너가 실제로 보는 파일 시스템입니다.
  • work: OverlayFS 내부 작업용 디렉토리 (atomic operation에 사용).

실제 확인해보기

BASH
# 컨테이너의 OverlayFS 정보 확인
docker inspect mycontainer --format '{{.GraphDriver.Data.MergedDir}}'
docker inspect mycontainer --format '{{.GraphDriver.Data.UpperDir}}'
docker inspect mycontainer --format '{{.GraphDriver.Data.LowerDir}}'

# 실제 디렉토리 확인 (root 권한 필요)
sudo ls /var/lib/docker/overlay2/

Copy-on-Write (CoW)

CoW는 OverlayFS의 핵심 메커니즘입니다.

파일 읽기

PLAINTEXT
요청: /app/config.json 읽기

merged (통합 뷰)

  ├── upper: config.json 없음 → 다음 레이어 확인

  └── lower: config.json 발견! → 이 파일을 반환

lower layer의 파일을 그대로 읽습니다. 복사가 발생하지 않습니다.

파일 수정

PLAINTEXT
요청: /app/config.json 수정

1단계: lower에서 upper로 파일 복사 (copy-up)
upper: config.json (복사본 생성됨)
lower: config.json (원본 그대로)

2단계: upper의 복사본 수정
upper: config.json (수정됨) ← 이 파일이 보임
lower: config.json (원본)   ← 가려져서 안 보임

이것이 Copy-on-Write입니다. 쓰기가 발생할 때만 복사하므로 효율적입니다.

파일 삭제

PLAINTEXT
요청: /app/old-file.txt 삭제

upper: .wh.old-file.txt (whiteout 파일 생성)
lower: old-file.txt (원본 그대로, 하지만 가려짐)

merged에서 old-file.txt가 보이지 않음

lower layer는 읽기 전용이므로 직접 삭제할 수 없습니다. 대신 upper layer에 whiteout 파일(.wh. 접두사)을 만들어 해당 파일을 가립니다.

디렉토리 삭제 (opaque whiteout)

PLAINTEXT
요청: /app/cache/ 디렉토리 삭제

upper: cache/.wh..wh..opq (opaque whiteout 생성)

lower의 cache/ 내용물이 전부 가려짐

이미지 레이어의 공유

같은 이미지를 사용하는 컨테이너들은 lower layer를 공유합니다.

PLAINTEXT
┌── 컨테이너 A ──┐  ┌── 컨테이너 B ──┐  ┌── 컨테이너 C ──┐
│   upper A      │  │   upper B      │  │   upper C      │
│   (각자의     │  │   (각자의     │  │   (각자의     │
│    변경사항)   │  │    변경사항)   │  │    변경사항)   │
├────────────────┤  ├────────────────┤  ├────────────────┤
│                                                        │
│              공유되는 lower layers                       │
│              (이미지 레이어 — 읽기 전용)                  │
│                                                        │
└────────────────────────────────────────────────────────┘
BASH
# 이미지 레이어 확인
docker image inspect nginx:1.25-alpine --format '{{.RootFS.Layers}}'
# [sha256:abc... sha256:def... sha256:ghi...]

# 디스크 사용량 확인
docker system df
# TYPE          TOTAL   ACTIVE   SIZE      RECLAIMABLE
# Images        5       3        1.2GB     400MB (33%)
# Containers    3       3        25MB      0B (0%)
# Local Volumes 4       2        500MB     200MB (40%)

# 상세 디스크 사용량
docker system df -v

Docker의 스토리지 드라이버

overlay2 (기본)

BASH
# 현재 사용 중인 스토리지 드라이버 확인
docker info | grep "Storage Driver"
# Storage Driver: overlay2
  • Linux 커널 4.0+에서 지원
  • 현재 모든 주요 배포판에서 기본값
  • 레이어 수 제한 없음 (이전 overlay는 2개 레이어만 가능)
  • 가장 안정적이고 성능이 좋음

btrfs

JSON
// /etc/docker/daemon.json
{
  "storage-driver": "btrfs"
}
  • Btrfs 파일 시스템에서만 사용 가능
  • 스냅샷 기능을 활용한 CoW
  • 서브볼륨으로 레이어 관리
  • Btrfs 자체의 기능(스냅샷, 압축)을 활용 가능

zfs

JSON
{
  "storage-driver": "zfs"
}
  • ZFS 파일 시스템에서 사용
  • 데이터 무결성 검증(checksum)
  • 압축, 중복 제거 지원
  • 엔터프라이즈 스토리지 환경에서 유리

비교

드라이버파일시스템CoW 단위성능안정성
overlay2ext4/xfs파일좋음매우 높음
btrfsbtrfs블록보통높음
zfszfs블록보통높음
devicemapper제한 없음블록낮음보통 (deprecated)

devicemapper와 aufs

이 두 드라이버는 현재 **deprecated(사용 중단 예정)**입니다.

  • aufs: Docker 초기에 사용, 커널 mainline에 포함되지 않음
  • devicemapper: 성능 문제, 설정 복잡성

CoW의 성능 영향

큰 파일 수정 시 주의

DOCKERFILE
# 나쁜 예: 큰 파일을 복사한 후 수정하면
# CoW로 인해 전체 파일이 upper layer에 복사됨
COPY large-data.db /data/
# 나중에 컨테이너에서 이 파일을 수정하면 →
# 수 GB 파일이 upper layer에 전체 복사

큰 파일을 자주 수정해야 한다면 볼륨을 사용하는 것이 좋습니다. 볼륨은 OverlayFS를 거치지 않고 직접 파일 시스템에 접근합니다.

파일 시스템 수준 CoW vs 블록 수준 CoW

방식overlay2btrfs/zfs
CoW 단위파일 전체블록 (4KB~128KB)
작은 파일 수정효율적효율적
큰 파일 일부 수정비효율적 (전체 복사)효율적 (변경 블록만)
메모리 사용낮음보통

레이어와 이미지 크기

레이어가 이미지 크기에 미치는 영향

DOCKERFILE
# 나쁜 예: 파일이 삭제되어도 이전 레이어에 남아있음
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y build-essential  # +500MB
RUN make && make install
RUN apt-get remove -y build-essential && apt-get autoremove -y  # -500MB?
# 실제 이미지 크기: ~500MB가 여전히 포함됨!

# 좋은 예: 같은 레이어에서 설치와 삭제
FROM ubuntu:22.04
RUN apt-get update \
    && apt-get install -y --no-install-recommends build-essential \
    && make && make install \
    && apt-get remove -y build-essential \
    && apt-get autoremove -y \
    && rm -rf /var/lib/apt/lists/*

docker history로 레이어 분석

BASH
docker history myapp:latest
# IMAGE          CREATED       CREATED BY                           SIZE
# abc123         5 min ago     CMD ["node" "server.js"]             0B
# def456         5 min ago     COPY dist/ ./dist/                   5MB
# ghi789         5 min ago     RUN npm ci --omit=dev                50MB
# jkl012         5 min ago     COPY package*.json ./                500KB
# mno345         2 weeks ago   /bin/sh -c #(nop) WORKDIR /app       0B
# pqr678         2 weeks ago   ...node:20-alpine layers...          180MB

정리

  • Docker는 OverlayFS(overlay2 드라이버)를 사용하여 이미지 레이어를 효율적으로 관리합니다.
  • lower layer는 읽기 전용인 이미지 레이어이고, upper layer는 컨테이너의 쓰기 레이어입니다. merged로 합쳐져서 하나의 파일 시스템처럼 보입니다.
  • Copy-on-Write로 파일 수정 시에만 복사가 발생하여 디스크를 효율적으로 사용합니다. 삭제는 whiteout 파일로 처리합니다.
  • 같은 이미지를 사용하는 여러 컨테이너는 lower layer를 공유하므로 디스크 공간이 절약됩니다.
  • 큰 파일을 자주 수정해야 한다면 OverlayFS의 CoW 오버헤드를 피하기 위해 볼륨을 사용하세요.
댓글 로딩 중...