LabHub

블로그

containerd 이미지 관리: OCI 이미지와 스냅샷

한국어English日本語

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. 스케줄러 플러그인 (io.containerd.gc.v1.scheduler)의 임계값:
   - pause_threshold = 0.02
   - deletion_threshold = 0
   - mutation_threshold = 100
   - schedule_delay = "0ms"
   - startup_delay = "100ms"

2. 이벤트 기반:
   - 이미지 삭제 시
   - 컨테이너 삭제 시
   - API를 통한 명시적 호출

3. ctr을 통한 수동 실행:
   ctr -n k8s.io content prune references --dry

7. 노드에서 실제로 실행하는 명령

여기까지는 구조 설명이고, 노드에 들어가서 손에 잡히는 것은 명령의 출력입니다. 가장 먼저 부딪히는 벽은 네임스페이스입니다. containerd의 네임스페이스는 Kubernetes의 네임스페이스와 이름만 같을 뿐 아무 관계가 없는, 메타데이터 저장소를 논리적으로 가르는 단위입니다. kubelet이 CRI 플러그인을 통해 받아온 이미지, 스냅샷, 컨테이너 레코드는 전부 k8s.io 네임스페이스에 들어갑니다. 반면 ctr은 아무 옵션이 없으면 default 네임스페이스를 봅니다. 그래서 파드가 수십 개 떠 있는 노드에서 ctr images list를 치면 빈 표가 나오고, 여기서 이미지가 사라졌다거나 containerd가 이미지를 잃어버렸다는 잘못된 결론이 나옵니다. 사라진 것은 없고 다른 서랍을 연 것뿐입니다. 쿠버네티스 노드에서 ctr을 쓸 때는 예외 없이 -n k8s.io를 붙인다고 외워두는 편이 낫습니다.

# 어떤 네임스페이스가 있는지부터 확인 (NAME / LABELS)
ctr namespaces list

# kubelet이 쓰는 네임스페이스를 명시
ctr -n k8s.io images list         # 이미지 레코드
ctr -n k8s.io content ls          # DIGEST / SIZE / AGE / LABELS
ctr -n k8s.io content active      # REF / SIZE / AGE (진행 중인 ingest)
ctr -n k8s.io snapshots ls        # KEY / PARENT / KIND
ctr -n k8s.io snapshots tree      # 스냅샷 부모-자식 트리
ctr -n k8s.io snapshots usage     # KEY / SIZE / INODES
ctr -n k8s.io leases ls           # ID / CREATED AT / LABELS

이 몇 줄이 앞 절의 개념도를 전부 눈으로 확인할 수 있는 것으로 바꿔줍니다. content ls의 LABELS 컬럼에는 6.2에서 설명한 containerd.io/gc.ref.content 같은 참조 레이블이 그대로 찍히므로, 어떤 blob이 무엇에 붙들려 살아남아 있는지를 추측이 아니라 조회로 알 수 있습니다. snapshots ls의 KIND 컬럼은 Committed와 Active를 구분해 주는데, 컨테이너가 하나도 없는 노드에 Active 스냅샷이 남아 있다면 정리되지 않은 쓰기 레이어가 있다는 뜻입니다. snapshots usage는 스냅샷 키별 크기와 아이노드 수를 보여주므로, 디스크 용량보다 아이노드가 먼저 고갈되는 상황도 여기서 잡힙니다. content active는 커밋되지 않은 채 ingest 디렉토리에 남아 있는 다운로드를 보여줍니다. 정상 상태라면 pull이 진행 중일 때만 비어 있지 않아야 합니다.

kubelet 쪽 시야는 crictl로 봅니다. ctr이 containerd 전체를 보는 도구라면 crictl은 CRI 인터페이스만 통해서 보기 때문에, kubelet이 실제로 무엇을 알고 있는지에 더 가깝습니다. 두 도구의 출력이 어긋난다면 그 자체가 진단 정보입니다.

crictl images                       # CRI가 인지하는 이미지 목록
crictl images --digests --no-trunc  # 다이제스트까지 전부 표시
crictl imagefsinfo                  # 이미지 파일시스템 사용량
crictl rmi --prune                  # 사용 중이 아닌 이미지 정리

