앞선 글에서 컴퓨터에서 프로그램이 실행되는 기본 구조를 정리했다.
프로그램과 데이터는 Disk/SSD에 저장되어 있고, 프로그램을 실행하면 필요한 내용이 RAM에 올라오며 CPU가 실제 명령을 처리한다.
Disk / SSD
프로그램과 데이터 저장
↓
RAM
실행에 필요한 프로그램과 데이터 적재
↓
CPU
실제 명령 실행
그리고 CPU, RAM, Disk 같은 하드웨어 자원을 관리하는 것이 운영체제(Operating System, OS)이고, 그중에서도 실제 하드웨어와 가장 가까운 곳에서 자원을 관리하는 핵심 부분이 Kernel이었다.
Application
↓
Operating System
↓
Kernel
↓
CPU / RAM / Disk / Network
여기까지 이해하고 나니 클라우드에서 계속 등장하던 VM(Virtual Machine)과 Container도 조금씩 이해되기 시작했다.
결국 VM과 Container도 완전히 새로운 세계의 개념이 아니라,
CPU, RAM, Disk 같은 실제 컴퓨터 자원을 어떻게 나누고, 애플리케이션 실행 환경을 어떻게 격리할 것인가?
에서 출발한 기술이다.
하나의 물리 서버가 있을 때,
Physical Server
CPU
RAM
Disk
Network
이 서버에서 애플리케이션 하나만 실행하고 있는데 실제 자원 사용량이 다음과 같다면?
CPU 사용률 10%
RAM 사용률 20%
나머지 CPU와 RAM은 사용되지 않은 채 남아 있게 된다.
비싼 서버를 구매했는데 일부 자원만 사용한다면 비효율적이다.
그래서 천재들은 이런 생각을 했나보다..
하나의 물리 컴퓨터를 여러 개의 컴퓨터처럼 나누어 사용할 수 없을까?
이렇게 실제 컴퓨터의 자원을 가상의 환경으로 나누어 사용할 수 있도록 만드는 기술을 가상화(Virtualization)라고 한다.
그리고 대표적인 방식이 VM(Virtual Machine)이다.
VM은 하나의 물리 컴퓨터 안에서 독립된 컴퓨터처럼 동작하도록 만든 가상의 컴퓨터이다.
쉽게 말하면 컴퓨터 안에 또 다른 컴퓨터를 만드는 것이다.
예를 들어 실제 물리 서버가 다음과 같은 자원을 가지고 있을 때,
Physical Server
CPU 16 Core
RAM 64GB
Disk 2TB
이 서버의 자원을 여러 VM으로 나눌 수 있다.
Physical Server
CPU 16 Core / RAM 64GB
↓
VM A
├── vCPU 4
└── RAM 16GB
VM B
├── vCPU 4
└── RAM 16GB
VM C
├── vCPU 8
└── RAM 32GB
각 VM 입장에서는 자신이
CPU 4개
RAM 16GB
를 가진 하나의 독립적인 컴퓨터처럼 보인다.
실제로 새로운 CPU와 RAM이 만들어진 것은 아니다.
실제 물리 서버가 가지고 있는 CPU와 RAM을 가상화해서 VM에게 제공하는 것이다.
여기서 Hypervisor(하이퍼바이저)가 등장한다.
Hypervisor는 실제 하드웨어 자원을 여러 VM이 나누어 사용할 수 있도록 관리하는 가상화 계층이다.
단순하게 표현하면 다음과 같다.

