상태: 초안 (2026-10-17), 일부 실측. 기준 판 containerd/containerd v2.3.5(커밋 1294c24a7da8), kubernetes/cri-api v0.34.0(커밋 9b3ce5ec4798). 1.7 과의 비교는 containerd v1.7.27(커밋 05044ec0a9a7)을 함께 열어 소스로 확인한 것만 적었습니다. 실측은 2026-10-17 작은 클러스터에서 읽기만 했습니다(노드 목록 읽기, 워커 두 대에 일반 사용자로 ssh 해
ps·mount·설정 파일 읽기, sudo 없음). 작성 규칙: 실측 데이터가 기본이고, 못 잰 것은 「미실측」, 확인하지 못한 것은 「미확인」으로 표시합니다.
목차
- 1. 한 줄 요약: 주문서 양식과 주방
- 2. 구조도
- 3. 코드 지도
- 4. 핵심 경로 따라가기
- 5. 사용자 기여와의 연결
- 6. 실측: 1.7 워커와 2.3 워커를 권한 없이 읽기
- 7. 어디부터 읽을까
- 8. 첫 기여하기 좋은 곳
- 9. 한계
- 참고
1. 한 줄 요약: 주문서 양식과 주방
kubelet 은 컨테이너를 직접 만들지 않습니다. 「이 파드의 샌드박스를 차려라」, 「이 이미지로 컨테이너를 만들어라」, 「켜라」 같은 주문을 CRI(Container Runtime Interface) 라는 정해진 양식으로 보낼 뿐입니다. 주문서 양식이 정해져 있으니 주방(런타임)은 containerd 든 CRI-O 든 바꿀 수 있습니다. containerd 는 그 주문을 받아 재료(이미지 레이어)를 꺼내 손질하고(스냅샷), 테이블마다 담당 직원(shim)을 붙이고, 실제로 불을 켜는 일은 조리사(runc)에게 맡깁니다. 조리사는 요리를 앉히고 바로 퇴근하고, 남은 시중은 담당 직원이 듭니다.
| 구성요소 | 하는 일 | 어디서 도나 |
|---|---|---|
| kubelet | 파드 명세를 CRI 호출로 바꿔 보냄 | 노드마다 systemd 서비스 |
| CRI(cri-api) | RuntimeService·ImageService 두 gRPC 서비스의 규약 | api.proto 한 파일 |
| containerd CRI 플러그인 | CRI 요청을 받아 코어 서비스·CNI 를 부름 | containerd 프로세스 안 |
| containerd 코어 서비스 | 컨테이너 메타데이터·태스크·이미지·콘텐츠·스냅샷·임대(lease) | containerd 프로세스 안 |
| containerd-shim-runc-v2 | 파드 하나의 컨테이너 프로세스를 지키고 종료 코드를 받음 | 파드마다 프로세스 1개 |
| runc | OCI 번들로 네임스페이스·cgroup 을 만들고 프로세스를 띄움 | 실행 후 바로 종료 |
| CNI 플러그인 | 파드 네트워크 네임스페이스에 인터페이스·IP 를 붙임 | containerd 가 실행하는 바이너리 |
2. 구조도
- 위에서 아래로 한 번의 파드 생성이 흘러갑니다. kubelet 과 containerd 사이만 gRPC(유닉스 소켓)이고, CRI 플러그인과 코어 서비스 사이는 같은 프로세스 안의 함수 호출입니다.
- 원통 셋은 디스크에 남는 상태입니다.
meta.db(bolt 메타데이터),blobs/sha256(콘텐츠 저장소),snapshots/N(overlayfs 스냅샷터)입니다. - containerd 와 shim 사이는 ttrpc(gRPC 를 가볍게 줄인 프로토콜)입니다. shim 은 파드마다 하나이고, runc 는 일을 마치면 사라집니다.
- CNI 는 containerd 바깥의 바이너리라 주황색으로 따로 그렸습니다.
- 왼쪽 더미가 overlayfs 스냅샷터의
snapshots/<번호>/fs디렉터리입니다. 아래 셋은 커밋된 읽기 전용 층, 맨 위가 컨테이너마다 새로 만든 쓰기 층입니다. - 오른쪽 ①~④ 는 4.4절에서 따라갈 함수 순서입니다.
3. 코드 지도
containerd 2.x 저장소는 Go 모듈 하나(github.com/containerd/containerd/v2)입니다. 처음 열 때 중요한 것만 추렸습니다.
| 경로 | 무엇 | 처음 읽을 파일 |
|---|---|---|
cmd/containerd/ |
데몬 진입점, 내장 플러그인 목록, 플러그인 적재 | builtins/builtins.go, server/server.go |
plugins/ |
플러그인 등록부. 서비스·스냅샷터·콘텐츠·CRI·GC 등 | types.go, cri/cri.go |
internal/cri/ |
CRI 구현 본체(서버·샌드박스 컨트롤러·이미지 서비스·설정) | server/sandbox_run.go |
core/ |
도메인 로직: 메타데이터(bolt), runtime v2, unpack, transfer | runtime/v2/shim_manager.go |
client/ |
Go 클라이언트. CRI 플러그인도 이것을 「메모리 안」으로 씀 | container.go |
cmd/containerd-shim-runc-v2/ |
runc 용 shim | manager/manager_linux.go |
api/ |
containerd 자체 gRPC·ttrpc proto | services/tasks/v1/tasks.proto |
kubernetes/cri-api 는 거의 한 파일입니다. pkg/apis/runtime/v1/api.proto
에 RPC 35개와 메시지가 다 있고, 나머지는 생성된 Go 코드입니다.
1.7 과 2.x 의 차이 (소스로 확인한 것만)
- CRI 의 자리. 1.7.27 은
pkg/cri/아래에 구현이 두 벌 있었습니다. 기본server/(Go 파일 108개)와 실험용sbserver/(98개)이고, 환경 변수ENABLE_CRI_SANDBOXES로 골랐습니다 (pkg/cri/cri.go:102). 2.x 는 샌드박스 쪽 구현을 기본으로 삼아internal/cri/로 옮겼고, 플러그인도 셋으로 나눴습니다. gRPC 를 받는io.containerd.grpc.v1.cri, 그리고 그것이 의존하는io.containerd.cri.v1.runtime·io.containerd.cri.v1.images입니다(cmd/containerd/builtins/cri.go). - pause 를 띄우는 주체. 1.7 의
RunPodSandbox는 한 함수에서 pause 컨테이너를 만들고(sandbox_run.go:178), CNI 를 부르고(338행), 태스크를 시작합니다(481행). 2.x 는 네트워크까지만 CRI 서버가 하고, pause 는podsandbox라는 샌드박스 컨트롤러 플러그인에 맡깁니다(4.2절). - 설정 판. 2.x 는 설정 판이 4 입니다(
version/version.go:41). 판 2 설정도 읽되 기동 때마다 판 3·4 로 옮겨 적용합니다(config.go:48의migrations). 판 4 는 서버 속성([grpc]·[ttrpc]등)을io.containerd.server.v1.*플러그인 설정으로 옮긴 판입니다.
4. 핵심 경로 따라가기
4.1 데몬은 플러그인 그래프로 조립된다
containerd 의 거의 모든 기능은 플러그인입니다. 각 패키지가 init() 에서 registry.Register 로 자신을 등록하고,
builtins.go 는 그 패키지들을 빈 import(_)로
끌어들이기만 합니다. 빌드에 넣을지 말지가 import 한 줄로 정해집니다.
- 플러그인은 타입과 ID 로 불립니다. 타입은
io.containerd.grpc.v1,io.containerd.snapshotter.v1,io.containerd.cri.v1같은 문자열입니다(plugins/types.go:35부근). 설정 파일의[plugins."io.containerd.cri.v1.images"]는 「타입.ID」 를 그대로 쓴 것입니다. - 등록에는
Requires(필요한 타입 목록)가 있습니다. CRI gRPC 플러그인은 CRI 서비스·샌드박스 컨트롤러·NRI·이벤트·lease·transfer·shim 등 열한 가지 타입을 요구합니다(plugins/cri/cri.go:46init). - 데몬은
LoadPlugins에서registry.Graph(...)로 의존 순서대로 줄을 세우고(386행),New의 반복문(161행)에서 하나씩p.Init합니다(189행). 그래프 정렬 자체는 별도 모듈github.com/containerd/pluginv1.1.0 에 있어 이 저장소에는 없습니다. - 서비스는 대개 두 겹입니다. 실제 일을 하는
io.containerd.service.v1타입의 「local」 구현 (plugins/services/tasks/local.go:92)과, 그것을 gRPC 로 내보내는 얇은io.containerd.grpc.v1포장(containers/service.go:34)입니다.
왜 두 겹일까요? CRI 플러그인이 답입니다. initCRIService 는
containerd 클라이언트를 만들되 containerd.WithInMemoryServices(ic)(121행)를 넘깁니다. 바깥 ctr·nerdctl 은 소켓을 거쳐 gRPC 포장을 부르고,
같은 프로세스 안의 CRI 는 local 구현을 직접 부릅니다. 직렬화 한 번을 아끼면서도 클라이언트 코드는 하나로 유지하려는 설계입니다.
CRI 요청이 쓰는 이름공간도 이때 k8s.io 로 고정됩니다. 그래서 ctr -n k8s.io 로 봐야 쿠버네티스 컨테이너가 보입니다.
4.2 RunPodSandbox: 네트워크가 먼저, pause 는 컨트롤러가
CRI 규약의 첫 RPC 는 RunPodSandbox 입니다.
「성공하면 샌드박스가 ready 여야 한다」는 한 줄이 계약의 전부이고, 샌드박스가 무엇인지는 런타임이 정합니다. containerd 의 답은
「네트워크 네임스페이스 + 그것을 붙잡고 있는 pause 컨테이너」입니다.
criService.RunPodSandbox는 먼저 샌드박스 ID 로 lease 를 만듭니다(97행). 이 lease 가 살아 있는 동안 샌드박스가 쓰는 콘텐츠·스냅샷은 GC 대상에서 빠집니다.hostNetwork가 아니면netns.NewNetNS로/var/run/netns/아래에 이름 있는 네트워크 네임스페이스를 만듭니다(209행).- 이어서
setupPodNetwork가 CNI 의Setup을 부릅니다(496행). 컨테이너가 하나도 뜨기 전에 네트워크부터 붙는 셈입니다. 받은 IP 는 샌드박스 상태에 캐시해 두고,PodSandboxStatus때마다 네임스페이스를 뒤지지 않게 합니다. - 그다음 샌드박스 서비스의
CreateSandbox(281행)와StartSandbox(299행)를 부릅니다. 여기서 실제 일은 샌드박스 컨트롤러가 합니다. - 기본 컨트롤러
podsandbox의Start가 pause 이미지를 찾고(86행), 그 이미지 위에 스냅샷을 준비해 컨테이너를 만들고(209행), 표준 입출력 없이 태스크를 만들어(253행) 종료 감시(Wait, 270행)를 건 뒤 시작합니다(295행).
왜 이렇게 나눴을까요? 샌드박스를 「pause 컨테이너」로 못 박지 않기 위해서입니다. 컨트롤러가 플러그인이므로 VM 기반 런타임처럼 샌드박스가
가상 머신인 구현도 같은 CRI 서버 아래에 끼울 수 있습니다. pause 이미지 이름이 설정에서 sandbox_image 한 줄이 아니라
pinned_images 표의 sandbox 항목으로 바뀐 것도(설정 구조체 config.go:307)
같은 흐름입니다. 주석은 이것을 「CRI 클라이언트가 지우면 안 되는 이미지들」로 정의하고, 키로 다른 플러그인이 찾아 쓰게 합니다.
pause 는 그중 하나일 뿐입니다.
4.3 CreateContainer·StartContainer → tasks → shim → runc
CreateContainer는 CRI 의ContainerConfig를 OCI 런타임 명세로 바꾸고,WithNewSnapshot(378행)으로 이미지 위 쓰기 층을 준비해client.NewContainer(462행)를 부릅니다. 이 단계는 메타데이터만 만듭니다. 프로세스는 아직 없습니다.StartContainer는 샌드박스가 태스크 API 주소를 갖고 있으면 그것을 옵션으로 붙여(197행)container.NewTask(206행) →task.Start(247행)를 부릅니다.- 클라이언트의
NewTask는handleMounts(328행)에서 스냅샷터의Mounts를 받아 rootfs 마운트 목록을 요청에 싣습니다. tasks 서비스의local.Create가 이를 받아 runtime v2 의TaskManager.Create로 넘깁니다. ShimManager.Start와binary.Start가 shim 바이너리를start하위 명령으로 실행합니다. shim 은 자기 ttrpc 소켓 주소를 돌려주고 데몬에서 떨어져 나갑니다.- shim 쪽
manager.Start에 이 글에서 가장 중요한 몇 줄이 있습니다.
grouping := id
spec, err := readSpec()
...
for _, group := range groupLabels {
if groupID, ok := spec.Annotations[group]; ok {
grouping = groupID
break
}
}
...
s, err := newShimSocket(ctx, socketDir, opts.GetContainerdGrpcAddress(), grouping, false)
if err != nil {
if errdefs.IsAlreadyExists(err) {
params.Address = s.addr
return ¶ms, nil
출처: containerd v2.3.5 cmd/containerd-shim-runc-v2/manager/manager_linux.go, Apache-2.0
groupLabels 에는 io.kubernetes.cri.sandbox-id 가 있습니다(67행). 같은 파드의 컨테이너는 같은 샌드박스 ID 로 같은 소켓 이름을 얻고,
소켓이 이미 있으면 새 shim 을 띄우지 않고 기존 주소를 돌려줍니다. 그래서 shim 은 컨테이너마다가 아니라 파드마다 하나입니다.
6절의 실측이 이것을 그대로 보여 줍니다.
6. shim 의 service.Create 는
runc.NewContainer 에서
번들 아래 rootfs 에 overlay 를 shim 이 직접 마운트하고(120행), Init.Create
가 runc create 를 실행합니다(149행). Start 는 start 에서 runc start 입니다.
왜 shim 을 따로 둘까요? 컨테이너 프로세스의 부모가 containerd 이면, containerd 를 재시작하거나 올려 받을 때 모든 컨테이너가 함께 흔들립니다. shim 이 부모 역할(종료 코드 수거, 표준 입출력 유지)을 맡으면 데몬은 언제든 내려갔다 올라와 소켓으로 다시 붙을 수 있습니다. runc 를 상주시키지 않는 것도 같은 이유입니다. 오래 사는 프로세스가 적을수록 업그레이드가 쉽습니다.
4.4 이미지 pull 과 스냅샷: 레이어가 rootfs 가 되기까지
- CRI 의
PullImage는CRIImageService.PullImage로 옵니다. 2.1 부터 기본은 transfer 서비스이고,use_local_image_pull을 켜면 예전client.Pull경로를 씁니다(184행). transfer 경로는pullImageWithTransferService→Transfer(326행) →localTransferService.pull입니다. - 내려받은 매니페스트·설정·레이어는 모두 콘텐츠 주소 저장소에 들어갑니다. 경로는 다이제스트 그대로
blobs/sha256/<hex>입니다(blobPath). 같은 레이어를 쓰는 이미지가 여럿이어도 디스크에는 한 번만 있습니다. - 풀기는
Unpacker.unpack이 레이어마다 스냅샷터Prepare(415행) → diff 서비스Apply(tar 를 풀어 넣기, 542행) →Commit(494행)을 반복합니다. 커밋된 스냅샷의 이름은 그 레이어까지의 chainID 라서, 같은 레이어 사슬은 이미지가 달라도 한 번만 풀립니다. - 컨테이너를 만들 때
WithNewSnapshot이 맨 위 chainID 를 부모로Prepare해 비어 있는 active 스냅샷을 만듭니다(276행). 이미지가 아직 안 풀려 있으면 CRI 쪽 래퍼가unpackImage로 풀고 다시 시도합니다. - 마지막으로 overlayfs 스냅샷터의
mounts가 마운트 옵션을 만듭니다.
if s.Kind == snapshots.KindActive {
options = append(options,
fmt.Sprintf("workdir=%s", o.workPath(s.ID)),
fmt.Sprintf("upperdir=%s", o.upperPath(s.ID)),
)
}
...
parentPaths := make([]string, len(s.ParentIDs))
for i := range s.ParentIDs {
parentPaths[i] = o.upperPath(s.ParentIDs[i])
}
options = append(options, fmt.Sprintf("lowerdir=%s", strings.Join(parentPaths, ":")))
출처: containerd v2.3.5 plugins/snapshots/overlay/overlay.go, Apache-2.0
부모 스냅샷들의 fs 디렉터리가 그대로 lowerdir 가 되고, 자기 fs·work 가 upperdir·workdir 가 됩니다. 컨테이너가 쓰는
파일은 모두 upper 에만 쌓이므로 같은 이미지를 쓰는 컨테이너 백 개도 아래층을 공유합니다. 같은 발상을 VM 디스크에 쓴 이야기는
학생 VM 이 뜨기까지 4에 있습니다.
이 모든 것을 이어 주는 장부가 bolt DB 입니다. 이름공간 아래에 이미지·컨테이너·스냅샷·콘텐츠·lease 버킷이 있고
(buckets.go:60 의 주석 도면),
GarbageCollect 는 어떤 이미지·컨테이너·lease 에서도
닿지 않는 콘텐츠와 스냅샷을 지웁니다. pull 도중의 레이어가 지워지지 않는 것은 그 작업이 lease 를 쥐고 있기 때문입니다.
「참조가 없으면 지운다」를 기본으로 두고, 진행 중인 작업만 lease 로 예외를 거는 설계입니다.
5. 사용자 기여와의 연결
글쓴이는 2026-10-17 현재 containerd 와 kubernetes/cri-api 에 낸 PR 이 없습니다. 기여 원장에는 「컨테이너 런타임 저수준(containerd·CRI-O·runc 등)」을 조사 중인 분야로 적어 두었고, 이 글은 그 준비 운동입니다. 8절의 후보는 이 글을 쓰며 소스에서 직접 찾은 것입니다.
한 가지 미리 적어 둡니다. kubernetes/cri-api 는 kubernetes/kubernetes 의 staging/src/k8s.io/cri-api 를 봇이 옮겨 싣는 저장소라서,
이 저장소에 직접 보낸 PR 은 무시됩니다(저장소 CONTRIBUTING). CRI 쪽 수정은 kubernetes/kubernetes 로 보내야 하고, 그러면 쿠버네티스의
AI 사용 규칙(PR 본문 고지, AI 가 쓴 커밋 메시지·공동 저자 표기 금지)을 따릅니다.
6. 실측: 1.7 워커와 2.3 워커를 권한 없이 읽기
2026-10-17, 작은 클러스터(노드 8대)에서 읽기만 했습니다. 노드 런타임 분포는 kubectl get nodes -o custom-columns=...containerRuntimeVersion
으로, 노드 안은 일반 사용자 ssh 로 ps·mount·/etc/containerd/config.toml 읽기만 했습니다. /run/containerd·/var/lib/containerd 는
권한이 없어 목록을 못 읽었고(sudo 는 쓰지 않음), crictl 은 설치돼 있지 않았습니다.
| 항목 | 워커 A (containerd 1.7.27) | 워커 B (containerd 2.3.5) |
|---|---|---|
| 같은 판 노드 수 | 4대 | 4대 |
| runc | 1.2.5 | 1.5.1 |
| 실행 중 파드(그중 hostNetwork) | 22 (10) | 31 (6) |
| 앱 컨테이너 수 | 28 | 42 |
containerd-shim-runc-v2 프로세스 |
22 | 31 |
pause 프로세스 |
22 | 31 |
| 태스크 디렉터리 수(디렉터리 링크 수 − 2) | 50 = 22 + 28 | 74 |
| overlay 마운트 | 50 | 74 |
| 그중 1층(lowerdir 하나) | 22 | 33 |
| 가장 깊은 lowerdir | 42층 | 52층 |
/var/run/netns 항목 |
12 = 22 − 10 | 25 = 31 − 6 |
| shim RSS 평균 / containerd RSS | 15.7 MiB / 74 MiB | 12.6 MiB / 153 MiB |
| 설정 판 | version = 2 |
version = 4 |
| CRI 설정 블록 | io.containerd.grpc.v1.cri 하나 |
io.containerd.cri.v1.images·.runtime 으로 나뉨 |
| pause 이미지 | sandbox_image = pause:3.8 |
pinned_images.sandbox = pause:3.10.2 |
| snapshotter / cgroup | overlayfs / SystemdCgroup = true |
overlayfs / SystemdCgroup = true |
shim 한 줄은 이렇게 생겼습니다(ID 는 가림).
containerd-shim /usr/bin/containerd-shim-runc-v2 -namespace k8s.io -id 0c1e…(64자) -address /run/containerd/containerd.sock
overlay on /run/containerd/io.containerd.runtime.v2.task/k8s.io/0c1e…/rootfs type overlay (rw,relatime,lowerdir=/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/1/fs,upperdir=…/snapshots/3774/fs,workdir=…)
읽은 것:
- shim 수 = 파드 수. 두 판 모두 정확히 같았습니다. 4.3절의
sandbox-id묶기가 1.7 에도 이미 있었다는 뜻입니다.-id에 찍힌 값은 컨테이너가 아니라 샌드박스 ID 입니다.ps의 comm 이containerd-shim으로 잘린 것은 리눅스가 프로세스 이름을 15자로 자르기 때문입니다. - hostNetwork 파드에도 pause 가 있습니다. pause 22개 중 10개는 hostNetwork 파드의 것이고, 네트워크 네임스페이스 수(12)는 정확히 나머지 파드 수입니다. 4.2절의 「hostNetwork 가 아니면 netns 를 만든다」 분기가 숫자로 보입니다.
- 태스크 디렉터리 50 = 샌드박스 22 + 컨테이너 28 입니다. pause 도 컨테이너 하나로 셈합니다. 워커 B 는 31 + 42 = 73 으로 하나가 남는데, 어떤 컨테이너의 것인지는 목록 권한이 없어 확인하지 못했습니다(미확인).
- 1층 overlay 22개 = pause 22개. pause 이미지는 레이어가 하나라 lowerdir 도 하나입니다. 워커 B 의 33개는 pause 31개와 단일 레이어 이미지 둘로 보입니다(추정).
- 워커 B 의 lowerdir
snapshots/1은 이 노드에서 처음 풀린 스냅샷이고, upper 번호 3774 는 그 사이에 만들어지고 지워진 스냅샷이 그만큼 많았다는 뜻입니다. 번호는storage.CreateSnapshot이 bolt 버킷의NextSequence로 매기므로(252행) 지워진 번호를 다시 쓰지 않습니다. - 1.7 노드의 설정을 2.3.5 데몬에 그대로 주면 판 2 → 4 이전을 기동 때마다 수행하고 경고를 남깁니다(3절). 실제로 올려 보지는 않았습니다(미실측).
kubelet 쪽은 두 판 모두 containerRuntimeEndpoint: unix:///run/containerd/containerd.sock(2.x 노드는 /var/run/... 표기),
cgroupDriver: systemd 였습니다. 런타임의 SystemdCgroup = true 와 짝이 맞습니다.
7. 어디부터 읽을까
api.proto의RuntimeService·ImageService주석만 훑습니다. 계약이 무엇을 요구하고 무엇을 런타임에 맡기는지가 보입니다.plugins/cri/cri.go와server.go로 플러그인이 어떻게 엮이는지 봅니다.sandbox_run.go→podsandbox/sandbox_run.go순서로 파드 하나를 띄웁니다.container_start.go→shim_manager.go→manager_linux.go로 프로세스까지 내려갑니다.unpacker.go와overlay.go로 디스크 쪽을 봅니다.docs/snapshotters/·docs/content-flow.md를 곁들이면 좋습니다.
k3s·RKE2 처럼 containerd 를 품고 배포하는 배포판이 이 구조를 어떻게 감싸는지는 k3s·RKE2·k0s 구조 비교에서 이어 볼 수 있습니다.
8. 첫 기여하기 좋은 곳
- CRI 설정 문서의 판 번호가 낡았습니다.
docs/cri/config.md의 「Config versions」 절은 판 3 이 최신이고 판 2 는 「판 3 으로 자동 변환된다」고 적고, 「Full configuration」 예시도version = 3입니다. 같은 태그의RELEASES.md는 최신이 판 4(v2.3.0 부터)라고 적고, 코드의ConfigVersion도 4 입니다. 문서 PR 하나로 맞출 수 있습니다. - TODO 두 개.
plugins/cri/cri.go의initCRIService끝에 「Closer 는 어디서 불리나」, 「containerd 를 어떻게 멈출지」라는 TODO 가 남아 있습니다. 실제로 종료 경로에서Close가 불리는지 추적해 이슈로 정리하는 것만으로도 도움이 됩니다. - 샌드박스 플랫폼 판별의 임시 처리.
sandbox_run.go안 180행 부근에 「컨트롤러가 플랫폼을 일찍 알려 주지 못해 Linux 절 유무로 추정한다, 이것은 hack 이다」라는 주석이 있습니다. 컨트롤러 인터페이스 논의가 필요해 첫 PR 로는 크지만, 이슈 맥락을 읽기 좋은 입구입니다. - cri-api README 의 판마다의 변경 목록은 v1.32 에서 멈춰 있습니다(v0.34.0 태그 기준, master 는 미확인). 보낼 곳은 kubernetes/kubernetes 의 staging 디렉터리입니다.
- 라벨: containerd 는
exp/beginner라벨을 씁니다(2026-10-17 열린 것 2건). AI 규칙은 CONTRIBUTING 의 「Automated and AI-generated contributions」 절에 있습니다. PR 은 사람이 열고, AI 로 만든 코드·본문·댓글은 작성자가 모두 검토해야 합니다.
9. 한계
- kubelet 소스는 열지 않았습니다. kubelet 이 어떤 순서로 CRI 를 부르는지(예:
PullImage와CreateContainer사이의 재시도)는 이 글의 범위 밖입니다. - NRI, CDI(장치 주입), 사용자 네임스페이스, 체크포인트, 스트리밍(exec·attach·port-forward) 서버, Windows 경로는 다루지 않았습니다.
- transfer 서비스와 로컬 pull 의 차이, GC 스케줄러의 주기·임계값은 함수 이름까지만 보고 세부는 읽지 않았습니다.
- 실측은 권한 없는 읽기라 콘텐츠 저장소 크기, bolt DB 내용, 스냅샷 수는 미실측입니다. 1.7 → 2.3 판 올림을 실제로 해 본 기록도 없습니다(미실측).
- 1.7 과의 비교는 v1.7.27 소스의 파일 구성과 몇 함수만 대조한 것입니다.
티티 한마디. kubelet 은 주문서만 내고, 테이블마다 담당 직원(shim)이 붙어요. 그래서 셰프(containerd)가 잠깐 쉬어도 손님 접시는 그대로예요!
참고
모든 자료는 2026-10-17 에 열람했습니다.
- kubernetes/cri-api v0.34.0
pkg/apis/runtime/v1/api.proto, CONTRIBUTING.md - containerd v2.3.5:
plugins/cri/cri.go,cmd/containerd/server/server.go,internal/cri/server/sandbox_run.go,internal/cri/server/podsandbox/sandbox_run.go,core/runtime/v2/shim_manager.go,cmd/containerd-shim-runc-v2/manager/manager_linux.go,plugins/snapshots/overlay/overlay.go,core/unpack/unpacker.go,docs/cri/architecture.md,docs/containerd-2.0.md,RELEASES.md,CONTRIBUTING.md - containerd v1.7.27:
pkg/cri/cri.go,pkg/cri/server/sandbox_run.go - 인용 코드: containerd v2.3.5, Apache License 2.0.