컨테이너 격리 메커니즘, 프로세스 격리를 중심으로 구현
도커 안에서 컨테이너가 어떻게 동작하는지 이해하기 위해 Docker, containerd, runc 없이 Linux 커널 기능만으로 컨테이너를 직접 구현한 프로젝트입니다.
Prometheus + Grafana + Loki 기반 모니터링 스택을 함께 구성해 격리 상태를 실시간으로 관찰합니다.
컨테이너는 격리된 프로세스입니다. 리눅스의 세 가지 기능으로 프로세스를 제어합니다.
├── Linux Namespace 프로세스가 무엇을 "보는지" (격리)
│ ├── PID namespace 독립적인 프로세스 트리
│ ├── Mount namespace 파일시스템 뷰 분리
│ └── UTS namespace hostname 분리
│
├── cgroups v2 프로세스가 얼마나 "쓰는지" (리소스 제한)
│
│
└── seccomp 프로세스가 무엇을 "할 수 있는지" (syscall 필터링)
reboot, ptrace, mount/umount2 등 차단
chmod 계열은 SCMP_ACT_LOG → Loki 수집
[monitoring/]
├── custom_exporter.py 컨테이너 상태 → Prometheus 메트릭 (포트 8000)
├── Prometheus 메트릭 수집
├── Grafana 대시보드 시각화
└── Loki + Promtail seccomp 차단 로그 수집
unshare --pid --fork --mount --uts 로 세 가지 namespace를 동시에 분리합니다.
- PID namespace: 컨테이너 내부 프로세스는 PID 1부터 시작. 호스트에서는 다른 PID로 보임
- Mount namespace: 컨테이너 내부 마운트 조작이 호스트에 영향을 주지 않음
- UTS namespace:
hostname mini-container설정이 호스트 hostname과 독립적으로 동작
lower/ 읽기전용 베이스 레이어 (busybox 바이너리 + 기본 디렉토리)
upper/ 컨테이너 내 변경사항만 기록되는 레이어
merged/ 컨테이너가 실제로 보는 합성 뷰
컨테이너 종료 후 upper/ 디렉토리만 확인하면 컨테이너 내부에서 발생한 변경사항을 diff처럼 볼 수 있습니다.
| 항목 | 제한 |
|---|---|
| CPU | 50% (cpu.max = 50000 100000) |
| 메모리 | 256MB |
| PID | 최대 32개 |
| cpuset | 전체 코어의 절반만 허용 |
| hugetlb | 비활성화 |
libseccomp을 Python ctypes로 직접 바인딩해 seccomp.json 프로파일을 로드합니다.
차단 대상: reboot, kexec_load, ptrace, mount/umount2, swapon/swapoff
chmod 계열은 SCMP_ACT_LOG로 설정해 Loki로 로그를 수집합니다.
demo/
├── container/
│ ├── minicontainer.sh 컨테이너 실행 (rootfs → OverlayFS → cgroups → namespace → seccomp)
│ ├── cleanup.sh 마운트 해제, cgroup 제거, 임시 디렉토리 삭제
│ ├── seccomp_helper.py libseccomp ctypes 바인딩 + chroot 진입
│ └── seccomp.json syscall 허용/차단 프로파일
└── monitoring/
├── custom_exporter.py Prometheus 메트릭 서버 (포트 8000)
├── docker-compose.yml Prometheus + Grafana + Loki + Promtail
├── prometheus.yml
├── promtail.yml
└── grafana/
apt install busybox-static libseccomp2 libseccomp-dev auditdsudo ./container/minicontainer.sh# 메트릭 서버
sudo python3 monitoring/custom_exporter.py
# Prometheus + Grafana + Loki
cd monitoring && docker compose up -d
# Grafana: http://localhost:3000sudo ./container/cleanup.shcustom_exporter.py가 /proc, /sys/fs/cgroup을 직접 읽어 Prometheus 메트릭으로 노출합니다.
| 패널 | 메트릭 | 설명 |
|---|---|---|
| 컨테이너 상태 | container_running |
실행 중 / 중지 |
| CPU 사용량 | cgroup_cpu_usage_usec |
cgroup CPU 누적 사용 시간 |
| CPU 제한선 | cgroup_cpu_quota_percent |
설정된 CPU 쿼터 (50%) |
| 메모리 사용량 | cgroup_memory_current_bytes |
현재 메모리 사용량 |
| 메모리 제한선 | cgroup_memory_limit_bytes |
설정된 메모리 한도 (256MB) |
| seccomp 로그 | Loki type=1326 |
차단된 syscall 감사 로그 |
모니터링을 위해 컨테이너 프로세스의 호스트 PID가 필요했습니다.
그런데 unshare --pid로 생성된 namespace 내부에서는 자신의 PID가 1로 보입니다.
/proc를 새로 마운트하기 전에 readlink /proc/self를 읽으면 호스트 proc을 통해 실제 PID를 얻을 수 있습니다.
# unshare 내부, proc 마운트 전에 실행
readlink /proc/self > $PID_FILE # 호스트 PID 기록
# 이후 proc 마운트
mount -t proc proc $MERGED/proccpu.max, memory.max를 child cgroup에 쓰면 No such file or directory 오류가 발생했습니다.
cgroups v2는 부모 cgroup의 cgroup.subtree_control에 컨트롤러를 먼저 활성화해야 child cgroup에 해당 파일이 생성됩니다.
# 수정 전: child cgroup에 바로 쓰기 시도 → 파일 없음
echo "50000 100000" > /sys/fs/cgroup/minicontainer/cpu.max
# 수정 후: 부모에서 컨트롤러 활성화 → child에 파일 생성됨
echo "+cpu" > /sys/fs/cgroup/cgroup.subtree_control
echo "+memory" > /sys/fs/cgroup/cgroup.subtree_control
echo "50000 100000" > /sys/fs/cgroup/minicontainer/cpu.maxcleanup.sh 실행 시 OverlayFS 언마운트가 device is busy로 실패하는 경우가 있었습니다.
merged/ 하위에 마운트된 proc, sys, dev를 먼저 언마운트해야 상위 OverlayFS를 해제할 수 있습니다.
# 순서가 중요
umount merged/proc
umount merged/sys
umount merged/dev
umount merged/ # OverlayFS이 프로젝트에서 구현한 cpu.max는 CFS quota 방식입니다.
Kubernetes에서 limits.cpu를 설정하면 내부적으로 동일하게 동작합니다.
문제:
100ms 주기마다 50ms만 CPU 사용 허용
→ 50ms를 다 쓰면 남은 50ms는 다른 컨테이너가 놀고 있어도 throttle
→ 실제 운영에서 P99 latency 급등으로 이어짐
해결 방향:
1단계: cpu.weight 도입
cpu.max (하드 캡) → cpu.weight (상대적 우선순위)
여유 CPU가 있으면 더 사용하고, 경합 시 가중치대로 분배
→ Kubernetes requests가 내부적으로 하는 일
2단계: CFS period 조정
기본 100ms period를 줄여 throttle 주기를 단축
→ latency spike 완화
3단계: CPU Pinning (latency-critical 워크로드)
cpuset exclusive로 특정 컨테이너에 코어 전용 할당
→ Kubernetes CPU Manager static policy와 동일한 원리
관찰 수단 고도화:
현재 /proc 폴링으로는 nr_throttled 숫자만 확인할 수 있습니다.
eBPF와 PSI를 도입하면 스케줄러 동작을 직접 관찰할 수 있습니다.
PSI → cpu.pressure 수치로 실제 CPU 압박 정도 측정
eBPF → 어떤 프로세스가 언제 얼마나 throttle 당했는지 실시간 트레이싱


