- Authors

- Name
- Youngju Kim
- @fjvbn20031
containerd 이미지 관리: OCI 이미지와 스냅샷
containerd의 이미지 관리 서브시스템은 OCI 이미지 스펙을 기반으로 이미지를 저장, 배포, 언패킹하는 핵심 기능을 담당합니다. 이 글에서는 Content Store, Snapshotter, 이미지 Pull 플로우, 가비지 컬렉션의 내부 동작을 분석합니다.
1. OCI 이미지 스펙
1.1 이미지 구조
OCI 이미지는 세 가지 핵심 구성 요소로 이루어집니다:
OCI 이미지 구조:
1. Image Index (Fat Manifest)
- 여러 플랫폼(linux/amd64, linux/arm64 등)을 지원
- 각 플랫폼별 Manifest를 가리킴
2. Image Manifest
- Config 객체의 다이제스트
- 레이어 목록 (순서 보장)
- 미디어 타입 정보
3. Image Config
- 환경 변수, 엔트리포인트, CMD
- 레이어 diff ID 목록
- 생성 히스토리
1.2 콘텐츠 주소 지정
콘텐츠 주소 지정(Content Addressable Storage):
모든 객체는 SHA256 다이제스트로 식별:
sha256:abc123... -> Image Index JSON
sha256:def456... -> Image Manifest JSON
sha256:789ghi... -> Image Config JSON
sha256:jkl012... -> Layer tar.gz
장점:
- 중복 제거: 동일 레이어는 한 번만 저장
- 무결성 검증: 다이제스트로 데이터 검증
- 캐싱: 다이제스트 기반 캐시 룩업
2. Content Store
2.1 개요
Content Store는 containerd의 콘텐츠 주소 저장소로, 이미지의 모든 바이너리 데이터를 관리합니다.
Content Store 디렉토리 구조:
/var/lib/containerd/io.containerd.content.v1.content/
blobs/
sha256/
abc123... (Image Index)
def456... (Image Manifest)
789ghi... (Image Config)
jkl012... (Layer 1 tar.gz)
mno345... (Layer 2 tar.gz)
ingest/
(임시 다운로드 데이터)
2.2 Content Store API
Content Store 주요 연산:
Info(digest) -> 콘텐츠 메타데이터 조회 (크기, 생성시간)
ReaderAt(digest) -> 콘텐츠 읽기 (io.ReaderAt 인터페이스)
Writer(ref) -> 콘텐츠 쓰기 (원자적 커밋)
Delete(digest) -> 콘텐츠 삭제
ListStatuses() -> 진행 중인 쓰기 작업 조회
Abort(ref) -> 진행 중인 쓰기 작업 취소
2.3 Ingest 프로세스
콘텐츠 쓰기 (Ingest) 프로세스:
1. Writer 생성 (참조 키 할당)
|
v
2. ingest/ 디렉토리에 임시 파일 생성
|
v
3. 데이터 스트리밍 쓰기
(레지스트리에서 레이어 다운로드 등)
|
v
4. 다이제스트 검증
(기대 다이제스트와 실제 데이터 해시 비교)
|
v
5. 원자적 커밋
(ingest/ -> blobs/sha256/ 으로 이동)
|
v
6. 실패 시 ingest/ 에서 정리
3. Snapshotter
3.1 Snapshotter 개요
Snapshotter는 이미지 레이어를 파일시스템 스냅샷으로 관리하는 플러그인입니다. 컨테이너가 사용할 루트 파일시스템을 준비합니다.
Snapshotter 역할:
이미지 레이어 (tar.gz)
|
v
Snapshotter가 각 레이어를 스냅샷으로 변환
|
v
스냅샷을 쌓아서 통합 파일시스템 구성
|
v
컨테이너에 마운트 포인트 제공
3.2 스냅샷 타입
스냅샷 종류:
1. Committed (커밋됨)
- 읽기 전용 스냅샷
- 이미지 레이어에 대응
- 여러 컨테이너가 공유 가능
2. Active (활성)
- 읽기/쓰기 스냅샷
- 컨테이너의 쓰기 가능 레이어
- 하나의 컨테이너에 할당
3.3 overlayfs Snapshotter
가장 널리 사용되는 Snapshotter입니다:
overlayfs 동작:
레이어 1 (base): /snapshots/1/fs (lowerdir)
레이어 2 (app): /snapshots/2/fs (lowerdir)
쓰기 레이어: /snapshots/3/fs (upperdir)
작업 디렉토리: /snapshots/3/work (workdir)
마운트:
mount -t overlay overlay \
-o lowerdir=/snapshots/2/fs:/snapshots/1/fs,\
upperdir=/snapshots/3/fs,\
workdir=/snapshots/3/work \
/container/rootfs
장점:
- Copy-on-Write: 변경 시에만 복사
- 빠른 컨테이너 시작
- 레이어 공유로 디스크 절약
3.4 native Snapshotter
native Snapshotter:
- 각 스냅샷을 독립 디렉토리에 저장
- 부모 스냅샷을 완전히 복사 (hardlink 사용)
- overlayfs를 지원하지 않는 환경에서 사용
- 디스크 사용량이 큼
- 간단하고 이식성이 높음
3.5 devmapper Snapshotter
devmapper Snapshotter:
- Linux device mapper의 thin provisioning 사용
- 블록 레벨 Copy-on-Write
- 고성능 워크로드에 적합
- Firecracker microVM과 함께 사용
- 설정이 복잡함 (thin-pool 사전 구성 필요)
사용 사례:
- AWS Fargate (Firecracker)
- 고성능 컨테이너 환경
- 블록 스토리지 기반 인프라
3.6 Snapshotter API
Snapshotter 주요 연산:
Stat(key) -> 스냅샷 정보 조회
Prepare(key, parent) -> Active 스냅샷 생성 (쓰기 가능)
View(key, parent) -> Committed 스냅샷의 읽기 전용 뷰
Commit(name, key) -> Active 스냅샷을 Committed로 변환
Mounts(key) -> 스냅샷의 마운트 정보 반환
Remove(key) -> 스냅샷 삭제
4. 이미지 Pull 플로우
4.1 전체 플로우
이미지 Pull 전체 과정:
1. 이미지 참조 해석
docker.io/library/nginx:latest
|
v
2. Image Index/Manifest 다운로드
- 레지스트리에서 매니페스트 가져오기
- 플랫폼에 맞는 매니페스트 선택
|
v
3. Config 다운로드
- 이미지 설정 JSON 다운로드
- Content Store에 저장
|
v
4. 레이어 다운로드 (병렬)
- 각 레이어를 Content Store에 저장
- 이미 존재하는 레이어는 건너뜀
|
v
5. 레이어 언패킹
- Content Store에서 레이어 읽기
- Snapshotter로 스냅샷 생성
|
v
6. 이미지 메타데이터 등록
- BoltDB에 이미지 레코드 생성
- 태그와 다이제스트 매핑
4.2 레이어 다운로드 상세
레이어 다운로드:
1. 매니페스트에서 레이어 다이제스트 목록 추출
2. Content Store에 이미 존재하는지 확인
3. 없는 레이어만 레지스트리에서 다운로드
4. Transfer Service가 다운로드 관리:
- 동시 다운로드 제한 (기본 3개)
- 진행 상황 추적
- 재시도 로직
5. 각 레이어는 gzip 압축 상태로 Content Store에 저장
4.3 레이어 언패킹
레이어 언패킹 (Unpack):
1. Content Store에서 레이어 blob 읽기
2. gzip 해제
3. tar 아카이브 추출
4. Snapshotter에 스냅샷 생성:
a. 첫 번째 레이어: 부모 없이 Prepare
b. 레이어 내용을 스냅샷에 적용 (Apply)
c. Commit으로 읽기 전용으로 변환
d. 다음 레이어: 이전 스냅샷을 부모로 Prepare
5. 최종 스냅샷 체인 완성
스냅샷 체인:
Layer 1 (committed) <- Layer 2 (committed) <- Layer 3 (committed)
5. 이미지 메타데이터
5.1 이미지 레코드
이미지 메타데이터 (BoltDB):
이미지 레코드:
- Name: "docker.io/library/nginx:latest"
- Target:
MediaType: "application/vnd.oci.image.index.v1+json"
Digest: "sha256:abc123..."
Size: 1234
- Labels:
"containerd.io/gc.ref.content.0": "sha256:def456..."
"containerd.io/gc.ref.content.1": "sha256:789ghi..."
- CreatedAt: 2026-03-20T00:00:00Z
- UpdatedAt: 2026-03-20T00:00:00Z
5.2 이미지 조회
# ctr로 이미지 목록 조회
ctr -n k8s.io images list
# 이미지 상세 정보
ctr -n k8s.io images check
# 이미지 콘텐츠 확인
ctr -n k8s.io content get sha256:abc123... | jq .
6. 가비지 컬렉션
6.1 GC 메커니즘
가비지 컬렉션 동작:
1. 루트 객체 식별:
- 이미지 레코드
- 컨테이너 레코드
- Lease 레코드
2. 참조 추적 (Mark):
- 이미지 -> Manifest -> Config + Layers
- 컨테이너 -> 스냅샷 체인
- Lease -> 보호 중인 리소스
3. 미참조 객체 삭제 (Sweep):
- Content Store의 미참조 blob 삭제
- Snapshotter의 미참조 스냅샷 삭제
- 메타데이터의 고아 레코드 정리
6.2 GC 레이블
GC 참조 레이블:
containerd는 GC 참조를 레이블로 관리합니다:
이미지 레이블:
"containerd.io/gc.ref.content.0": "sha256:..." (매니페스트 참조)
"containerd.io/gc.ref.content.1": "sha256:..." (레이어 참조)
콘텐츠 레이블:
"containerd.io/gc.ref.content.config": "sha256:..." (config 참조)
"containerd.io/gc.ref.content.l.0": "sha256:..." (레이어 참조)
스냅샷 레이블:
"containerd.io/gc.ref.snapshot.overlayfs": "sha256:..." (스냅샷 참조)
6.3 Lease
Lease (임대):
- 진행 중인 작업의 리소스를 GC로부터 보호
- 이미지 Pull 중 다운로드된 레이어 보호
- 컨테이너 생성 중 스냅샷 보호
- TTL 기반 자동 만료
- 작업 완료 후 명시적 삭제 가능
예시:
이미지 Pull 시작 -> Lease 생성
레이어 다운로드 -> Lease가 콘텐츠 보호
이미지 등록 완료 -> Lease 삭제 (이미지 레코드가 참조)
6.4 GC 스케줄링
GC 트리거:
1. 주기적 실행:
- containerd 설정의 gc_schedule (기본값 없음)
- 설정 시 cron 표현식으로 주기 지정
2. 이벤트 기반:
- 이미지 삭제 시
- 컨테이너 삭제 시
- API를 통한 명시적 호출
3. ctr을 통한 수동 실행:
ctr -n k8s.io content prune
7. 정리
containerd의 이미지 관리는 Content Store의 콘텐츠 주소 저장, Snapshotter의 레이어 관리, 그리고 GC의 리소스 정리라는 세 축으로 구성됩니다. overlayfs Snapshotter의 Copy-on-Write 메커니즘은 빠른 컨테이너 시작과 효율적인 디스크 사용을 가능하게 하며, Lease 기반 GC 보호는 이미지 작업의 안전성을 보장합니다. 다음 글에서는 containerd의 컨테이너 생명주기 관리를 분석합니다.