Hypervisor가 실제 CPU, RAM 등의 자원을 VM들이 사용할 수 있도록 중간에서 관리한다.
따라서 VM에서는 보통 VM 단위로 사용할 vCPU와 RAM 등의 사양이 정해진다.
VM을 이해할 때 가장 중요한 특징이다.
각 VM은 독립적인 컴퓨터처럼 동작하기 때문에 자기만의 OS와 Kernel을 가진다.
VM A
├── Spring Boot
├── Java
├── Ubuntu
└── Linux Kernel
VM B
├── Application
├── Windows
└── Windows Kernel
즉, 앞에서 공부했던 컴퓨터의 기본 구조가 각 VM 내부에 다시 존재한다.
Application
↓
VM의 Operating System
↓
VM의 Kernel
↓
VM에 제공된 CPU / RAM / Disk
예를 들어 VM 내부에서 Spring Boot를 실행하면:
java -jar app.jar
Spring Boot는 Process로 실행되고 VM 내부의 Kernel이 해당 Process가 사용할 CPU와 RAM을 관리한다.
Spring Boot
↓
Java Process
↓
VM의 Kernel
↓
CPU / RAM
AWS에서 사용하는 EC2 Instance도 이 개념과 연결된다.
EC2를 생성할 때 우리는 Instance Type을 선택한다.
그리고 Instance Type에 따라:
vCPU
Memory
Network 성능
등의 사양이 정해진다.
예를 들어 어떤 EC2를 생성했다고 하면 개발자 입장에서는:
EC2
vCPU
RAM
Network
...
라는 사양을 가진 하나의 서버를 받은 것처럼 사용할 수 있다.
그 위에 Amazon Linux를 설치하고:
EC2
↓
Amazon Linux
↓
Java
↓
Spring Boot
애플리케이션을 실행할 수도 있다.
그래서 EC2에 SSH로 접속했을 때 실제 Linux 컴퓨터 한 대를 사용하는 것처럼:
Java 설치
File 생성
Process 실행
Port 사용
Docker 설치
등을 할 수 있었던 것이다.
VM은 독립성이 강하다.
그래서 그만큼 무겁다.
VM을 3개 실행한다면:
VM A
├── Application
├── OS
└── Kernel
VM B
├── Application
├── OS
└── Kernel
VM C
├── Application
├── OS
└── Kernel
각 VM마다 OS와 Kernel이 각각 필요하다.
OS 자체도 CPU와 RAM을 사용하기 때문에 VM이 늘어날수록 추가적인 자원이 필요하다.
그리고 새로운 VM을 실행할 때는 운영체제를 부팅하는 과정도 필요하다.
이런 불편함 때문이었을까? 다시 천재들은 아래와 같은 생각을 하게 된다.
애플리케이션은 서로 분리하되, OS와 Kernel까지 매번 따로 만들 필요가 있을까?
이러한 방향에서 등장하는 것이 Container이다.
Container는 Host OS의 Kernel을 공유하면서 애플리케이션의 실행 환경을 서로 격리하는 방식이다.
VM과 가장 큰 차이는 Kernel을 어떻게 사용하는가이다.
VM에서는:
VM A → Kernel A
VM B → Kernel B
VM C → Kernel C
각 VM이 자기 Kernel을 가지고 있다.
하지만 Container에서는:
Container A ─┐
Container B ─┼── Host Kernel
Container C ─┘
여러 Container가 하나의 Host Kernel을 공유한다.
그래서 흔히
Container는 OS를 공유한다.
라고 표현하기도 하고 이렇게 배웠지만, 조금 더 정확하게
Container는 자기만의 독립적인 Kernel을 가지지 않고 Host OS의 Kernel을 공유한다.
라고 이해해두자.
Docker를 처음 배울 때 Container를 작은 가상 컴퓨터라고 생각하기 쉬웠다.
하지만 VM과는 구조가 다르다.
Container는 새로운 컴퓨터와 Kernel을 하나 만드는 것이 아니다.
Host OS 위에서 실행되는 Process를 서로 독립된 환경처럼 격리해서 실행하는 것에 가깝다.
예를 들어 하나의 Linux 서버에서 다음 Process들이 실행된다고 했을 때,
Host Linux
├── Spring Boot Process
├── Redis Process
└── MySQL Process
Container를 사용하면:
Container A
└── Spring Boot
Container B
└── Redis
Container C
└── MySQL
처럼 서로 분리된 환경에서 실행할 수 있다.
하지만 이 Process들을 실제로 관리하는 Kernel은 하나다.
Spring Boot Container ─┐
Redis Container ─┼── Host Linux Kernel
MySQL Container ─┘
즉,
Container 내부에서 실행되는 Application도 결국 Host Kernel이 관리하는 Process이다.
당연히 사용한다.
Container라고 해서 새로운 CPU나 RAM이 만들어지는 것은 아니다.
Host 컴퓨터가 가지고 있는 CPU와 RAM을 사용한다.
Physical Server
CPU
RAM
Disk
↓
Host OS
↓
Host Kernel
│
├── Spring Boot Container
├── Redis Container
└── MySQL Container
즉 여러 Container가 같은 Host의 CPU와 RAM을 공유한다.
여기에서 VM과 Container의 자원 관리 방식에 중요한 차이가 생긴다.
VM에서는 보통 VM을 생성할 때 사용할 vCPU와 RAM 같은 사양을 VM 단위로 정한다.
예를 들어:
Physical Server
CPU 16 Core
RAM 64GB
↓ Hypervisor
VM A
├── vCPU 4
└── RAM 16GB
VM B
├── vCPU 4
└── RAM 16GB
VM C
├── vCPU 8
└── RAM 32GB
VM 입장에서는 자신에게 제공된 CPU와 RAM을 가진 하나의 컴퓨터처럼 동작한다.
Container는 조금 다르다.
다음과 같은 Host Server가 있다고 해보자.
Host Server
CPU 8 Core
RAM 16GB
그리고 Container 세 개가 실행되고 있다.
Spring Boot Container
Redis Container
MySQL Container
별도로 CPU나 Memory 제한을 설정하지 않았다면 이 Container들은 기본적으로 Host가 가지고 있는 CPU와 RAM을 함께 사용한다.
예를 들어 MySQL Container가 갑자기 Memory를 많이 사용한다고 해보자.
Host RAM 16GB
MySQL → 12GB 사용
Spring Boot → RAM 필요
Redis → RAM 필요
그러면 Spring Boot와 Redis가 사용할 수 있는 Memory가 부족해질 수 있다.
CPU에서도 비슷한 일이 발생할 수 있다.
Container A
CPU를 계속 많이 사용
↓
Host CPU 경쟁 증가
↓
Container B / C 성능 저하
하나의 Container가 자원을 많이 사용한다고 해서 다른 Container가 CPU를 무조건 하나도 받지 못하는 것은 아니다.
운영체제의 Scheduler가 CPU 시간을 나누어주기 때문이다.
하지만 특정 Container가 자원을 지나치게 많이 사용하면 같은 Host에서 실행되는 다른 Container의 성능까지 영향을 받을 수 있다.
이러한 상황은 Noisy Neighbor 문제와도 연결된다.
특정 Container 하나가 Host의 자원을 과도하게 사용하는 것을 방지하기 위해 Container마다 Resource Limit을 설정할 수 있다.
Docker에서는 Linux의 cgroup(Control Groups) 기능을 이용한다.
예를 들어 Spring Boot Container가:
CPU 최대 1 CPU
RAM 최대 512MB
까지만 사용하도록 제한하고 싶다면 다음과 같이 설정할 수 있다.
docker run \
--cpus="1.0" \
--memory="512m" \
my-spring-app
각 옵션은 다음 의미이다.
--cpus="1.0"
→ CPU 사용량 제한
--memory="512m"
→ Memory 최대 사용량을 512MB로 제한
이렇게 여러 Container에 각각 제한을 설정할 수 있다.
Host Server
CPU 8 Core
RAM 16GB
│
▼
Host Kernel
│
├── Spring Boot Container
│ CPU ≤ 1
│ RAM ≤ 512MB
│
├── Redis Container
│ CPU ≤ 1
│ RAM ≤ 1GB
│
└── MySQL Container
CPU ≤ 2
RAM ≤ 4GB
이렇게 하면 하나의 Container가 Host의 RAM을 거의 전부 사용하거나 CPU를 과도하게 점유하면서 다른 Container에 영향을 주는 것을 줄일 수 있다.
cgroup(Control Groups)은 Process가 CPU, Memory 등의 컴퓨터 자원을 얼마나 사용할 수 있는지 제한하고 관리하는 Linux 기능이다.
Container 역시 결국 Process이기 때문에 Docker가 cgroup을 이용해서 자원을 관리할 수 있다.
CPU 제한
Memory 제한
I/O 제한
Process 수 제한
...
그리고 Container와 함께 자주 등장하는 또 하나의 Linux 기능이 Namespace이다.
두 개를 간단히 구분하면:
Namespace
→ 서로 다른 공간처럼 보이게 한다.
cgroup
→ 사용할 수 있는 자원의 양을 관리한다.
라고 이해할 수 있다.
즉 Container를 만들 때:
Namespace
→ 격리
cgroup
→ 자원 제한
이라는 역할을 한다.
Docker는 Container를 생성하고 실행하고 관리할 수 있도록 도와주는 대표적인 Container Platform이다.
예를 들어 Spring Boot 애플리케이션을 Docker Image로 만든다고 했을 때,
FROM eclipse-temurin:17-jdk
COPY app.jar app.jar
ENTRYPOINT ["java", "-jar", "app.jar"]
Image를 생성한다.
docker build -t my-app .
그리고 실행한다.
docker run my-app
전체 흐름은 다음과 같다.
Dockerfile
↓
docker build
↓
Docker Image
↓
docker run
↓
Container
↓
Java Process
↓
Spring Boot 실행
결국 Container에서 Spring Boot를 실행하더라도 프로그램 실행의 기본 구조는 변하지 않는다.
Disk
↓
RAM
↓
CPU
다만 이 자원을 Host Kernel을 통해 사용한다는 것이 중요하다.
VM은 가상의 컴퓨터 자체를 만든다.

