- Authors

- Name
- Youngju Kim
- @fjvbn20031
containerd CRI 구현: Kubernetes 런타임 통합
containerd는 Kubernetes CRI(Container Runtime Interface)를 내장 플러그인으로 구현합니다. 이 글에서는 CRI gRPC 서비스의 구현 상세, Pod Sandbox 관리, 컨테이너 스펙 변환, 스트리밍 API, RuntimeClass, NRI를 분석합니다.
1. CRI gRPC 서비스
1.1 서비스 구조
CRI는 두 개의 gRPC 서비스로 구성됩니다:
CRI gRPC 서비스:
RuntimeService:
+-- PodSandbox 관리
| RunPodSandbox
| StopPodSandbox
| RemovePodSandbox
| PodSandboxStatus
| ListPodSandbox
|
+-- Container 관리
| CreateContainer
| StartContainer
| StopContainer
| RemoveContainer
| ListContainers
| ContainerStatus
| UpdateContainerResources
|
+-- 스트리밍
| ExecSync
| Exec
| Attach
| PortForward
|
+-- 런타임 정보
Status
Version
ImageService:
+-- PullImage
+-- ListImages
+-- ImageStatus
+-- RemoveImage
+-- ImageFsInfo
1.2 소켓 구성
CRI 소켓:
containerd는 동일한 gRPC 소켓에서 CRI를 제공:
/run/containerd/containerd.sock
kubelet 설정:
--container-runtime-endpoint=unix:///run/containerd/containerd.sock
CRI 플러그인이 containerd 서버에 CRI 서비스를 등록:
플러그인 ID: io.containerd.grpc.v1.cri
2. Pod Sandbox
2.1 Pod Sandbox 개념
Pod Sandbox는 Pod의 격리 환경을 나타냅니다:
Pod Sandbox 구성:
Pod Sandbox = Pause 컨테이너 + 공유 네임스페이스
공유 리소스:
- 네트워크 네임스페이스 (같은 IP, 포트 공간)
- IPC 네임스페이스 (프로세스 간 통신)
- UTS 네임스페이스 (호스트명)
- PID 네임스페이스 (선택적)
격리 리소스:
- 마운트 네임스페이스 (컨테이너별)
- cgroup (컨테이너별 리소스 제한)
2.2 RunPodSandbox 흐름
RunPodSandbox 처리:
1. Sandbox 메타데이터 생성
- ID 생성
- 로그 디렉토리 생성
|
v
2. Pause 이미지 풀
- sandbox_image 설정에서 이미지 결정
- 기본값: registry.k8s.io/pause:3.9
|
v
3. Pause 컨테이너 스냅샷 준비
|
v
4. OCI 스펙 생성
- Pause 컨테이너용 최소 스펙
- 호스트명, DNS 설정 포함
|
v
5. 네트워크 네임스페이스 생성
- /var/run/netns/ 에 네임스페이스 파일 생성
|
v
6. CNI 플러그인 호출
- 네트워크 인터페이스 생성
- IP 할당
|
v
7. Pause 컨테이너 Task 생성 및 시작
|
v
8. Sandbox 상태를 SANDBOX_READY로 설정
2.3 Pause 컨테이너
Pause 컨테이너의 역할:
1. 네임스페이스 보유자:
- 네트워크 네임스페이스의 첫 번째 프로세스
- App 컨테이너가 종료되어도 네임스페이스 유지
- 네임스페이스의 생명주기를 Pod에 바인딩
2. PID 1 역할:
- Pod PID 네임스페이스의 init 프로세스
- 좀비 프로세스 수거 (reap)
- 최소 리소스 사용 (약 1MB)
3. 동작:
- pause() 시스템 콜로 무한 대기
- SIGTERM 수신 시 종료
3. 컨테이너 스펙 변환
3.1 CRI 요청에서 OCI 스펙으로
스펙 변환 과정:
CRI ContainerConfig:
- Image
- Command, Args
- Envs
- Mounts
- Devices
- SecurityContext
- Resources
|
v
containerd CRI 플러그인이 변환
|
v
OCI Runtime Spec:
- root (이미지 스냅샷 경로)
- process (커맨드, env, capabilities)
- mounts (볼륨, 특수 파일시스템)
- linux.resources (cgroup 설정)
- linux.namespaces (Sandbox와 공유)
- hooks (OCI 훅)
3.2 리소스 변환
Kubernetes 리소스 -> OCI 리소스 변환:
CPU:
requests.cpu: 250m
-> linux.resources.cpu.shares = 256
(1000m = 1024 shares 기준)
limits.cpu: 500m
-> linux.resources.cpu.quota = 50000
linux.resources.cpu.period = 100000
(500m/1000m * 100000us)
Memory:
limits.memory: 512Mi
-> linux.resources.memory.limit = 536870912
(바이트 단위)
requests.memory:
-> 스케줄링에만 사용, OCI 스펙에 반영하지 않음
Hugepages:
limits.hugepages-2Mi: 100Mi
-> linux.resources.hugepageLimits:
pageSize: "2MB"
limit: 104857600
3.3 보안 컨텍스트 변환
SecurityContext -> OCI 스펙 변환:
runAsUser: 1000
-> process.user.uid = 1000
runAsGroup: 1000
-> process.user.gid = 1000
readOnlyRootFilesystem: true
-> root.readonly = true
privileged: true
-> 모든 capabilities 부여
-> 모든 디바이스 접근 허용
-> AppArmor/SELinux/Seccomp 비활성화
capabilities:
add: ["NET_ADMIN"]
drop: ["ALL"]
-> process.capabilities 설정
seccompProfile:
type: RuntimeDefault
-> linux.seccomp 프로파일 적용
4. 스트리밍 API
4.1 ExecSync
ExecSync 동작:
동기적으로 컨테이너에서 명령 실행:
1. kubelet이 ExecSync(containerID, cmd, timeout) 호출
|
v
2. containerd가 shim에 Exec 요청
|
v
3. shim이 runc exec 실행
- 컨테이너 네임스페이스에 새 프로세스 생성
|
v
4. stdout/stderr 캡처
|
v
5. 프로세스 종료 대기
|
v
6. exit code + stdout + stderr 반환
사용 사례: liveness/readiness probe, kubectl exec (동기)
4.2 Exec (비동기 스트리밍)
Exec 스트리밍 동작:
1. kubelet이 Exec(containerID, cmd, stdin, stdout, stderr) 호출
|
v
2. containerd가 스트리밍 URL 반환
- 스트리밍 서버 주소: https://node:10250/exec/...
|
v
3. kubelet이 클라이언트에 URL 전달
|
v
4. 클라이언트가 WebSocket/SPDY로 스트리밍 서버 연결
|
v
5. 스트리밍 서버가 containerd에 실제 Exec 수행
|
v
6. stdin/stdout/stderr 양방향 스트리밍
스트리밍 프로토콜:
- SPDY (레거시)
- WebSocket (최신)
4.3 Attach
Attach 동작:
실행 중인 컨테이너의 메인 프로세스에 연결:
1. 스트리밍 URL 생성 (Exec와 유사)
|
v
2. 컨테이너의 stdin/stdout/stderr에 연결
- 새 프로세스를 생성하지 않음
- 기존 프로세스의 I/O에 직접 연결
|
v
3. 양방향 스트리밍
사용 사례: kubectl attach
4.4 PortForward
PortForward 동작:
Pod의 포트에 로컬 트래픽 전달:
1. 스트리밍 URL 생성
|
v
2. Pod의 네트워크 네임스페이스에서 socat/nsenter 실행
- 지정 포트에 TCP 연결
|
v
3. 로컬 포트와 Pod 포트 간 양방향 데이터 전달
구현:
containerd는 Pod의 네트워크 네임스페이스에 진입하여
대상 포트에 TCP 연결을 생성합니다.
사용 사례: kubectl port-forward
5. RuntimeClass
5.1 RuntimeClass 매핑
RuntimeClass 처리:
1. Kubernetes RuntimeClass 리소스:
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: kata
handler: kata
2. kubelet이 CRI RunPodSandbox 호출 시
runtime_handler = "kata" 전달
3. containerd가 handler를 런타임 설정에 매핑:
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.kata]
runtime_type = "io.containerd.kata.v2"
4. 해당 shim 바이너리로 Task 생성:
containerd-shim-kata-v2
5.2 기본 런타임
기본 런타임 설정:
[plugins."io.containerd.grpc.v1.cri".containerd]
default_runtime_name = "runc"
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
runtime_type = "io.containerd.runc.v2"
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
SystemdCgroup = true
Pod에 runtimeClassName이 없으면 기본 런타임(runc) 사용
5.3 RuntimeClass별 오버헤드
RuntimeClass 리소스 오버헤드:
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: kata
handler: kata
overhead:
podFixed:
memory: "160Mi"
cpu: "250m"
오버헤드 처리:
- kubelet이 Pod 리소스에 오버헤드를 추가
- 스케줄러가 오버헤드를 포함하여 노드 선택
- VM 기반 런타임의 고정 비용 반영
6. NRI (Node Resource Interface)
6.1 NRI 개요
NRI는 containerd의 플러그인 확장 메커니즘으로, 컨테이너 생명주기 이벤트에 훅을 등록할 수 있습니다:
NRI 아키텍처:
kubelet -> containerd
|
+-- NRI Plugin 1 (리소스 할당)
+-- NRI Plugin 2 (토폴로지 인식)
+-- NRI Plugin 3 (모니터링)
NRI 플러그인은 컨테이너 생명주기 이벤트를 수신하고
OCI 스펙을 수정할 수 있습니다.
6.2 NRI 훅 포인트
NRI 훅 포인트:
1. RunPodSandbox:
- Pod 생성 시 호출
- Pod 레벨 리소스 할당
2. CreateContainer:
- 컨테이너 생성 시 호출
- OCI 스펙 수정 가능
- CPU 핀닝, 메모리 NUMA 할당 등
3. StartContainer:
- 컨테이너 시작 시 호출
4. UpdateContainer:
- 리소스 업데이트 시 호출
5. StopContainer:
- 컨테이너 중지 시 호출
- 리소스 해제
6. RemoveContainer:
- 컨테이너 삭제 시 호출
6.3 NRI 사용 사례
NRI 활용 사례:
1. CPU/메모리 토폴로지 인식 할당:
- NUMA 노드 인식 CPU 핀닝
- 메모리를 특정 NUMA 노드에 할당
- 토폴로지 매니저와 연동
2. 디바이스 리소스 관리:
- GPU 할당 최적화
- RDMA 리소스 관리
- 디바이스 플러그인 보완
3. 보안 정책 적용:
- 동적 Seccomp 프로파일
- 런타임 보안 규칙 주입
4. 모니터링/감사:
- 컨테이너 시작/종료 이벤트 로깅
- 리소스 사용 추적
7. 이미지 서비스
7.1 이미지 Pull
CRI PullImage 처리:
1. kubelet이 PullImage(imageSpec, authConfig) 호출
|
v
2. containerd가 이미지 참조 해석
- 태그 또는 다이제스트
- 레지스트리 인증 정보 적용
|
v
3. 이미지 다운로드
- Manifest, Config, Layers
- k8s.io 네임스페이스에 저장
|
v
4. 레이어 언패킹
- Snapshotter로 스냅샷 체인 생성
|
v
5. 이미지 참조(imageRef) 반환
7.2 이미지 캐싱
이미지 캐싱:
containerd의 이미지 캐싱:
- Content Store에 레이어가 이미 존재하면 다운로드 건너뜀
- Snapshotter에 스냅샷이 이미 존재하면 언패킹 건너뜀
- 다이제스트 기반 정확한 중복 제거
kubelet의 이미지 정책:
imagePullPolicy: Always
-> 항상 레지스트리 매니페스트 확인 (레이어는 캐시 활용)
imagePullPolicy: IfNotPresent
-> 로컬에 없을 때만 Pull
imagePullPolicy: Never
-> 로컬 이미지만 사용
8. 모니터링과 디버깅
8.1 CRI 메트릭
containerd CRI 관련 메트릭:
container_runtime_cri_operations_total: CRI 연산 수
container_runtime_cri_operations_errors_total: CRI 연산 오류 수
container_runtime_cri_operations_latency_seconds: CRI 연산 지연
containerd 내부 메트릭:
containerd_task_count: 실행 중인 Task 수
containerd_container_count: 컨테이너 수
containerd_image_pull_duration_seconds: 이미지 Pull 소요 시간
8.2 디버깅 도구
디버깅 도구:
1. crictl (CRI CLI):
crictl ps # 컨테이너 목록
crictl pods # Pod 목록
crictl images # 이미지 목록
crictl inspect CONTAINER_ID # 컨테이너 상세
crictl logs CONTAINER_ID # 컨테이너 로그
crictl exec -it CONTAINER_ID /bin/sh # exec
2. ctr (containerd CLI):
ctr -n k8s.io containers list
ctr -n k8s.io tasks list
ctr -n k8s.io images list
3. containerd 로그:
journalctl -u containerd -f
9. 정리
containerd의 CRI 구현은 Kubernetes와 컨테이너 런타임 사이의 핵심 인터페이스입니다. Pod Sandbox를 통한 Pod 수준의 격리, CRI 요청에서 OCI 스펙으로의 정확한 변환, WebSocket/SPDY 기반 스트리밍, RuntimeClass를 통한 다중 런타임 지원, NRI를 통한 유연한 확장이 주요 특징입니다. 이러한 계층화된 설계로 containerd는 Kubernetes의 안정적인 컨테이너 런타임으로 자리매김하고 있습니다.