Split View: containerd 컨테이너 생명주기 관리
containerd 컨테이너 생명주기 관리
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의 네트워킹과 스토리지를 분석합니다.
[containerd] Container Lifecycle Management
containerd Container Lifecycle Management
This post analyzes the complete lifecycle of containers in containerd, from creation to termination. We examine the separation of container metadata and execution processes (Tasks), process management through shims, and integration of various runtime classes.
1. Separation of Container and Task
1.1 Core Concepts
containerd separates container metadata from execution state:
Container (metadata):
- ID, image reference, snapshot key
- OCI runtime spec
- Labels, extension data
- Persisted in BoltDB
Task (execution state):
- Actually running process
- PID, state (created/running/stopped)
- stdin/stdout/stderr
- Managed by shim process
1.2 Benefits of Separation
Benefits of separated design:
1. Container metadata can exist without a Task
- Create container and start later
- Preserve metadata of stopped containers
2. Independent of containerd restarts
- Tasks are managed by shim, surviving containerd restarts
- Reconnect to existing shims after restart
3. Support for multiple runtimes
- Container object is runtime-agnostic
- Runtime selected at Task creation time
2. Container Creation
2.1 Creation Process
Container creation flow:
1. Generate OCI spec from image
|
v
2. Prepare snapshot
- Add Active snapshot to image snapshot chain
- Writable layer for the container
|
v
3. Store container metadata
- Create Container record in BoltDB
- Store ID, image, snapshot, runtime, spec
|
v
4. Return container object
(process not yet started)
2.2 OCI Runtime Spec
containerd generates an OCI runtime spec to define the container execution environment:
OCI runtime spec key sections:
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 Generators (Spec Opts)
containerd spec generation pattern:
Spec Opts are function chains that incrementally build the OCI spec:
WithImageConfig(image) -> Apply image CMD, ENV, WORKDIR
WithHostNamespace(ns) -> Share host namespace
WithMemoryLimit(limit) -> Set memory limit
WithCPUs(cpus) -> Set CPU limit
WithMounts(mounts) -> Add mount points
WithProcessArgs(args) -> Set process arguments
WithRootfsPropagation(p) -> Set rootfs mount propagation
WithSeccompProfile(p) -> Apply Seccomp profile
WithApparmorProfile(p) -> Apply AppArmor profile
3. Task Execution
3.1 Task Creation
Task creation flow:
1. Check container's runtime type
(e.g., io.containerd.runc.v2)
|
v
2. Execute shim binary
(containerd-shim-runc-v2 start)
|
v
3. Shim returns ttrpc socket address
|
v
4. containerd sends Create request to shim
- Pass OCI spec
- Pass bundle path
|
v
5. Shim executes runc create
- Create namespaces
- Configure cgroups
- Mount rootfs
- Create process (not yet started)
|
v
6. Task state: Created
3.2 Task Start
Task start:
1. containerd sends Start request to shim
|
v
2. Shim executes runc start
- Start container process init
- Synchronize via exec.fifo
|
v
3. Task state: Running
- PID assigned
- stdin/stdout/stderr connected
3.3 Task State Transitions
Task state machine:
Created
|
| Start()
v
Running
|
+-- Kill(signal) -> Send signal
|
+-- Pause() -> Paused
| |
| +-- Resume() -> Running
|
+-- Process exits -> Stopped
|
v
Stopped
|
| Delete()
v
(deleted)
3.4 Exec (Additional Processes)
Exec operation:
Add a new process to an already running container:
1. Create ExecProcess
- Define new process spec (args, env, user)
- Assign execID
|
v
2. Send Exec request to shim
|
v
3. Execute runc exec
- Enter existing container namespaces
- Start new process
|
v
4. Independently manage stdin/stdout/stderr
Use cases: kubectl exec, docker exec
4. Shim Lifecycle
4.1 Shim Start
Shim start process:
1. containerd fork/execs shim binary
containerd-shim-runc-v2 -namespace k8s.io \
-id container-abc \
-address /run/containerd/containerd.sock \
start
|
v
2. Shim daemonizes itself
- Detach from parent process (setsid)
- Run independently of containerd
|
v
3. Create ttrpc Unix socket
/run/containerd/s/abc123...
|
v
4. Output socket address to stdout
containerd reads this address to connect
4.2 Shim Responsibilities
Shim key responsibilities:
1. Process management:
- Act as parent of container process
- Collect exit status via wait4()
- Detect and report OOM events
2. I/O management:
- Manage stdin/stdout/stderr FIFOs
- Connect to log drivers
- Copy I/O (containerProcess <-> FIFO)
3. Communication with containerd:
- Receive commands via ttrpc
- Report events (TaskExit, etc.)
- Respond to status queries
4. containerd restart resilience:
- Continue running when containerd restarts
- Restarted containerd reconnects to existing shim
- State recovery
4.3 Shim Shutdown
Shim shutdown:
1. Receive Task Delete request
|
v
2. Clean up container resources
- Delete cgroups
- Clean up namespaces
- Unmount rootfs
|
v
3. Close ttrpc socket
|
v
4. Shim process exits
5. Checkpoint/Restore
5.1 Checkpoint
Checkpoint operation:
Save running container state as a snapshot:
1. Invoke CRIU (Checkpoint/Restore in Userspace)
|
v
2. Dump process memory
- Save memory pages
- Save file descriptor state
- Save network connection state
|
v
3. Create checkpoint image
- CRIU image file set
- Stored alongside container spec
|
v
4. Optionally stop the container
Use cases:
- Live migration
- Fast start (restore from pre-warmed state)
- Debugging (capture state at specific point)
5.2 Restore
Restore operation:
1. Load checkpoint image
|
v
2. Prepare new container environment
- Create namespaces
- Mount rootfs
|
v
3. Execute CRIU restore
- Restore memory pages
- Restore process state
- Reconnect file descriptors
|
v
4. Resume process execution
6. Runtime Classes
6.1 Multiple Runtime Support
containerd supports various runtimes through the shim interface:
Runtime class comparison:
+----------+------------+-----------+----------+---------------+
| Runtime | Isolation | Overhead | Startup | Compatibility |
+----------+------------+-----------+----------+---------------+
| runc | Namespace | Minimal | Fast | Best |
| kata | Light VM | Medium | Medium | High |
| gVisor | User kernel| Low | Fast | Medium |
| Wasm | Wasm sandbox| Minimal | Very fast| Limited |
+----------+------------+-----------+----------+---------------+
6.2 runc
runc:
- Default OCI runtime
- Linux namespace and cgroup-based isolation
- Uses host kernel directly
- Lowest overhead
- Suitable for all Linux container workloads
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:
- Runs containers inside lightweight VMs
- Uses QEMU/Cloud-Hypervisor/Firecracker
- Strong isolation with separate guest kernel
- Suited for multi-tenant environments
- VM overhead exists
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):
- User-space kernel (Sentry)
- Intercepts and reimplements system calls
- Reduces host kernel attack surface
- Operates via ptrace or KVM
- Some system calls unsupported
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 runtime:
- Runs WebAssembly binaries as containers
- Uses Wasmtime, WasmEdge, etc.
- Very fast startup (millisecond range)
- Minimal memory usage
- Portable binaries
- Limited system access (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 Selection
Kubernetes RuntimeClass integration:
1. Define RuntimeClass resource:
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: kata
handler: kata
2. Specify RuntimeClass in Pod:
spec:
runtimeClassName: kata
containers:
- name: app
image: nginx
3. containerd selects runtime matching handler:
handler "kata" -> containerd.runtimes.kata config
-> execute containerd-shim-kata-v2
7. Summary
containerd container lifecycle management revolves around the separation of Container (metadata) and Task (execution), process isolation through shims, and support for diverse runtime classes. The shim's daemonized design ensures containers survive containerd restarts, while the standardized OCI runtime spec interface integrates runtimes like runc, Kata, gVisor, and Wasm seamlessly.