각 VM이 자신의 OS와 Kernel을 가진다.
Container는 Application 실행 환경을 격리한다.

여러 Container가 Host Kernel을 공유한다.
| 구분 | VM | Container |
|---|---|---|
| 가상화 단위 | 컴퓨터 전체 | Application 실행 환경 |
| OS | VM마다 존재 | Host OS 기반 |
| Kernel | VM마다 존재 | Host Kernel 공유 |
| CPU/RAM | VM 사양 단위로 가상 자원 제공 | Host 자원을 공유 |
| 자원 제한 | VM 구성 시 vCPU/RAM 등을 정함 | 별도로 Resource Limit 설정 가능 |
| 시작 속도 | 상대적으로 느림 | 빠름 |
| 격리 수준 | 높음 | VM보다 상대적으로 낮음 |
| 구분 | VM | Container |
|---|---|---|
| 장점 | 격리 수준이 높고 서로 다른 OS 환경을 구성하기 좋다. | 가볍고 실행이 빠르며 자원 효율과 배포 편의성이 좋다. |
| 단점 | OS와 Kernel을 각각 실행하므로 상대적으로 무겁고 자원을 많이 사용한다. | Host Kernel과 자원을 공유하기 때문에 Resource Limit을 적절히 관리해야 한다. |
즉,
VM은 독립성과 강한 격리에 강점이 있고, Container는 가벼움과 빠른 실행, 자원 효율에 강점이 있다.
Container가 VM보다 무조건 좋은 것이 아니라 상황과 목적에 따라 선택한다.
처음에는 VM과 Container를 보면 둘 중 하나를 선택해야 하는 관계처럼 보였다.
VM vs Container
하지만 실제로는 함께 사용되는 경우도 많다.
예를 들어 AWS EC2 위에서 Docker를 이용해 Spring Boot를 실행한다면:

