IT/컨테이너

containerd snapshotter 내부 - overlay2에서 stargz로 넘어갈 때 실제로 무슨 일이 벌어지나

gfrog 2026. 7. 31. 21:18

컨테이너 이미지를 pull하고 시작할 때, 표면적으로는 그냥 kubectl apply 한 방이면 끝나는 것처럼 보인다. 근데 사실 그 아래에서는 containerd가 snapshotter라는 놈을 통해 파일시스템 레이어를 조립하고 마운트하는 꽤 정교한 과정이 돌아간다. 대부분은 overlay2를 그냥 쓰고 지나가지만, 몇 달 전부터 우리 팀에서 콜드스타트 지연을 잡느라 stargz-snapshotter를 검토하면서, 이 계층이 실제로 뭘 하고 있는지 다시 파헤쳐볼 일이 생겼다.

지난 2월에 stargz 관련 lazy pulling이 다시 주목받으면서 관련 글이 여럿 올라왔는데, 대부분 "빠르다"에서 멈춰 있다. 사실 내부적으로 뭐가 바뀌는지 짚어봐야 언제 써야 하는지 판단할 수 있다. 그래서 이번 글에서는 snapshotter 인터페이스 자체, overlay2 구현, 그리고 stargz가 어떻게 그 계약을 다른 방식으로 만족시키는지를 순서대로 본다.

snapshotter는 결국 뭘 하는 놈인가

containerd에서 snapshotter는 Prepare / Mount / Commit 이 세 개의 오퍼레이션을 중심으로 정의된 인터페이스다. 이미지 pull이 끝나면 각 레이어에 대해 unpacker가 tar.gz를 풀고, snapshotter의 Prepare를 호출해서 "이 부모 레이어 위에 쓸 수 있는 새 레이어를 준비하라"고 요청한다. 이 요청에 대해 snapshotter는 []mount.Mount 슬라이스를 반환한다. runc는 이걸 받아서 실제 컨테이너 rootfs로 마운트하고 프로세스를 띄운다.

여기서 중요한 포인트가 하나 있다. containerd 상위 계층은 마운트가 어떻게 조립되는지 모른다. snapshotter가 overlay를 반환하든, fuse를 반환하든, zfs 스냅샷을 반환하든 인터페이스만 맞으면 그만이다. 그래서 snapshotter는 사실 stateful storage backend에 가깝다 - 레이어별로 어떤 디스크 위치에 뭐가 있는지, 어느 게 read-only이고 어느 게 upper인지를 자기가 관리한다.

overlay2가 실제로 하는 일

overlay2 스냅샷터는 이름 그대로 리눅스 커널의 overlayfs를 얹는다. /var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots//fs 아래에 각 스냅샷의 파일들이 물리적으로 존재한다. Prepare가 호출되면 상위 스냅샷의 부모 체인을 따라 올라가면서 lowerdir 목록을 만들고, 새 스냅샷 하나를 upperdir로 배치한 뒤 다음과 같은 마운트 옵션을 반환한다.

overlay overlay
  lowerdir=/var/lib/.../snapshots/42/fs:/var/lib/.../snapshots/41/fs:...
  upperdir=/var/lib/.../snapshots/99/fs
  workdir=/var/lib/.../snapshots/99/work

이때 각 lowerdir에는 이미 unpack된 실제 파일들이 앉아 있다. 여기서 우리가 흔히 잊는 사실 하나 - 컨테이너가 뜨기 전에 이 unpack이 이미 다 끝나 있어야 한다는 점이다. 즉 4GB짜리 이미지를 pull하면 4GB의 tar.gz를 다 받고, 다 풀고, 디스크에 다 눕힌 다음에야 프로세스가 실행된다. 콜드스타트 지연의 상당 부분이 여기서 발생한다.

overlay2의 read 경로도 짚어둘 만하다. 컨테이너 안에서 파일을 열면 overlayfs는 상위 upperdir부터 lowerdir을 순서대로 뒤져서 첫 번째로 발견되는 파일을 반환한다. 이 조회는 커널 안에서 벌어지고, 파일 자체는 이미 로컬 파일시스템에 있으니 별도의 네트워크 왕복이 없다. 빠르지만, 데이터를 미리 다 받아왔기에 빠른 거다.

stargz가 이 그림을 어떻게 뒤집나

stargz-snapshotter는 다른 접근을 한다. 같은 Prepare/Mount 인터페이스를 구현하지만, 반환하는 마운트가 overlay가 아니라 FUSE 마운트다. 정확히는 stargz-snapshotter가 자체 프로세스로 뜨면서 각 레이어에 대해 FUSE 파일시스템을 사용자 공간에서 서빙한다.

