Docker, LXC, VM은 뭐가 다를까? 예시로 nginx를 기반으로 직접 비교 실험을 하는 문서입니다.
총 4편으로 구성했으며, Concepts - Setup - Results - Conclusion 순으로 작성합니다.
| VM | 컨테이너 (LXC / Docker) | |
|---|---|---|
| 커널 | 게스트마다 자기 커널 | 호스트 커널을 공유 |
| 가상화 층위 | 하드웨어를 가상화 | OS(커널)를 가상화 |
| 부팅 | 커널 부팅 O (BIOS→커널→init) | 커널 부팅 X (프로세스만 시작) |
| 격리 경계 | 하드웨어(가상) 경계 — 강함 | 커널 기능으로 나눈 논리 경계 — 상대적으로 약함 |
컨테이너 = 격리된 프로세스 묶음입니다.
새 커널을 부팅하지 않고, 호스트 커널을 그대로 쓰되 리눅스 커널 기능(namespaces + cgroups)으로 “너는 여기까지만 보여” 하고 시야를 잘라준 것입니다.
그래서 가볍고 빠르지만, 커널을 공유하므로 격리 경계가 VM보다 얇습니다.

물리 서버의 자원을 쪼개 VM 여러 대를 굴리는 소프트웨어입니다.
Type 1 (베어메탈): 하드웨어 위에 바로 올라감. Xen, KVM, VMware ESXi, Hyper-V
Type 2 (호스티드): 일반 OS 위 앱으로 실행. VirtualBox, VMware Workstation
KVM은 리눅스 커널 모듈로, 리눅스를 하이퍼바이저로 바꿔줍니다.
VM을 만들려면 CPU의 가상화 기능(
vmx=Intel,svm=AMD)이 필요합니다.
컨테이너는 이 세 가지의 조합일 뿐입니다.
LXC도 Docker도 똑같이 이걸 씁니다.
프로세스가 보는 시스템 자원의 시야를 잘라냅니다.
종류별로 나뉩니다:
| namespace | 격리하는 것 |
|---|---|
pid | 프로세스 ID 트리 (컨테이너 안에선 자기 PID 1부터 시작) |
net | 네트워크 인터페이스·IP·라우팅 (컨테이너 전용 IP의 근거) |
mnt | 마운트/파일시스템 트리 |
uts | 호스트네임 |
ipc | 프로세스 간 통신 자원 |
user | UID/GID 매핑 (컨테이너 root ≠ 호스트 root) |
cgroup | cgroup 트리 뷰 |
→ 비교 항목 “프로세스 가시성 / IP 할당 / 파일시스템 격리”가 전부 여기서 나옵니다.
CPU · 메모리 · 디스크 I/O를 프로세스 그룹 단위로 제한하고 계량합니다.
-cpus=1, m 512m 같은 옵션이 결국 cgroup 설정으로 내려간다
→ 비교 항목 “리소스 사용량 / blast radius”의 핵심
폭주하는 컨테이너가 cgroup 상한에 막혀 이웃을 못 죽이는 게 격리의 실체
overlayfs: 읽기전용 하위 레이어(lowerdir) + 쓰기 가능 상위 레이어(upperdir)를 겹쳐 하나의 파일시스템으로 보이게 합니다.
변경분만 상위에 쓰는 copy-on-write.

같은 커널 기능(namespaces + cgroups)을 쓰는 형제입니다.
차이는 철학과 사용법:
| LXC / LXD·Incus | Docker | |
|---|---|---|
| 성격 | 시스템 컨테이너 | 애플리케이션 컨테이너 |
| 안에 뭐가 도나 | OS 유저스페이스 전체 (init/systemd + 여러 프로세스) | 보통 프로세스 하나(서비스 1개) |
| 비유 | 가벼운 리눅스 서버 한 대 | 포장된 프로세스 하나 |
| 상태 | 보통 stateful (VM처럼 다룸) | 보통 stateless, 이미지로 재생성 |
| 배포 단위 | rootfs / 컨테이너 스냅샷 | 레이어드 이미지 + 레지스트리 |
| 대표 도구 | lxc, lxd, incus | docker, Dockerfile, Docker Hub |
초기 Docker는 백엔드로 LXC를 썼다가, 이후 자체 라이브러리(libcontainer→runc)로
옮겨갔습니다.즉 둘은 뿌리가 이어져 있고, 지금은 같은 커널 기능을 다른 방식으로 포장한 것이죠.
한 줄 요약: LXC는 OS를 통째로 담는 가벼운 컨테이너, Docker는 앱 하나를 담는 이미지 중심 컨테이너입니다.
그래서 LXC는 VM에 가깝게, Docker는 프로세스에 가깝게 느껴집니다.