즉 VM 위에서 Container를 실행하는 구조도 매우 자연스럽다.
처음부터 다시 연결하면 다음과 같다.
Disk / SSD
↓
RAM
↓
CPU
Disk에 저장된 프로그램을 실행하면 필요한 내용이 RAM에 올라가고 CPU가 실제 명령을 처리한다.
Application
↓
Operating System
↓
Kernel
↓
CPU / RAM / Disk
Kernel이 실제 하드웨어 자원을 관리한다.
Application
↓
VM의 Operating System
↓
VM의 Kernel
↓
VM에 제공된 CPU / RAM / Disk
VM마다 자기 OS와 Kernel을 가진다.
Application
↓
Container
↓
Host Kernel
↓
Host의 CPU / RAM / Disk
Container마다 새로운 Kernel이 생기는 것이 아니라 Host Kernel을 공유한다.
그리고 필요한 경우:
cgroup
↓
CPU / RAM 사용량 제한
을 통해 특정 Container가 Host 자원을 과도하게 사용하는 것을 막을 수 있다.
처음에는 단순히 이렇게 외웠다.
VM
→ 무겁다.
Container
→ 가볍다.
하지만 이제는 왜 그런지 이해할 수 있다.
VM은 하나를 만들 때마다:
Application
Runtime
Operating System
Kernel
이 필요하다.
반면 Container는:
Container A ─┐
Container B ─┼── Host Kernel
Container C ─┘
처럼 기존 Kernel을 공유한다.
그래서 일반적으로 Container는 VM보다:
VM과 Container의 차이는 단순히
VM은 무겁고 Container는 가볍다.
에서 끝나는 것이 아니었다.
앞에서 공부했던 OS, Kernel, CPU, RAM, Disk의 구조와 연결해서 보니 차이가 조금 더 명확해졌다.
VM은:
VM A
→ 자기 OS
→ 자기 Kernel
VM B
→ 자기 OS
→ 자기 Kernel
처럼 독립된 가상 컴퓨터를 만든다.
반면 Container는:
Container A ─┐
Container B ─┼── Host Kernel
Container C ─┘
처럼 Host Kernel을 공유하면서 Application 실행 환경을 격리한다.
그리고 CPU와 RAM 관점에서도 차이가 있다.
VM
→ VM별로 vCPU / RAM 등의 사양을 구성
Container
→ Host CPU / RAM을 함께 사용
→ 필요한 경우 cgroup으로 사용량 제한
Container에 Resource Limit을 설정하지 않으면 특정 Container가 Host의 CPU나 RAM을 과도하게 사용하면서 다른 Container의 성능에도 영향을 줄 수 있다.
그래서 Container 환경에서는 단순히 실행하는 것뿐 아니라 각 Container가 어느 정도의 CPU와 Memory를 사용하도록 허용할 것인지 관리하는 것도 중요하다.
결국 VM과 Container는 완전히 새로운 개념이 아니라,
CPU / RAM / Disk
↑
Kernel
↑
Operating System
↑
Application
이라는 컴퓨터의 기본 구조를 어떤 방식으로 가상화하고 격리해서 사용할 것인가의 차이였다.