최소 컨테이너 런타임 완성
1주차부터 6주차까지, namespace 기초부터 시작해서 mount 격리, cgroup 리소스 제한, 네트워킹, OverlayFS, 그리고 이번 주 lifecycle 관리까지 붙이면서 "동작하는 최소 컨테이너 런타임" 하나를 완성했다. 이번 글에서는 지금까지 만든 게 정확히 뭘 할 수 있는지, 그리고 전체 기능을 이어서 돌려본 통합 테스트 결과를 정리한다.
지금까지 쌓아온 것
- 1주차: UTS/PID namespace로 hostname과 PID 격리
- 2주차: pivot_root로 rootfs 격리 (chroot의 한계를 직접 탈출해보며 확인)
- 3주차: cgroup v2로 memory/cpu/pids 제한
- 4주차: veth + bridge + NAT로 컨테이너 네트워킹
- 5주차: OverlayFS로 이미지 레이어 흉내내기, User namespace로 root 권한 격리
- 6주차: 컨테이너 lifecycle 관리 (이번 글의 주제)
이 여섯 주가 쌓여서 나온 결과물이 mycontainer다. run, ps, exec, stop 네 개의 서브커맨드로 이루어져 있다.
지금 mycontainer가 할 수 있는 것
run — 컨테이너 실행
sudo ./run_container.sh run -- /bin/sh
clone(CLONE_NEWUSER | CLONE_NEWNS | CLONE_NEWPID | CLONE_NEWUTS)로 격리된 프로세스를 만들고, uid_map/gid_map으로 user namespace 매핑을 마친 뒤, overlay 마운트 + pivot_root로 rootfs를 교체하고, cgroup에 가입시키고 network namespace에 붙인다. 실행 직후에는 container_dir/state 파일에 PID/CGROUP_PATH/NETNS_NAME/MERGEDDIR을 기록해서, 이후 ps/exec/stop이 이 컨테이너를 찾아올 수 있게 해둔다.
ps — 실행 중인 컨테이너 목록
sudo ./mycontainer_ps
/var/lib/mycontainer/containers/ 아래를 훑으면서, state 파일이 있는 디렉토리만 "running"으로 판단해 나열한다. 컨테이너가 정상 종료되면 state 파일이 지워지므로, 별도의 상태 플래그 없이 파일 존재 여부만으로 running/stopped를 구분한다.
exec — 실행 중인 컨테이너에 재진입
sudo ./mycontainer_exec <container_dir> /bin/sh
state 파일에서 target PID를 읽어, /proc/[pid]/ns/{pid,mnt,net,uts}로 fd를 확보하고 setns()로 재진입한 뒤 cgroup에도 가입시켜서 새 프로세스를 실행한다. docker exec와 같은 원리다. (자세한 내부 동작은 [docker exec는 내부적으로 어떻게 동작하는가] 글에 따로 정리했다.) user namespace 재진입은 이번엔 스코프 아웃했다.
stop — graceful shutdown
sudo ./mycontainer_stop <container_dir>
SIGTERM을 한 번 보내고, 10초 동안 1초 간격으로 kill(pid, 0)으로 생존 여부를 폴링한다. 그 안에 종료되면 끝, 안 되면 SIGKILL로 강제 종료한다.
통합 테스트
네 기능을 따로따로 테스트하는 것과, 이어서 한 번에 돌렸을 때 문제가 없는지는 다른 이야기다. run으로 띄우고 → ps로 확인하고 → exec로 들어갔다 나오고 → stop으로 정리하는 전체 흐름을 실제로 이어서 돌려봤다.
1. 시작 전 상태 확인
$ ls /var/lib/mycontainer/containers/*/state 2>/dev/null
$ ip netns list
container2 (id: 1)
container1 (id: 0)
running 중인 컨테이너 없이 깨끗한 상태에서 시작했다.

2. run — 컨테이너 실행
$ sudo ./run_container.sh run -- /bin/sh
overlay 마운트, pivot_root, cgroup 가입, netns 연결까지 마치고 컨테이너 안 셸이 뜬다.

3. ps — running 목록 확인
다른 터미널에서:
$ sudo ./mycontainer_ps
NAME PID NETNS
mycontainer-XXXXXX XXXXXX mycontainer-XXXXXX
방금 띄운 컨테이너가 정확히 하나 잡혔다.

4. exec — 재진입해서 확인 후 빠져나오기
세 번째 터미널에서:
$ sudo ./mycontainer_exec /var/lib/mycontainer/containers/mycontainer-XXXXXX /bin/sh
들어가서 hostname과 cat /proc/self/cgroup으로 원래 컨테이너와 같은 UTS/cgroup 환경인지 확인한 뒤 exit으로 나왔다.
[여기에 exec 진입/확인 이미지]
여기서 확인하고 싶었던 부분: exec로 만든 프로세스는 원래 컨테이너의 PID 1이 아니므로, exec 세션에서 나와도(exit) 원래 컨테이너는 영향을 받지 않아야 한다. 실제로 exec 종료 후 다시 ps를 돌려보면:
$ sudo ./mycontainer_ps
NAME PID NETNS
mycontainer-XXXXXX XXXXXX mycontainer-XXXXXX
원래 컨테이너는 여전히 running으로 남아 있었다. exec 프로세스 하나가 빠진 것이 원래 컨테이너의 생존에 영향을 주지 않는다는 걸 확인했다 — PID namespace 안에서 "리더(PID 1)의 생사만 전체에 영향을 준다"는 원칙과도 맞아떨어지는 결과다.

5. stop — graceful shutdown
$ sudo ./mycontainer_stop /var/lib/mycontainer/containers/mycontainer-XXXXXX

6. 정리 확인
$ sudo ./mycontainer_ps
NAME PID NETNS
$ ls /sys/fs/cgroup/ | grep XXXXXX
$ ip netns list | grep XXXXXX
컨테이너가 ps 목록에서 사라지고, cgroup과 netns도 모두 정리된 것을 확인했다.


아직 못 채운 부분
정직하게 남겨두는 게 나을 것 같아서 적어둔다.
- exec의 user namespace 재진입 생략: setns(CLONE_NEWUSER) 이후 setuid/setgid 순서까지 맞춰야 하는 추가 복잡도가 있어 스코프 아웃했다.
- cgroup 정리 타이밍 이슈: 4주차부터 있었던 문제로, SIGKILL로 컨테이너가 비정상 종료되면 run_container.sh의 set -e가 뒤이은 cgroup/netns 정리를 스킵시키는 버그가 있었다. || true / || echo로 각 정리 단계를 독립적으로 만들어 해결했다.
- cgroup 관리자 프로세스도 같은 cgroup 소속: mycontainer_run의 main() 자신도 join_cgroup()으로 컨테이너와 같은 cgroup에 들어가 있다. 실무상 문제는 거의 없지만, 엄밀히는 "컨테이너 프로세스만 격리"는 아니다.
https://github.com/Leekb0804/mycontainer-ebpf/tree/main/mycontainer
'컨테이너 > 기초개념' 카테고리의 다른 글
| docker exec는 내부적으로 어떻게 동작하는가 (0) | 2026.08.26 |
|---|---|
| container state (0) | 2026.08.20 |
| mycontainer CLI (overlayfs + usernamespace) (0) | 2026.08.19 |
| OverlayFS + User Namespace 결합 (0) | 2026.08.12 |
| user namespace (0) | 2026.08.11 |