- Authors

- Name
- Youngju Kim
- @fjvbn20031
containerd 컨테이너 생명주기 관리
containerd에서 컨테이너의 생성부터 종료까지의 전체 생명주기를 분석합니다. 컨테이너 메타데이터와 실행 프로세스(Task)의 분리, shim을 통한 프로세스 관리, 다양한 런타임 클래스의 통합 방식을 살펴봅니다.
1. 컨테이너와 Task의 분리
1.1 핵심 개념
containerd는 컨테이너의 메타데이터와 실행 상태를 분리합니다:
Container (메타데이터):
- ID, 이미지 참조, 스냅샷 키
- OCI 런타임 스펙
- 레이블, 확장 데이터
- BoltDB에 영구 저장
Task (실행 상태):
- 실제 실행 중인 프로세스
- PID, 상태 (created/running/stopped)
- stdin/stdout/stderr
- shim 프로세스가 관리
1.2 분리의 장점
분리 설계의 이점:
1. 컨테이너 메타데이터는 Task 없이 존재 가능
- 컨테이너를 생성하고 나중에 시작 가능
- 중지된 컨테이너의 메타데이터 보존
2. containerd 재시작에 독립적
- Task는 shim이 관리하므로 containerd가 재시작되어도 유지
- 재시작 후 기존 shim에 재연결
3. 다양한 런타임 지원
- Container 객체는 런타임에 무관
- Task 생성 시 런타임 선택
2. 컨테이너 생성
2.1 생성 과정
컨테이너 생성 플로우:
1. 이미지에서 OCI 스펙 생성
|
v
2. 스냅샷 준비
- 이미지 스냅샷 체인에 Active 스냅샷 추가
- 컨테이너의 쓰기 가능 레이어
|
v
3. 컨테이너 메타데이터 저장
- BoltDB에 Container 레코드 생성
- ID, 이미지, 스냅샷, 런타임, 스펙 저장
|
v
4. 컨테이너 객체 반환
(아직 프로세스는 시작되지 않음)
2.2 OCI 런타임 스펙
containerd는 OCI 런타임 스펙을 생성하여 컨테이너 실행 환경을 정의합니다:
OCI 런타임 스펙 주요 섹션:
ociVersion: "1.0.2"
process:
terminal: false
user: uid=0, gid=0
args: ["/bin/sh"]
env: ["PATH=/usr/local/sbin:..."]
cwd: "/"
capabilities: ...
rlimits: ...
root:
path: "rootfs"
readonly: false
hostname: "container-abc"
mounts:
- destination: "/proc"
type: "proc"
source: "proc"
- destination: "/dev"
type: "tmpfs"
source: "tmpfs"
linux:
namespaces:
- type: "pid"
- type: "network"
- type: "ipc"
- type: "uts"
- type: "mount"
resources:
memory:
limit: 536870912
cpu:
shares: 1024
quota: 100000
period: 100000
cgroupsPath: "/kubelet/pod-abc/container-xyz"
2.3 스펙 생성기 (Spec Opts)
containerd의 스펙 생성 패턴:
Spec Opts는 OCI 스펙을 점진적으로 구성하는 함수 체인입니다:
WithImageConfig(image) -> 이미지의 CMD, ENV, WORKDIR 적용
WithHostNamespace(ns) -> 호스트 네임스페이스 공유
WithMemoryLimit(limit) -> 메모리 제한 설정
WithCPUs(cpus) -> CPU 제한 설정
WithMounts(mounts) -> 마운트 포인트 추가
WithProcessArgs(args) -> 프로세스 인자 설정
WithRootfsPropagation(p) -> rootfs 마운트 전파 설정
WithSeccompProfile(p) -> Seccomp 프로파일 적용
WithApparmorProfile(p) -> AppArmor 프로파일 적용
3. Task 실행
3.1 Task 생성
Task 생성 플로우:
1. 컨테이너의 런타임 타입 확인
(예: io.containerd.runc.v2)
|
v
2. shim 바이너리 실행
(containerd-shim-runc-v2 start)
|
v
3. shim이 ttrpc 소켓 주소 반환
|
v
4. containerd가 shim에 Create 요청
- OCI 스펙 전달
- 번들 경로 전달
|
v
5. shim이 runc create 실행
- 네임스페이스 생성
- cgroup 설정
- rootfs 마운트
- 프로세스 생성 (아직 시작 안 됨)
|
v
6. Task 상태: Created
3.2 Task 시작
Task 시작:
1. containerd가 shim에 Start 요청
|
v
2. shim이 runc start 실행
- 컨테이너 프로세스의 init 프로세스 시작
- exec.fifo를 통해 동기화
|
v
3. Task 상태: Running
- PID 할당됨
- stdin/stdout/stderr 연결됨
3.3 Task 상태 전이
Task 상태 머신:
Created
|
| Start()
v
Running
|
+-- Kill(signal) -> 시그널 전송
|
+-- Pause() -> Paused
| |
| +-- Resume() -> Running
|
+-- 프로세스 종료 -> Stopped
|
v
Stopped
|
| Delete()
v
(삭제됨)
3.4 Exec (추가 프로세스)
Exec 동작:
기존 실행 중인 컨테이너에 새 프로세스를 추가:
1. ExecProcess 생성
- 새 프로세스의 스펙 정의 (args, env, user)
- execID 할당
|
v
2. shim에 Exec 요청
|
v
3. runc exec 실행
- 기존 컨테이너의 네임스페이스에 진입
- 새 프로세스 시작
|
v
4. 독립적으로 stdin/stdout/stderr 관리
사용 예: kubectl exec, docker exec
4. Shim 생명주기
4.1 Shim 시작
Shim 시작 과정:
1. containerd가 shim 바이너리를 fork/exec
containerd-shim-runc-v2 -namespace k8s.io \
-id container-abc \
-address /run/containerd/containerd.sock \
start
|
v
2. shim이 자신을 데몬화
- 부모 프로세스에서 분리 (setsid)
- containerd와 독립적으로 실행
|
v
3. ttrpc Unix 소켓 생성
/run/containerd/s/abc123...
|
v
4. 소켓 주소를 stdout으로 출력
containerd가 이 주소를 읽어 연결
4.2 Shim 역할 상세
Shim의 주요 책임:
1. 프로세스 관리:
- 컨테이너 프로세스의 부모 역할
- wait4()로 종료 상태 수집
- OOM 이벤트 감지 및 보고
2. I/O 관리:
- stdin/stdout/stderr FIFO 관리
- 로그 드라이버와 연결
- I/O 복사 (containerProcess <-> FIFO)
3. containerd와의 통신:
- ttrpc를 통한 명령 수신
- 이벤트 보고 (TaskExit 등)
- 상태 조회 응답
4. containerd 재시작 대응:
- containerd가 재시작되어도 계속 실행
- 재시작된 containerd가 기존 shim에 재연결
- 상태 복구
4.3 Shim 종료
Shim 종료:
1. Task Delete 요청 수신
|
v
2. 컨테이너 리소스 정리
- cgroup 삭제
- 네임스페이스 정리
- rootfs 언마운트
|
v
3. ttrpc 소켓 종료
|
v
4. shim 프로세스 종료
5. Checkpoint/Restore
5.1 Checkpoint
Checkpoint 동작:
실행 중인 컨테이너의 상태를 스냅샷으로 저장:
1. CRIU (Checkpoint/Restore in Userspace) 호출
|
v
2. 프로세스 메모리 덤프
- 메모리 페이지 저장
- 파일 디스크립터 상태 저장
- 네트워크 연결 상태 저장
|
v
3. 체크포인트 이미지 생성
- CRIU 이미지 파일 세트
- 컨테이너 스펙과 함께 저장
|
v
4. 선택적으로 컨테이너 중지
사용 사례:
- 라이브 마이그레이션
- 빠른 시작 (사전 워밍업된 상태에서 복원)
- 디버깅 (특정 시점 상태 캡처)
5.2 Restore
Restore 동작:
1. 체크포인트 이미지 로드
|
v
2. 새 컨테이너 환경 준비
- 네임스페이스 생성
- rootfs 마운트
|
v
3. CRIU restore 실행
- 메모리 페이지 복원
- 프로세스 상태 복원
- 파일 디스크립터 재연결
|
v
4. 프로세스 실행 재개
6. 런타임 클래스
6.1 다양한 런타임 지원
containerd는 shim 인터페이스를 통해 다양한 런타임을 지원합니다:
런타임 클래스별 비교:
+----------+------------+-----------+----------+----------+
| 런타임 | 격리 수준 | 오버헤드 | 시작시간 | 호환성 |
+----------+------------+-----------+----------+----------+
| runc | 네임스페이스 | 최소 | 빠름 | 최고 |
| kata | 경량 VM | 중간 | 중간 | 높음 |
| gVisor | 유저 커널 | 낮음 | 빠름 | 중간 |
| Wasm | Wasm 샌드박스 | 최소 | 매우빠름 | 제한적 |
+----------+------------+-----------+----------+----------+
6.2 runc
runc:
- 기본 OCI 런타임
- Linux 네임스페이스와 cgroup 기반 격리
- 호스트 커널을 직접 사용
- 가장 낮은 오버헤드
- 모든 Linux 컨테이너 워크로드에 적합
shim: containerd-shim-runc-v2
config.toml:
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
runtime_type = "io.containerd.runc.v2"
6.3 Kata Containers
Kata Containers:
- 경량 VM 내부에서 컨테이너 실행
- QEMU/Cloud-Hypervisor/Firecracker 사용
- 별도 게스트 커널로 강력한 격리
- 멀티테넌트 환경에 적합
- VM 오버헤드 존재
shim: containerd-shim-kata-v2
config.toml:
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.kata]
runtime_type = "io.containerd.kata.v2"
6.4 gVisor
gVisor (runsc):
- 유저 스페이스 커널 (Sentry)
- 시스템 콜을 인터셉트하여 재구현
- 호스트 커널 공격 표면 축소
- ptrace 또는 KVM 기반 동작
- 일부 시스템 콜 미지원
shim: containerd-shim-runsc-v1
config.toml:
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runsc]
runtime_type = "io.containerd.runsc.v1"
6.5 WebAssembly (Wasm)
Wasm 런타임:
- WebAssembly 바이너리를 컨테이너로 실행
- Wasmtime, WasmEdge 등 사용
- 매우 빠른 시작 시간 (밀리초 단위)
- 최소 메모리 사용
- 포터블 바이너리
- 제한된 시스템 접근 (WASI)
shim: containerd-shim-wasm
config.toml:
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.wasm]
runtime_type = "io.containerd.wasm.v1"
6.6 RuntimeClass 선택
Kubernetes RuntimeClass 연동:
1. RuntimeClass 리소스 정의:
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: kata
handler: kata
2. Pod에서 RuntimeClass 지정:
spec:
runtimeClassName: kata
containers:
- name: app
image: nginx
3. containerd가 handler에 매칭되는 런타임 선택:
handler "kata" -> containerd.runtimes.kata 설정
-> containerd-shim-kata-v2 실행
7. 정리
containerd의 컨테이너 생명주기 관리는 Container(메타데이터)와 Task(실행)의 분리, shim을 통한 프로세스 격리, 다양한 런타임 클래스 지원이 핵심입니다. shim의 데몬화 설계로 containerd 재시작에도 컨테이너가 유지되며, OCI 런타임 스펙 기반의 표준화된 인터페이스로 runc, Kata, gVisor, Wasm 등 다양한 런타임을 통합합니다. 다음 글에서는 containerd의 네트워킹과 스토리지를 분석합니다.