VM과 컨테이너는 무엇이 다를까?
들어가면서
수업을 통해 가상화 기술을 학습하던 중, 오래전에 자바의 구동 원리를 정리하며 JVM이라는 가상 머신에 대해 고민했던 기억이 떠올랐다. 또한, 과거 Testcontainers를 활용한 통합 테스트 환경을 구축하면서 Docker를 사용했지만, 당시에는 VM과 컨테이너의 구조적 차이를 깊이 이해하지 못한 채 적용하며 겪었던 어려움이 있었다. 이번 학습을 통해 VM과 컨테이너의 본질적인 차이를 명확히 이해하고, 그때의 의문들을 해소하고자 한다.
“컨테이너는 작은 VM”이라는 말이 과연 맞을까? 왜 Docker는 이렇게 많이 쓰이고, VM은 무겁다고 이야기할까? 이 글에서는 이러한 질문들에 답하며 VM과 컨테이너의 본질적인 차이를 깊이 있게 탐구해보고자 한다.
이 글의 목표
- VM과 컨테이너의 구조적 차이를 이해한다.
- 왜 VM은 무겁고 컨테이너는 가벼운지 설명할 수 있다.
- “격리”가 정확히 무엇인지 이해한다.
1. 컴퓨터 실행 계층: 애플리케이션은 어떻게 동작하는가?
VM과 컨테이너의 차이를 이해하기 위해서는 먼저 컴퓨터 시스템이 애플리케이션을 실행하는 방식을 계층적으로 이해할 필요가 있다.
- Hardware (하드웨어): CPU, 메모리, 디스크, 네트워크 카드 등 물리적인 컴퓨터 자원이다.
- Kernel (커널): 운영체제의 핵심 부분으로, 하드웨어 자원을 관리하고 애플리케이션이 하드웨어에 접근할 수 있도록 중개한다. 시스템 콜(System Call)을 통해 애플리케이션의 요청을 처리한다.
- Userland (유저랜드): 커널 위에서 동작하는 모든 소프트웨어(라이브러리, 유틸리티, 애플리케이션 등)를 포함한다. 사용자 모드에서 실행되며, 커널 모드의 자원에 직접 접근할 수 없다.
- Process (프로세스): 실행 중인 애플리케이션의 인스턴스이다. 각 프로세스는 자신만의 메모리 공간과 실행 컨텍스트를 가지며, 커널의 관리 하에 동작한다.
2. VM 구조: 하드웨어 전체를 가상화하다
가상 머신(VM)은 하이퍼바이저(Hypervisor)를 통해 물리적인 하드웨어 전체를 소프트웨어적으로 가상화한다. 마치 물리 컴퓨터 위에 또 다른 물리 컴퓨터를 만드는 것과 같다.
Hypervisor (하이퍼바이저)
하이퍼바이저는 물리 하드웨어와 Guest OS 사이에 위치하여, 여러 Guest OS가 하나의 물리 하드웨어 자원을 공유할 수 있도록 중개한다. Type 1(베어메탈)과 Type 2(호스트 OS 위) 방식이 있다.
Guest OS (게스트 운영체제)
각 VM은 자신만의 독립적인 운영체제(Guest OS)를 가진다. 이 Guest OS는 커널, 시스템 라이브러리, 부팅 프로세스 등 실제 OS가 갖춰야 할 모든 요소를 포함한다.
시스템 콜 흐름 (VM)
애플리케이션이 시스템 콜을 호출하면, Guest OS의 커널이 이를 처리한다. Guest OS 커널은 하드웨어에 직접 접근할 수 없으므로, 하이퍼바이저에게 요청을 전달하고 하이퍼바이저가 물리 하드웨어에 대한 접근을 중개한다. 이 과정에서 여러 계층을 거치게 되어 오버헤드가 발생한다.
가상 하드웨어
하이퍼바이저는 각 VM에 가상 CPU, 가상 메모리, 가상 디스크, 가상 네트워크 인터페이스 등 가상 하드웨어를 제공한다. Guest OS는 이 가상 하드웨어가 실제 물리 하드웨어인 것처럼 인식하고 동작한다.
3. 컨테이너 구조: OS 커널을 공유하다
컨테이너는 하드웨어가 아닌 운영체제(OS) 수준에서 가상화를 수행한다. 호스트 OS의 커널을 공유하며, 애플리케이션 실행에 필요한 환경만을 격리하여 제공한다.
Host Kernel 공유
컨테이너는 호스트 OS의 커널을 직접 사용한다. 즉, 각 컨테이너는 별도의 Guest OS를 포함하지 않으며, 호스트 OS의 커널이 모든 컨테이너의 시스템 콜을 처리한다. 이는 컨테이너가 VM보다 훨씬 가벼운 핵심적인 이유이다.
프로세스 격리
컨테이너는 호스트 OS 위에서 실행되는 격리된 프로세스들의 집합이다. 리눅스의 namespace와 cgroup 기술을 활용하여 격리를 구현한다.
- Namespace: 프로세스들이 파일 시스템, 네트워크, 프로세스 ID(PID) 등을 독립적으로 사용할 수 있도록 격리된 환경을 제공한다. 예를 들어, 각 컨테이너는 자신만의 파일 시스템 루트(
chroot와 유사)를 가지며, 자신만의 네트워크 인터페이스를 갖는다. - Cgroup (Control Group): CPU, 메모리, 네트워크 대역폭, 디스크 I/O 등 시스템 자원의 사용량을 제한하고 할당하는 기술이다. 이를 통해 특정 컨테이너가 시스템 자원을 독점하여 다른 컨테이너나 호스트 OS에 영향을 주는 것을 방지한다.
시스템 콜 흐름 (컨테이너)
애플리케이션이 시스템 콜을 호출하면, 컨테이너는 Guest OS 커널을 거치지 않고 바로 호스트 OS의 커널로 요청을 전달한다. 호스트 OS 커널은 namespace와 cgroup에 의해 격리된 환경 내에서 요청을 처리한다. 이 과정은 VM에 비해 훨씬 효율적이며 오버헤드가 적다.
4. 왜 VM은 무겁고 컨테이너는 가벼운가?
VM과 컨테이너의 무게 차이는 위에서 설명한 구조적 차이에서 비롯된다.
- OS 부팅 여부: VM은 Guest OS를 포함하므로 부팅 과정이 필요하며, 이는 수 분의 시간을 소요한다. 반면 컨테이너는 호스트 OS의 커널을 공유하므로 OS 부팅 과정 없이 애플리케이션 프로세스만 실행하면 되어 수 초 내에 시작된다.
- 커널 개수 차이: VM은 각 인스턴스마다 독립적인 커널을 가지지만, 컨테이너는 모든 인스턴스가 하나의 호스트 커널을 공유한다. 이는 메모리 사용량과 이미지 크기에 큰 영향을 미친다.
- 자원 사용량 차이: VM은 Guest OS를 위해 상당한 양의 CPU, 메모리, 디스크 공간을 미리 할당해야 한다. 컨테이너는 필요한 바이너리와 라이브러리만 포함하므로 이미지 크기가 작고, 자원도 필요에 따라 유연하게 사용한다.
- 시작 속도 차이: OS 부팅 오버헤드와 자원 할당 방식의 차이로 인해 컨테이너가 VM보다 훨씬 빠르게 시작하고 종료할 수 있다.
5. 격리 강도 차이: OS 단위 vs 프로세스 단위
VM과 컨테이너는 모두 격리 기술이지만, 그 격리의 강도와 범위가 다르다.
- VM: OS 단위 격리: VM은 하이퍼바이저를 통해 하드웨어 수준에서 완전히 격리된 환경을 제공한다. 각 VM은 독립적인 OS를 가지므로, 한 VM에서 문제가 발생해도 다른 VM이나 호스트 시스템에 영향을 미치지 않는다. 이는 매우 강력한 격리이며 높은 보안성을 제공한다.
- 컨테이너: 프로세스 단위 격리: 컨테이너는 호스트 OS의 커널을 공유하며,
namespace와cgroup을 통해 프로세스 수준에서 격리된다. 이는 VM만큼 완전한 격리는 아니지만, 대부분의 애플리케이션에는 충분한 격리 수준을 제공한다. 커널을 공유한다는 것은 이론적으로 한 컨테이너의 취약점이 호스트 커널을 통해 다른 컨테이너나 호스트 시스템에 영향을 줄 가능성이 있다는 의미이다.
6. 자주 하는 오해
“컨테이너는 작은 VM이다?”
아니다. 컨테이너는 VM과 근본적으로 다른 가상화 방식이다. VM은 하드웨어를 가상화하여 Guest OS를 포함하지만, 컨테이너는 OS 커널을 공유하고 프로세스 수준에서 격리된다. 컨테이너는 VM보다 훨씬 가볍고 빠르지만, 격리 강도와 유연성 측면에서 차이가 있다.
“Docker 이미지에는 OS가 통째로 들어있다?”
아니다. Docker 이미지에는 애플리케이션 실행에 필요한 최소한의 파일 시스템(바이너리, 라이브러리 등)만 포함되어 있다. 커널은 호스트 OS의 것을 공유하므로, OS 전체가 들어있는 것이 아니다. 이 때문에 Docker 이미지는 VM 이미지보다 훨씬 작다.
마무리 정리
VM과 컨테이너는 모두 애플리케이션을 격리된 환경에서 실행하는 강력한 도구이지만, 그 작동 방식과 목적에 따라 명확한 차이를 보인다.
- VM = 가상 컴퓨터: 하드웨어 전체를 가상화하여 완전한 독립성과 강력한 격리를 제공한다. 무겁고 시작이 느리지만, 높은 보안성과 유연성이 필요한 경우에 적합하다.
- Container = 격리된 프로세스 실행 환경: OS 커널을 공유하고 프로세스 수준에서 격리하여 가볍고 빠르게 동작한다. 효율적인 자원 사용과 빠른 배포가 중요한 마이크로서비스 환경에 적합하다.
두 기술의 장단점을 이해하고 프로젝트의 특성에 맞춰 적절히 활용하는 것이 현대 소프트웨어 개발에서 중요한 역량이라고 할 수 있다.