커널을 공유하는 컨테이너는 커널 취약점 하나로 탈출(container escape) 가능성이 있습니다
→ 멀티테넌트/신뢰 못 하는 코드엔 VM이 안전
VM은 하드웨어 경계로 막혀 탈출이 훨씬 어렵습니다 (불가능은 아님)
→ 비교 항목 “blast radius / 언제 뭘 쓸까” 결론의 근거
진짜 랜선은 양 끝에 커넥터가 있습니다. veth는 그걸 소프트웨어로 만든 것.
한쪽 끝은 컨테이너 안에, 다른 쪽 끝은 호스트에 꽂힙니다.
컨테이너가 밖과 통신하려면 이 가상 랜선이 필요합니다.
컨테이너 하나당 랜선 한 쌍입니다.
사무실 스위치/공유기처럼, 여러 기기의 랜선을 한 군데 모아 서로 통하게 하는 장치입니다.
Docker는 docker0 라는 가상 스위치를 만듭니다.
모든 컨테이너의 veth 한쪽 끝을 여기에 꽂습니다.
그래서 컨테이너끼리 같은 스위치에 물려 통신하게 되죠.
컨테이너는 집 안 사설 IP(예:172.17.x)를 받는데, 외부에선 이걸 못 알아봅니다.
밖으로 나갈 때 호스트 IP로 주소를 갈아 끼워 내보냅니다.
공유기 뒤 여러 기기가 하나의 공인 IP로 인터넷 쓰는 것과 같죠.
즉, 나가는 트래픽의 주소를 바꿔주는 것이 NAT.
반대로 밖에서 컨테이너로 들어오는 경우입니다.
"호스트의 8080 문을 두드리면 → 컨테이너 80으로 안내하라"는 규칙을 커널(iptables/nftables)에 심는 것입니다.
공유기 포트포워딩과 같습니다.
이 규칙이 없으면 컨테이너는 밖에서 접근할 수 없습니다.
VM은 위 컨테이너 방식과 급이 다릅니다.
하이퍼바이저가 VM에게 진짜 랜카드처럼 보이는 가상 NIC를 주고, 그 NIC를 브리지로 LAN에 직접 물립니다.
그래서 VM은 NAT 뒤에 숨지 않고 LAN의 IP를 한 대의 컴퓨터처럼 직접 받습니다.
(이번에 진행할 실험 서버가 그 예 — 대전 LAN IP를 직접 갖고 있습니다.)
LXC는 둘 다 가능해서 선택할 수 있습니다.
기본은 Docker처럼 → 자기 브리지(lxdbr0) + NAT (사설 IP)
또는 VM처럼 → macvlan/bridge로 LAN IP 직접 수령
이것이 LXC가 가벼운 서버 한 대로 취급받는 이유 중 하나입니다.
필요하면 VM처럼 LAN에 IP를 직접 차지할 수 있죠.
| IP 받는 방식 | 밖에서 들어오려면 | |
|---|---|---|
| Docker | 사설 IP + NAT (숨음) | 포트 퍼블리시(-p) 필요 |
| LXC | 선택 (NAT 숨김 or LAN 직접) | 방식에 따라 다름 |
| VM | 보통 LAN IP 직접 | 그냥 그 IP로 접속 |
| 비교 항목 | 밑에 깔린 개념 |
|---|---|
| 리소스 사용량 | 커널 공유 + cgroups + overlayfs |
| IP 할당 | net namespace + veth/bridge/NAT |
| 프로세스 가시성 | pid namespace |
| 파일시스템 격리 | mnt namespace + overlayfs / 전용 디스크 |
| 포트 노출 | net namespace + NAT/DNAT |
| 장애 영향 범위 | cgroups 상한 + 격리 경계 강도 |
| 백업/스냅샷 | 이미지 레이어 vs rootfs vs VM 디스크 스냅샷 |
다음 글에서는 실습 전 환경 구축을 먼저 진행하겠습니다.
감사합니다.
정택준
Team: https://nangman.cloud/ko
E-mail: taekjunnnn@nangman.cloud