crictl rmi에는 주의할 점이 하나 있습니다. cri-tools 문서는 CRI API의 한계 때문에 태그로 이미지를 지정하면 그 태그만이 아니라 해당 이미지에 붙은 모든 태그가 함께 제거된다고 명시합니다. 특정 태그만 지우고 나머지를 남기고 싶다면 nerdctl이나 ctr 같은 런타임 네이티브 도구를 쓰라는 것이 문서의 권고입니다. --prune은 사용 중이 아닌 이미지를 전부 지우므로, 다음 배포가 곧바로 같은 이미지를 다시 받아야 하는 노드에서는 pull 폭풍을 만들 수 있습니다.


8. 이미지 동작을 좌우하는 설정 키

설정 파일을 열기 전에 버전부터 확인해야 합니다. containerd 1.x가 쓰던 version 2에서는 CRI 관련 설정이 전부 [plugins."io.containerd.grpc.v1.cri"] 아래 한 덩어리로 들어 있었습니다. containerd 2.x의 version 3은 이것을 런타임 쪽과 이미지 쪽으로 쪼개서 [plugins.'io.containerd.cri.v1.runtime'][plugins.'io.containerd.cri.v1.images']로 나눕니다. 블로그나 이슈에서 복사한 설정 조각이 적용되지 않는 가장 흔한 이유가 이 버전 차이입니다. 섹션 이름이 맞지 않으면 containerd는 그것을 모르는 플러그인 설정으로 무시하고, 오타를 알려주지 않은 채 기본값으로 동작합니다.

# containerd 1.x
version = 2

[plugins."io.containerd.grpc.v1.cri"]
  snapshotter = "overlayfs"
  discard_unpacked_layers = false

[plugins."io.containerd.grpc.v1.cri".registry]
  config_path = "/etc/containerd/certs.d"
# containerd 2.x
version = 3

[plugins.'io.containerd.cri.v1.images']
  snapshotter = "overlayfs"
  discard_unpacked_layers = false
  image_pull_progress_timeout = "5m0s"

[plugins.'io.containerd.cri.v1.images'.registry]
  config_path = "/etc/containerd/certs.d"

snapshotter의 기본값은 두 버전 모두 overlayfs입니다. 문서가 코어로 분류하는 스냅샷터는 overlayfs(Docker/Moby의 overlay2에 대응), native(vfs 드라이버에 대응), blockfile, devmapper, btrfs, zfs, erofs이고, fuse-overlayfs, nydus, overlaybd, stargz는 코어 밖의 플러그인입니다. btrfs와 zfs는 플러그인 루트가 해당 파일시스템으로 마운트되어 있어야 하고 erofs는 활성 스냅샷을 위해 OverlayFS 커널 모듈을 요구하기 때문에, 설정 파일에 이름을 적는 것만으로 켜지지 않습니다. 지금 돌고 있는 바이너리가 무엇을 실제로 들고 있는지는 ctr plugins ls로 확인하는 편이 문서를 뒤지는 것보다 빠릅니다.

discard_unpacked_layers는 기본값이 false이고, 문서의 설명은 레이어를 스냅샷터로 성공적으로 언패킹한 뒤 GC가 그 레이어를 콘텐츠 스토어에서 제거하도록 허용한다는 것입니다. 켜면 압축된 원본 레이어를 더 들고 있지 않게 되므로 노드 디스크 사용량이 눈에 띄게 줄어듭니다. 대신 콘텐츠 스토어에 blob이 없으므로 그 노드에서 이미지를 다시 push하거나 export할 수 없게 되고, 스냅샷을 재사용할 수 없는 상황이 오면 레지스트리에서 다시 받는 것 외에 방법이 없습니다. 순수 워커 노드에서는 대체로 이득이지만, 이미지를 만들거나 옮기는 데 쓰이는 노드에서는 켜면 안 됩니다.

image_pull_progress_timeout은 version 3 샘플 설정에 5m0s로 나오고, 문서의 설명은 이미지 pull 진행에 대한 타임아웃입니다. 이름이 오해를 부르기 쉬운데 pull 전체의 제한 시간이 아니라 진행이 멈춘 시간에 대한 제한으로 읽는 편이 안전합니다. 함께 보아야 할 값은 max_concurrent_downloads로, 문서는 이미지마다 동시 다운로드 수를 제한한다고 설명합니다. 레지스트리가 느린 것이 아니라 동시성이 낮아서 오래 걸리는 경우가 실제로 많습니다.

레지스트리 미러는 config_path로 붙입니다. version 2의 기본값은 /etc/containerd/certs.d:/etc/docker/certs.d이고 version 3의 기본값은 빈 문자열이므로, 2.x로 올라가면서 미러 설정이 갑자기 무시되는 일이 생깁니다. 경로를 지정하면 그 디렉토리 아래에 레지스트리 호스트 이름과 같은 이름의 디렉토리를 만들고 그 안에 hosts.toml을 둡니다. 어느 이름과도 일치하지 않을 때를 위한 폴백으로 _default 디렉토리를 쓸 수 있습니다.

