cgroup과 namespace 심화 — 컨테이너 격리의 커널 메커니즘
Docker 컨테이너의 격리는 마법이 아니라, 리눅스 커널의 namespace와 cgroup이라는 두 기능의 조합입니다. 이 두 가지를 이해하면 컨테이너의 실체가 보입니다.
핵심 개념
컨테이너 = namespace(격리) + cgroup(자원 제한) + rootfs(파일 시스템)
namespace: "뭘 볼 수 있는가" 제한
cgroup: "얼마나 쓸 수 있는가" 제한
Namespace (이름공간)
프로세스가 시스템 자원의 독립된 뷰 를 갖도록 합니다.
| Namespace | 격리 대상 | 플래그 |
|---|---|---|
| PID | 프로세스 ID | CLONE_NEWPID |
| Network | 네트워크 스택 | CLONE_NEWNET |
| Mount | 파일 시스템 마운트 | CLONE_NEWNS |
| UTS | 호스트네임 | CLONE_NEWUTS |
| IPC | IPC 자원 | CLONE_NEWIPC |
| User | UID/GID | CLONE_NEWUSER |
| Cgroup | cgroup 뷰 | CLONE_NEWCGROUP |
| Time | 시스템 시간 | CLONE_NEWTIME |
PID Namespace
호스트: 컨테이너 A: 컨테이너 B:
PID 1 (systemd) PID 1 (nginx) PID 1 (java)
PID 2 (...) PID 2 (worker) PID 2 (...)
...
PID 1234 (nginx) ← 호스트에서는 PID 1234
PID 5678 (java) ← 호스트에서는 PID 5678
컨테이너 내부에서 ps aux를 치면 PID 1부터 시작하는 독립된 프로세스 트리를 봅니다.
Network Namespace
호스트: eth0 (192.168.1.10)
│
veth pair (가상 이더넷)
│
컨테이너: eth0 (172.17.0.2) ← 독립된 네트워크 스택
각 컨테이너는 독립된 IP, 라우팅 테이블, iptables 규칙을 가집니다.
Namespace 실습
# 새 네트워크 namespace 생성
sudo ip netns add myns
# namespace 안에서 명령 실행
sudo ip netns exec myns ip addr # 독립된 네트워크 인터페이스
# 프로세스의 namespace 확인
ls -la /proc/self/ns/
# unshare로 새 namespace에서 프로세스 실행
sudo unshare --pid --fork --mount-proc bash
# 이 셸 안에서 ps aux를 치면 bash만 보임
Cgroup (Control Group)
프로세스 그룹의 자원 사용량을 제한하고 모니터링 합니다.
제어 가능한 자원
| 컨트롤러 | 제어 대상 |
|---|---|
| cpu | CPU 시간 할당 |
| memory | 메모리 사용 제한 |
| io (blkio) | 디스크 I/O 대역폭 |
| pids | 최대 프로세스 수 |
| cpuset | 특정 CPU 코어 할당 |
cgroup v2 실습
# cgroup v2 마운트 확인
mount | grep cgroup2
# 새 cgroup 생성
sudo mkdir /sys/fs/cgroup/mygroup
# 메모리 제한 설정 (100MB)
echo 104857600 > /sys/fs/cgroup/mygroup/memory.max
# CPU 제한 (50%)
echo "50000 100000" > /sys/fs/cgroup/mygroup/cpu.max
# 프로세스를 cgroup에 추가
echo $PID > /sys/fs/cgroup/mygroup/cgroup.procs
# 현재 메모리 사용량 확인
cat /sys/fs/cgroup/mygroup/memory.current
Docker에서의 cgroup
# Docker 컨테이너의 메모리/CPU 제한
docker run --memory=512m --cpus=1.5 nginx
# 내부적으로 cgroup이 생성됨
ls /sys/fs/cgroup/system.slice/docker-<container_id>.scope/
Docker 컨테이너의 전체 구조
docker run 실행 시:
1. 이미지에서 rootfs 준비 (OverlayFS)
2. Namespace 생성:
- PID namespace: 독립된 프로세스 트리
- Network namespace: 독립된 네트워크 (veth pair + bridge)
- Mount namespace: 독립된 파일 시스템 뷰
- UTS namespace: 독립된 호스트네임
- IPC namespace: 독립된 IPC
3. Cgroup 설정:
- CPU, 메모리, I/O 제한 적용
4. 프로세스 실행:
- 격리된 환경에서 PID 1로 시작
핵심 포인트
- 컨테이너가 VM이 아닌 이유: 커널을 공유함 — namespace로 뷰를 격리할 뿐 별도 커널이 아님
- namespace vs cgroup: namespace는 "보이는 범위", cgroup은 "쓸 수 있는 양"
- cgroup의 OOM: 메모리 제한 초과 시 cgroup 내 프로세스가 OOM Kill됨
- Docker는 리눅스 전용인가: 핵심 기술(namespace, cgroup)이 리눅스 전용 — Windows/Mac에서는 VM 위에서 실행
정리
namespace와 cgroup은 컨테이너 기술의 핵심입니다. Docker, Kubernetes, containerd 모두 이 두 커널 기능 위에 구축되어 있습니다. "Docker는 내부적으로 어떻게 격리하나요?"에 대해에 대한 답이 바로 namespace + cgroup입니다.