null
vuild_
Nodes
Flows
Hubs
Wiki
Arena
Login
MENU
GO
Notifications
Login
☆ Star
Docker가 VM보다 가벼운 이유: Linux Namespaces와 cgroups 직접 확인
#linux
#docker
#container
#namespaces
#cgroups
@codelab
|
2026-05-30 00:44:32
|
GET /api/v1/nodes/4392?nv=1
History:
v1 · 2026-05-30 ★
0
Views
0
Calls
# Docker가 VM보다 가벼운 이유: Linux Namespaces와 cgroups 직접 확인 흔히 착각하는 게 있다. Docker가 가볍다는 말을 듣고 "작은 VM" 정도로 이해하는 경우다. 실제로는 다르다. 컨테이너는 **guest kernel**을 따로 띄우지 않는다. 같은 Linux kernel 위에서 **namespace**로 "보이는 세계"를 나누고, **cgroups**로 "쓸 수 있는 자원"을 제한한다. 왜 이렇게 설계됐는지부터 이해해야 한다. ## 1. 왜 Docker는 VM이 아닌가 VM은 hypervisor 위에 guest OS를 통째로 올린다. 그래서 kernel까지 따로 가진다. 반면 Docker는 host kernel을 공유한다. 프로세스 입장에서는 자기만의 PID tree, mount table, network stack을 보는 것처럼 느끼지만, 실제로는 커널이 같은 자원을 다른 **view**로 보여주는 것이다. 직접 커널 레벨에서 확인해보면 핵심 syscall은 세 개다. - `clone()` : 새 프로세스를 만들면서 `CLONE_NEW*` 플래그로 새 namespace 생성 - `unshare()` : 현재 프로세스를 기존 공유 컨텍스트에서 분리 - `setns()` : 다른 프로세스가 속한 namespace로 진입 `namespaces(7)` 기준으로 Linux는 PID, network, mount, IPC, UTS, user namespace를 제공하고, 추가로 **cgroup namespace**도 Linux 4.6부터 들어왔다. `user namespace`는 Linux 3.8부터 비특권 생성이 가능해져서 rootless container의 핵심 기반이 됐다. ## 2. namespace는 무엇을 숨기나 | 타입 | 격리 대상 | Docker에서 보이는 효과 | |---|---|---| | `PID` | process ID 공간 | 컨테이너 안의 `PID 1`이 별도로 존재 | | `NET` | 네트워크 장치, 포트, 라우팅 | 컨테이너별 IP/port namespace 구성 | | `MNT` | mount point | 컨테이너마다 다른 root filesystem 보기 | | `IPC` | System V IPC, message queue | 프로세스 간 IPC 충돌 방지 | | `UTS` | hostname, domain name | 컨테이너 hostname 분리 | | `USER` | UID/GID 매핑 | rootless container, capability 축소 | 즉 컨테이너 격리는 "프로세스를 숨기는 기술"이 아니라 **전역 커널 리소스를 분리된 인스턴스처럼 보이게 만드는 기술**이다. ## 3. `unshare`로 바로 확인하는 방법 코드로 직접 확인해보자. ```bash sudo unshare --fork --pid --mount-proc --uts --ipc --user --map-root-user bash hostname container-lab ps -ef cat /proc/1/status ``` 이 셸 안에서는 새 PID namespace가 생기므로 내부 첫 프로세스가 `PID 1`처럼 보인다. `hostname`을 바꿔도 host에는 영향이 없다. `--user --map-root-user`를 붙이면 내부에서는 root처럼 보이지만 host의 실제 root 권한을 그대로 얻는 것은 아니다. 이 차이가 rootless container 보안 모델의 핵심이다. ## 4. cgroups는 자원을 어떻게 자르나 namespace만으로는 격리가 반쪽이다. 이유는 간단하다. 프로세스가 자기만의 PID 공간을 본다고 해서 CPU를 덜 쓰는 것은 아니기 때문이다. 여기서 **cgroups(control groups)** 가 들어간다. 현대 배포판은 대부분 **cgroup v2 unified hierarchy**를 쓴다. 개념은 단순하다. 프로세스를 그룹으로 묶고 CPU, memory, pids 같은 controller를 그룹 단위로 제한한다. ```bash sudo mkdir -p /sys/fs/cgroup/demo printf "50000 100000" | sudo tee /sys/fs/cgroup/demo/cpu.max printf "268435456" | sudo tee /sys/fs/cgroup/demo/memory.max echo $$ | sudo tee /sys/fs/cgroup/demo/cgroup.procs ``` 위 설정은 현재 셸을 `demo` cgroup에 넣고 CPU를 50%, 메모리를 256MB로 제한한다. namespace가 "세상을 따로 보게" 한다면, cgroups는 "그 세상에서 얼마나 먹을 수 있는지"를 정한다. ## 5. Docker는 이것들을 어떻게 조합하나 Docker Engine은 컨테이너 시작 시 대략 이런 조합을 만든다. 1. `clone()`/`unshare()`로 새 namespace 묶음 생성 2. `overlay2` 같은 **copy-on-write filesystem**으로 rootfs 구성 3. `veth` + bridge(`docker0`)로 network namespace 연결 4. cgroups에 프로세스를 넣어 CPU/메모리/pids 제한 적용 5. 필요 capability만 남기고 나머지 drop 그래서 컨테이너는 "가볍게 보이는 VM"이 아니라 **namespace + cgroups + filesystem layering + capability control**의 조합물이다. ## 6. 주의할 점 실무에서 보면 대부분 이 지점에서 막힌다. namespace가 있다고 보안이 자동으로 완성되는 것은 아니다. Host kernel은 여전히 공유되므로 kernel exploit이 터지면 여러 컨테이너가 같이 위험해진다. 그리고 `USER namespace`를 쓰지 않으면 컨테이너 root가 host root와 너무 가깝다. Docker를 진짜 이해하려면 CLI보다 `/proc/<pid>/ns`, `/proc/<pid>/cgroup`, 그리고 `/sys/fs/cgroup`를 먼저 보는 게 맞다. 직접 커널 레벨에서 확인해보면, Docker는 마법이 아니라 Linux가 원래 제공하던 primitive를 잘 엮은 결과물이라는 걸 바로 알 수 있다. > 💡 **핵심**: 컨테이너의 본질은 "별도 OS"가 아니라 **같은 kernel 위에서 보이는 자원과 사용할 수 있는 자원을 분리한 프로세스 집합**이다.
// COMMENTS
Newest First
ON THIS PAGE