핵심은 eStargz라는 이미지 포맷이다. 일반 gzip은 순차 읽기만 가능해서 특정 파일 하나를 꺼내려 해도 앞부분을 다 풀어야 한다. eStargz는 gzip 스트림 중간에 restart 지점을 심어두고, tar 헤더 오프셋을 TOC(Table of Contents)로 이미지 매니페스트 옆에 함께 배포한다. 그래서 클라이언트는 TOC만 먼저 받아본 뒤 특정 파일이 필요한 수간에 그 파일의 바이트 범위만 HTTP Range 요청으로 가져올 수 있다.

컨테이너 시작 시점의 흐름은 이렇게 바뀐다. Prepare가 호출되면 stargz-snapshotter는 실제 데이터를 받지 않고 TOC만 pull한다. 그리고 FUSE 마운트를 하나 만든 뒤 그걸 lowerdir처럼 상위에 노출한다. 컨테이너 프로세스가 execve를 통해 /bin/sh 같은 파일을 열려는 순간에야 FUSE 데몬이 해당 파일의 오프셋을 TOC에서 찾아 레지스트리로 Range 요청을 날린다. 받은 청크는 로컬 캐시에 쌓아두고, 다음 read부터는 캐시에서 서빙한다.

성능 특성이 어떻게 갈리는가

이 차이 때문에 두 스냅샷터는 워크로드에 따라 이상하게 갈린다.

우리가 실측한 결과 대략적으로 말하면, 4GB짜리 ML 이미지를 처음 뜨는 노드에서 overlay2는 pull+unpack에 90초 넘게 걸렸다. stargz는 같은 이미지를 대략 8초 만에 컨테이너 프로세스까지 살렸다. 근데 이건 hello world 수준으로 실행이 끝나는 워크로드 얘기고, 컨테이너가 그 안의 파일 대부분을 실제로 읽는 배치 잡 같은 경우엔 상황이 다르다. stargz는 결국 필요할 때 다 받게 되므로 총 read 시간이 overlay2보다 더 걸리는 순간도 있었다. 특히 여러 파일을 동시에 여는 프로세스에서 FUSE 왕복 오버헤드가 눈에 띄었다.

또 하나, 이미지 자체가 eStargz 포맷으로 빌드되어 있어야 한다. Docker의 기본 빌드 결과물은 stargz가 아니다. BuildKit으로 --output type=image,name=...,compression=estargz,force-compression=true 옵션을 줘서 다시 밀거나, ctr-remote로 기존 이미지를 컨버트해야 한다. 이 파이프라인 정비가 상상보다 손이 많이 간다. 기존 CI에 컨버전 스텝을 넣고, 레지스트리 저장 용량이 대략 20% 정도 늘어나는 것도 감안해야 했다.

노드 설정도 하나 짚어두자. 컨테이너드 config.toml에서 [plugins."io.containerd.grpc.v1.cri".containerd] 아래 snapshotter = "stargz"로 바꿔야 하는데, 이건 노드별 설정이라 mixed cluster에서 nodeSelector로 워크로드를 라우팅해야 한다. 처음엔 이 부분을 놓쳐서 절반의 노드에서만 lazy pulling이 걸리고 나머지는 정상 overlay로 돌아가는 이상한 상태가 됐다.

그래서 어디에 쓸 만한가

정리하면 이런 그림이다. 짧게 실행되는 콜드스타트, 특히 서버리스나 KEDA로 스케일아웃되는 job, GPU 노드에 CUDA 이미지처럼 무거운 이미지를 자주 새로 pull해야 하는 워크로드에는 확실히 이득이 있다. 반대로 상시 러닝하는 웹 서버나 이미지 안 파일을 광범위하게 훑는 배치 워크로드에는 굳이 이점을 못 챙긴다. 오히려 FUSE 데몬을 하나 더 관리해야 하는 운영 부담이 붙는다.

우리 팀은 결국 GPU 학습 노드 풀에만 stargz를 적용하고, 일반 애플리케이션 노드는 overlay2를 유지하기로 했다. 아직 몇 주 더 관찰이 필요하지만, 콜드스타트 P50이 유의미하게 내려간 건 확인했다. 다만 stargz-snapshotter 자체가 아직 활발히 개선되는 프로젝트라 config가 종종 바뀐다. 최근에도 FUSE 관련 개선과 hot reload가 들어갔으니 버전을 못 박아두고 정기적으로 릴리스 노트를 따라가는 게 안전하다.

혹시 다른 스냅샷터(SOCI, nydus 같은)를 프로덕션에서 굴려보신 분 있으면 어떤 트레이드오프가 있었는지 공유해주시면 감사할 것 같다.