Split View: containerd 이미지 관리: OCI 이미지와 스냅샷
containerd 이미지 관리: OCI 이미지와 스냅샷
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의 컨테이너 생명주기 관리를 분석합니다.
[containerd] Image Management: OCI Images and Snapshots
containerd Image Management: OCI Images and Snapshots
The containerd image management subsystem handles storing, distributing, and unpacking images based on the OCI image spec. This post analyzes the internals of Content Store, Snapshotter, image pull flow, and garbage collection.
1. OCI Image Spec
1.1 Image Structure
An OCI image consists of three core components:
OCI image structure:
1. Image Index (Fat Manifest)
- Supports multiple platforms (linux/amd64, linux/arm64, etc.)
- Points to per-platform Manifests
2. Image Manifest
- Digest of Config object
- Layer list (ordered)
- Media type information
3. Image Config
- Environment variables, entrypoint, CMD
- Layer diff ID list
- Creation history
1.2 Content Addressable Storage
Content Addressable Storage:
All objects are identified by SHA256 digest:
sha256:abc123... -> Image Index JSON
sha256:def456... -> Image Manifest JSON
sha256:789ghi... -> Image Config JSON
sha256:jkl012... -> Layer tar.gz
Benefits:
- Deduplication: identical layers stored only once
- Integrity verification: validate data via digest
- Caching: digest-based cache lookups
2. Content Store
2.1 Overview
Content Store is containerd's content-addressable storage that manages all binary data for images.
Content Store directory structure:
/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/
(temporary download data)
2.2 Content Store API
Content Store key operations:
Info(digest) -> Query content metadata (size, creation time)
ReaderAt(digest) -> Read content (io.ReaderAt interface)
Writer(ref) -> Write content (atomic commit)
Delete(digest) -> Delete content
ListStatuses() -> Query in-progress writes
Abort(ref) -> Cancel in-progress write
2.3 Ingest Process
Content write (Ingest) process:
1. Create Writer (assign reference key)
|
v
2. Create temporary file in ingest/ directory
|
v
3. Stream data writes
(e.g., downloading layers from registry)
|
v
4. Digest verification
(compare expected digest with actual data hash)
|
v
5. Atomic commit
(move from ingest/ -> blobs/sha256/)
|
v
6. Clean up ingest/ on failure
3. Snapshotter
3.1 Snapshotter Overview
The Snapshotter is a plugin that manages image layers as filesystem snapshots, preparing the root filesystem for containers.
Snapshotter role:
Image layers (tar.gz)
|
v
Snapshotter converts each layer into a snapshot
|
v
Stacks snapshots to form a unified filesystem
|
v
Provides mount point to container
3.2 Snapshot Types
Snapshot types:
1. Committed
- Read-only snapshot
- Corresponds to image layers
- Sharable across multiple containers
2. Active
- Read/write snapshot
- Writable layer for a container
- Assigned to a single container
3.3 overlayfs Snapshotter
The most widely used Snapshotter:
overlayfs operation:
Layer 1 (base): /snapshots/1/fs (lowerdir)
Layer 2 (app): /snapshots/2/fs (lowerdir)
Write layer: /snapshots/3/fs (upperdir)
Work directory: /snapshots/3/work (workdir)
Mount:
mount -t overlay overlay \
-o lowerdir=/snapshots/2/fs:/snapshots/1/fs,\
upperdir=/snapshots/3/fs,\
workdir=/snapshots/3/work \
/container/rootfs
Benefits:
- Copy-on-Write: copies only on modification
- Fast container startup
- Layer sharing saves disk space
3.4 native Snapshotter
native Snapshotter:
- Stores each snapshot in an independent directory
- Fully copies parent snapshot (using hardlinks)
- Used in environments without overlayfs support
- Higher disk usage
- Simple and highly portable
3.5 devmapper Snapshotter
devmapper Snapshotter:
- Uses Linux device mapper thin provisioning
- Block-level Copy-on-Write
- Suited for high-performance workloads
- Used with Firecracker microVMs
- Complex setup (requires thin-pool pre-configuration)
Use cases:
- AWS Fargate (Firecracker)
- High-performance container environments
- Block storage-based infrastructure
3.6 Snapshotter API
Snapshotter key operations:
Stat(key) -> Query snapshot info
Prepare(key, parent) -> Create Active snapshot (writable)
View(key, parent) -> Read-only view of Committed snapshot
Commit(name, key) -> Convert Active snapshot to Committed
Mounts(key) -> Return mount info for snapshot
Remove(key) -> Delete snapshot
4. Image Pull Flow
4.1 Complete Flow
Image pull complete flow:
1. Resolve image reference
docker.io/library/nginx:latest
|
v
2. Download Image Index/Manifest
- Fetch manifest from registry
- Select manifest for target platform
|
v
3. Download Config
- Download image config JSON
- Store in Content Store
|
v
4. Download layers (parallel)
- Store each layer in Content Store
- Skip already existing layers
|
v
5. Unpack layers
- Read layers from Content Store
- Create snapshots via Snapshotter
|
v
6. Register image metadata
- Create image record in BoltDB
- Map tags to digests
4.2 Layer Download Details
Layer download:
1. Extract layer digest list from manifest
2. Check if already exists in Content Store
3. Download only missing layers from registry
4. Transfer Service manages downloads:
- Concurrent download limit (default 3)
- Progress tracking
- Retry logic
5. Each layer stored gzip-compressed in Content Store
4.3 Layer Unpacking
Layer unpacking:
1. Read layer blob from Content Store
2. Decompress gzip
3. Extract tar archive
4. Create snapshot in Snapshotter:
a. First layer: Prepare without parent
b. Apply layer contents to snapshot
c. Commit to convert to read-only
d. Next layer: Prepare with previous snapshot as parent
5. Complete final snapshot chain
Snapshot chain:
Layer 1 (committed) <- Layer 2 (committed) <- Layer 3 (committed)
5. Image Metadata
5.1 Image Record
Image metadata (BoltDB):
Image record:
- 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 Querying Images
# List images with ctr
ctr -n k8s.io images list
# Detailed image info
ctr -n k8s.io images check
# Inspect image content
ctr -n k8s.io content get sha256:abc123... | jq .
6. Garbage Collection
6.1 GC Mechanism
Garbage collection operation:
1. Identify root objects:
- Image records
- Container records
- Lease records
2. Trace references (Mark):
- Image -> Manifest -> Config + Layers
- Container -> Snapshot chain
- Lease -> Protected resources
3. Delete unreferenced objects (Sweep):
- Delete unreferenced blobs from Content Store
- Delete unreferenced snapshots from Snapshotter
- Clean up orphaned metadata records
6.2 GC Labels
GC reference labels:
containerd manages GC references via labels:
Image labels:
"containerd.io/gc.ref.content.0": "sha256:..." (manifest reference)
"containerd.io/gc.ref.content.1": "sha256:..." (layer reference)
Content labels:
"containerd.io/gc.ref.content.config": "sha256:..." (config reference)
"containerd.io/gc.ref.content.l.0": "sha256:..." (layer reference)
Snapshot labels:
"containerd.io/gc.ref.snapshot.overlayfs": "sha256:..." (snapshot reference)
6.3 Lease
Lease:
- Protects in-progress operation resources from GC
- Protects downloaded layers during image pull
- Protects snapshots during container creation
- TTL-based automatic expiration
- Can be explicitly deleted after operation completes
Example:
Image pull starts -> Lease created
Layer download -> Lease protects content
Image registration complete -> Lease deleted (image record holds references)
6.4 GC Scheduling
GC triggers:
1. Periodic execution:
- gc_schedule in containerd config (no default)
- When configured, uses cron expression for scheduling
2. Event-based:
- On image deletion
- On container deletion
- Explicit API call
3. Manual execution via ctr:
ctr -n k8s.io content prune
7. Summary
containerd image management is built on three pillars: Content Store's content-addressable storage, Snapshotter's layer management, and GC's resource cleanup. The overlayfs Snapshotter's Copy-on-Write mechanism enables fast container startup and efficient disk usage, while Lease-based GC protection ensures image operation safety.