# /etc/containerd/certs.d/docker.io/hosts.toml
server = "https://docker.io"

[host."https://mirror.example.com"]
  capabilities = ["pull", "resolve"]
  skip_verify = true

capabilities에 올 수 있는 값은 pull, resolve, push입니다. 사설 CA를 쓴다면 ca, 상호 TLS를 쓴다면 client를 같은 host 블록에 적습니다. server는 이 네임스페이스의 기본 서버이고, host 블록들은 server보다 먼저 시도됩니다.


9. 워크드 예제: pull 하나를 끝까지 따라가기

앞의 4장을 명령으로 재현해 보겠습니다. 노드에 SSH로 들어가 먼저 기준선을 찍습니다. crictl imagefsinfo는 이미지 파일시스템의 사용량을 돌려주는데, CRI API 정의상 마운트 지점(fs_id.mountpoint), 이미지가 쓰는 바이트(used_bytes), 이미지가 쓰는 아이노드(inodes_used)가 들어 있습니다. 출력 형식은 버전에 따라 달라질 수 있으니 정확한 필드는 사용 중인 버전의 문서에서 확인하세요.

# 1) 기준선
crictl imagefsinfo
ctr -n k8s.io content ls | wc -l
ctr -n k8s.io snapshots ls | wc -l

# 2) pull
crictl pull registry.k8s.io/pause:3.10.2

# 3) 사후
crictl imagefsinfo
ctr -n k8s.io content ls
ctr -n k8s.io snapshots tree
crictl images | grep pause

pull이 실제로 네트워크를 탔다면 순서는 이렇습니다. 먼저 매니페스트(또는 인덱스)가 콘텐츠 스토어에 들어가고, 그다음 config JSON이, 그다음 레이어 blob들이 들어갑니다. 이 세 종류는 전부 ctr -n k8s.io content ls에 다이제스트로 나타나며 크기로 구분됩니다. 수백 바이트에서 수 KB짜리가 매니페스트와 config, 수 MB 이상이 레이어입니다. 그 뒤 언패킹이 일어나 snapshots tree에 새 체인이 생기고, 마지막에야 crictl images에 이미지가 등장합니다. 순서를 알고 있으면 중간에서 멈춘 상태를 구분할 수 있습니다. content에는 있는데 snapshots에 없으면 언패킹 단계에서 멈춘 것이고, 둘 다 있는데 crictl images에 없으면 메타데이터 등록 전에 죽은 것입니다.

이미 캐시된 경우와 새로 받은 경우를 구별하는 방법은 간단합니다. crictl imagefsinfo의 사용 바이트가 pull 전후로 사실상 변하지 않고 content ls의 줄 수도 그대로라면 레지스트리에서 받은 것이 없다는 뜻입니다. 다이제스트가 이미 있으면 containerd는 다운로드를 건너뛰기 때문에, imagePullPolicy가 Always여도 매니페스트만 확인하고 레이어는 재사용합니다. 반대로 사용 바이트가 레이어 크기만큼 올라갔다면 실제로 받은 것입니다. pull이 오래 걸린다는 신고를 받았을 때 이 두 줄만 비교해도 네트워크 문제인지 언패킹 문제인지가 갈립니다.


10. 실패 사례와 진단 순서

디스크가 차오르는데 아무것도 지워지지 않는 것이 가장 흔한 신고입니다. containerd의 GC는 cron 스케줄이 아니라 io.containerd.gc.v1.scheduler 플러그인의 임계값으로 돕니다. 문서가 명시하는 기본값은 pause_threshold = 0.02, deletion_threshold = 0, mutation_threshold = 100, schedule_delay = "0ms", startup_delay = "100ms"이고, 문서는 기본 설정에서 스케줄러가 데이터베이스를 98% 시간 동안 잠기지 않은 상태로 유지하려 하고, 삭제가 발생하지 않았거나 데이터베이스 쓰기가 100회 쌓이기 전이면 스스로를 예약하지 않는다고 설명합니다. 즉 이미지를 지우는 주체는 GC가 아니라 kubelet의 이미지 정리이거나 사람입니다. 확인 순서는 노드 디스크 사용량, crictl imagefsinfo, ctr -n k8s.io snapshots usage, 그리고 kubelet의 이미지 GC 임계값입니다.

