우리가 일상에서 컨테이너를 띄우는 것은 아주 고수준의 레이어인 docker 커맨드(cli)를 사용해서 띄우게 된다
하지만 hostOS의 cgroup을 조절하고 namespace를 설정하는 건 마법처럼 뚝딱되는 것이 아니다
컨테이너가 실행되기까지 여러 레이러를 거치는데 각 레이어가 무슨역할을 하는지 정리해본다
사용자 / 오케스트레이터 (docker, nerdctl)
↓
high-level runtime (containerd, CRI-O)
↓
shim (containerd-shim-runc-v2)
↓
OCI runtime (runc, crun)
↓
Linux kernel
high-level runtime: 이미지 pull, 스토리지, 네트워크 조율을 담당한다. 이미지를 rootfs + config.json으로 구성된 OCI bundle로 변환해서 shim을 통해 OCI
runtime(runc)에 넘긴다.
shim은 containerd 같은 하이레벨 런타임이 죽어도 컨테이너가 유지되도록 분리해주는 역할을 한다.
- 컨테이너 프로세스의 부모역할을 맡게 되는데, 만약 containerd 가 컨테이너들을 자식 프로세스로 지니고 있으면 containerd가 죽고나서 관리가 안된다
# shim이 없을 경우
containerd 죽음
→ 컨테이너 프로세스는 살아있음
→ 근데 exit code를 수거할 놈이 없음
→ stdio 연결이 끊김
→ 다시 살아난 containerd가 컨테이너 상태를 모름
# shim이 중간에 있으면?
containerd 죽음
→ shim은 살아있음
→ 컨테이너 프로세스의 부모 = shim
→ exit code, stdio 다 shim이 들고 있음
→ containerd 재시작 후 shim에 다시 연결
→ 컨테이너 상태 복구 가능
그 밑에 레이어가 OCI runtime 이다 (편하게 runc라고함)
runc는 OCI Runtime Spec에 정의된 5개의 operation을 구현한다
create → namespace, cgroup 설정. 프로세스는 블로킹 상태
start → exec()으로 실제 프로세스 실행
state → 현재 상태 JSON 출력
kill → 시그널 전송
delete → 리소스 정리
- start 후 runc는 종료된다. runc는 컨테이너를 띄우는 역할까지만 하고, 이후 컨테이너 프로세스의 부모는 shim이 맡는다.
runc create → namespace, cgroup 설정. 프로세스 블로킹
runc start → exec()으로 실제 프로세스 실행
runc exits → runc 프로세스 자체는 종료
이후 컨테이너 프로세스의 부모 = shim
runc는 리눅스 커널이 제공하는 기능들을 syscall로 호출하여 격리 환경을 구성한다.
namespaces → 프로세스 격리
cgroups → 리소스 제한
seccomp → syscall 필터링
capabilities → root 권한 세분화
<style>
svg text { font-family: sans-serif; }
.t { font-size: 14px; fill: #1a1a1a; }
.ts { font-size: 12px; fill: #555550; }
.th { font-size: 14px; font-weight: 500; fill: #1a1a1a; }
/* color ramps */
.c-gray > rect, .c-gray > circle { fill: #F1EFE8; stroke: #5F5E5A; }
.c-gray > text.th, .c-gray > text.t { fill: #444441; }
.c-gray > text.ts { fill: #5F5E5A; }
.c-blue > rect { fill: #E6F1FB; stroke: #185FA5; }
.c-blue > text.th, .c-blue > text.t { fill: #0C447C; }
.c-blue > text.ts { fill: #185FA5; }
.c-amber > rect { fill: #FAEEDA; stroke: #854F0B; }
.c-amber > text.th, .c-amber > text.t { fill: #633806; }
.c-amber > text.ts { fill: #854F0B; }
.c-teal > rect { fill: #E1F5EE; stroke: #0F6E56; }
.c-teal > text.th, .c-teal > text.t { fill: #085041; }
.c-teal > text.ts { fill: #0F6E56; }
.c-purple > rect { fill: #EEEDFE; stroke: #534AB7; }
.c-purple > text.th, .c-purple > text.t { fill: #3C3489; }
.c-purple > text.ts { fill: #534AB7; }
.c-green > rect { fill: #EAF3DE; stroke: #3B6D11; }
.c-green > text.th, .c-green > text.t { fill: #27500A; }
.c-green > text.ts { fill: #3B6D11; }
.c-coral > rect { fill: #FAECE7; stroke: #993C1D; }
.c-coral > text.th, .c-coral > text.t { fill: #712B13; }
.c-coral > text.ts { fill: #993C1D; }
.arr {
stroke: #888780;
stroke-width: 1.5;
fill: none;
}
</style>
사용자 / 오케스트레이터
docker CLI · kubectl · 직접 호출
명령
high-level runtime
containerd · CRI-O — 이미지 pull, 스토리지, 네트워크 조율
bundle 경로 + id
shim
containerd-shim-runc-v2 — 데몬 죽어도 컨테이너 유지, stdio·exit code 관리
OCI 커맨드 호출
OCI runtime (low-level runtime)
runc · crun — create / start / state / kill / delete 구현체
syscall
Linux kernel
namespaces · cgroups · seccomp · capabilities
OCI runtime이 실행하는 순서
create
bundle 읽기·fork
CREATED
블로킹·hook 실행
start
exec() 실행
RUNNING
프로세스 실행 중
STOPPED
종료·리소스 잔존
delete
리소스 정리
소멸
non-existent
state 조회 가능