노드가 pull 도중에 재부팅되면 커밋되지 않은 ingest가 남습니다. 콘텐츠 스토어의 커밋은 원자적이므로 blobs 디렉토리는 오염되지 않지만, ingest 디렉토리의 조각은 그대로 공간을 차지합니다. ctr -n k8s.io content active에 아무 pull도 진행 중이 아닌데 REF가 남아 있으면 이 경우입니다. 진행 중인 쓰기 작업을 정리하는 연산은 2.2에서 본 Abort이고, 콘텐츠 스토어 정리는 ctr -n k8s.io content prune references로 수행합니다. 실제로 지우기 전에 --dry 플래그로 무엇이 대상인지 먼저 보는 것이 안전합니다.

세 번째는 리스입니다. 리스는 작업 중인 리소스를 GC로부터 보호하려고 만드는 것이고, ctr leases create의 만료 기본값은 24시간, 0을 주면 만료 없음입니다. 클라이언트가 리스를 만들고 지우지 않은 채 죽으면 아무 이미지도 참조하지 않는 blob이 계속 살아남습니다. 증상은 crictl images에는 몇 개 없는데 content ls에는 다이제스트가 잔뜩 있는 상태이고, 확인은 ctr -n k8s.io leases ls로 합니다. CREATED AT이 오래된 리스가 보이면 무엇이 그것을 만들었는지부터 찾아야 합니다.

네 번째는 discard_unpacked_layers를 켠 노드에서 이미지를 다시 push하거나 tar로 내보내려 할 때입니다. 스냅샷은 있으나 원본 레이어 blob이 없으므로 실패하며, 설정을 되돌려도 이미 지워진 blob이 돌아오지는 않습니다. 다섯 번째는 미러 설정이 무시되는 경우로, 파일이 certs.d 아래 레지스트리 호스트 이름 디렉토리의 hosts.toml 경로에 정확히 있는지, 그리고 config_path가 실제로 설정되어 있는지를 순서대로 봅니다. 2.x에서는 기본값이 비어 있으므로 파일만 놓아두면 아무 일도 일어나지 않습니다. 마지막은 아주 큰 레이어에서의 pull 타임아웃입니다. 이때는 레지스트리 왕복 지연이 아니라 진행이 멈추는지를 봐야 하고, max_concurrent_downloadsimage_pull_progress_timeout을 함께 조정합니다.


11. 이 계층을 직접 건드리지 않는 편이 나은 경우

관리형 노드 그룹에서는 containerd 설정 파일이 노드 부트스트랩 때 다시 생성되는 경우가 많습니다. 손으로 고친 /etc/containerd/config.toml은 노드가 교체되는 순간 사라지고, 더 나쁘게는 일부 노드에만 남아서 재현되지 않는 차이를 만듭니다. 설정을 바꿔야 한다면 노드에 직접 들어가는 대신 노드 그룹의 부트스트랩 스크립트나 런치 템플릿, 또는 노드 이미지 자체를 고쳐야 합니다.

그리고 대부분의 이미지 관련 문제는 이 계층을 건드리지 않고 해결됩니다. pull이 잦아서 느리다면 imagePullPolicy를 IfNotPresent로 낮추고 태그 대신 다이제스트를 고정하는 편이 낫습니다. 레지스트리 대역폭이 문제라면 hosts.toml에 미러를 붙이거나 클러스터 안에 pull-through 캐시를 두는 것이 스냅샷터를 바꾸는 것보다 훨씬 적은 위험으로 같은 효과를 냅니다. 스냅샷터 교체나 discard_unpacked_layers 같은 스위치는 되돌리기 어렵고 노드마다 상태가 달라지기 쉬우므로, 위의 방법을 다 써 본 뒤에 마지막으로 고려할 카드입니다.


12. 참고 자료


13. 정리

containerd의 이미지 관리는 Content Store의 콘텐츠 주소 저장, Snapshotter의 레이어 관리, 그리고 GC의 리소스 정리라는 세 축으로 구성됩니다. overlayfs Snapshotter의 Copy-on-Write 메커니즘은 빠른 컨테이너 시작과 효율적인 디스크 사용을 가능하게 하며, Lease 기반 GC 보호는 이미지 작업의 안전성을 보장합니다. 다음 글에서는 containerd의 컨테이너 생명주기 관리를 분석합니다.

댓글

아직 댓글이 없습니다.

로그인하면 댓글을 쓸 수 있습니다