15 min read

[Part1. 기본 개념 다지기] 추상화

클라우드는 "추상화"에서 시작해서 "추상화"로 끝납니다. 이 글에서는 과거의 물리 서버가 어떻게 VM 이 됐고, VM 이 어떻게 컨테이너가 됐는지 설명합니다.

김현욱
[Part1. 기본 개념 다지기] 추상화

들어가며 - 컴퓨터는 저렴하지 않다

클라우드가 없던 시절 열심히 만든 소프트웨어를 세상에 공개할 때 서버용 컴퓨터를 구매하는 것이 당연한 수순이었다고 합니다. (제가 처음 코딩 공부를 했을 땐 이미 AWS 가 있었어요!)

라즈베리파이5 (Raspberry Pi 5) 8GB + 가이드북 가격 | 출처: devicemart

라즈베리파이5 8GB 가격 | 출처: devicemart

물리 서버 종류 중 저렴한 편에 속한 라즈베리파이5 가격을 찾아봐도 그렇게 저렴하진 않습니다. 또한 CPU, RAM, LAN, Power, OS 등을 연결, 설치할 수 있어야 하고 잘못 구매했다면 교환/환불처리도 진행해야합니다. 보안설정을 제대로 하지 않는다면 해커들의 먹잇감이 될 수도 있습니다.

INFO

같은 스펙의 VM 가격이 절대적으로 저렴하다는 말은 아닙니다. 장기적으로 봤을 때는 온프레미스가 이득일 수 있습니다. 이 글에서는 VM을 소개하는 것이니까 "초기 도입 비용은 온프레미스가 비싸다!" 로 가정하겠습니다.

Amazon, Google, Microsoft 와 같이 공룡기업들은 엄청 좋은 컴퓨터를 많이 갖고 있습니다. 아무리 운영하는 서비스가 많고, 사용자가 많더라도 사용하지 않는 리소스가 발생할 것입니다.
본인들이 갖고 있는 컴퓨터 자원을 다른 사람들에게 임대해주고, 그 이용료를 받을 수 있도록 해준 기술이 "추상화" 입니다.

이번 글에서는 세상 어딘가에 있는 남의 컴퓨터가 마치 "내 컴퓨터"처럼 느껴질 수 있도록한 "가상화(Virtualization)" 에 대해 다뤄보겠습니다.


복습 - 운영체제가 자원을 분배하는 방법

가상화(Virtualization)을 이해하기 위해서는 운영체제(OS)가 하는 일을 알아야 합니다. 운영체제의 모든 내용을 다루기에는 양이 너무 많으니, 필요한 내용만 간단하게 설명해보겠습니다.

프로세스와 CPU 시간 분할

Chrome, VS Code, Slack 등 컴퓨터에서 실행되고 있는 프로그램들이 실제로는 동시에 수행하는 게 아니다 라는 사실은 아실겁니다. (그렇게 믿습니다..!!) 이러한 것이 가능하도록 한 기법이 "동시성(Concurrency)"입니다.

OS의 스케줄러(Scheduler)가 CPU 시간을 아주 짧게(보통 1~10ms) 쪼개서 각 프로세스(Process: 실행 중인 프로그램의 단위)에게 돌아가며 할당하기 때문입니다. 이 전환이 너무 빠르기 때문에 우리 눈에는 동시 실행처럼 보이는 거죠.

1CPU 시간축
2─────────────────────────────────────────────────────▶
3
4[Chrome][VS Code][Slack][Chrome][VS Code][Slack][Chrome]...
5 1ms 1ms 1ms 1ms 1ms 1ms 1ms

위과 같이 각각의 프로세스(Process)들이 번갈아가며 자신의 프로그램(Program) 중 일부분을 짧은 시간 동안 실행하고 이걸 "시분할(Time-sharing)" 이라고 합니다.

SUCCESS

💡 노트

현대 OS는 단순한 라운드로빈이 아니라 CFS(Completely Fair Scheduler) 같은 정교한 알고리즘을 사용합니다. 각 프로세스에 가중치(Nice value)를 부여하고, 모든 프로세스가 CPU를 "공평하게" 사용했는지 누적 실행 시간으로 추적합니다. 클라우드에서 CPU 크레딧(t-type 인스턴스)이 이 개념에서 파생된 겁니다.

가상 메모리(Virtual Memory) — 모두가 0번지부터 시작할 수 이유

프로세스마다 메모리 주소 공간이 독립적으로 있다는 것을 기억하시나요? Chrome과 VS Code가 둘 다 메모리 주소 0x00400000을 사용하더라도 서로 충돌하지 않습니다. OS가 가상 메모리(Virtual Memory) 를 통해 각 프로세스에게 "자기만의 주소 공간"이 있는 것처럼 착각하게 만들기 때문입니다.

Loading diagram…
가상 주소 변환 과정

OS는 시분할(Time-sharing) 과 가상 메모리 (Virtual Memory)처럼 이미 하나의 물리 자원(CPU, 메모리)를 여러 주체(여러 프로세스)에게 독립적인 공간처럼 나눠주는 일을 하고 있습니다. 가상화는 이 아이디어를 바탕으로 더 고도화한 것입니다.


하이퍼바이저 - OS 위에서 OS를 실행하는 마법

가상화가 없던 시절 환경을 상상해봅시다. OS 요구사항과 같은 환경상의 이유 혹은 소프트웨어 충돌 방지를 위해 특정 애플리케이션들은 서로 간섭받지 않는 독립된 환경이 필요했습니다. 이를 위해서는 물리 서버가 각각 따로 있어야 했고, 서버 리소스 낭비가 너무 심했습니다.

그래서 서버 자체도 CPU 시간, 메모리처럼 나누기 위해 "하이퍼바이저(Hypervisor)"가 등장합니다.

하이퍼바이저 (Hypervisor)란

하이퍼바이저는 하드웨어와 여러 OS 사이에 위치하는 소프트웨어입니다. OS 가 프로세스들에게 가상 메모리를 제공했던 것처럼, 하이퍼바이저는 여러 OS 에게 "가상 하드웨어" 를 제공합니다.

Hypervisor | 출처: Geeks for Geeks

Hypervisor | 출처: Geeks for Geeks

각 Virtual Machine(이하 VM)은 자신이 독립된 물리 서버 위에 있다고 "믿습니다". VM 별로 BIOS, 커널, 드라이버가 있지만 실제로는 하이퍼바이저가 모든 하드웨어 접근을 가로채서 중재하고 있는 것입니다.

1낮음 ◀─────────────────────────────────────────────▶ 높음
2 프로세스 컨테이너 VM 전용 물리 서버
3(같은 커널) (namespace) (하이퍼바이저) (Dedicated Host)
4소프트웨어 소프트웨어 소프트웨어 격리 + 하드웨어까지
5격리만 격리만 하드웨어 공유 완전 분리
WARNING

💡 주의

VM이 소프트웨어 수준의 충돌과 간섭으로부터는 잘 격리됐지만, 물리 하드웨어를 공유한다는 사실 자체는 바뀌지 않습니다.VM 들은 L3 캐시와 메모리버스를 공유때문에 하드웨어 취약점을 통해 다른 VM 의 데이터를 접근할 수 있는 가능성이 존재합니다.

2018년에 발견된 Spectre/Meltdown이 대표적인 사례로, L3 캐시 접근 타이밍 차이를 이용해 다른 VM의 메모리 내용을 간접적으로 읽어낼 수 있었습니다.

이 때문에 금융권이나 공공기관처럼 보안 규정이 엄격한 곳에서는 AWS Dedicated Host(물리 서버 한 대를 혼자 점유하는 옵션)를 요구하는 경우가 있습니다.

Type1 vs Type2 하이퍼바이저

하이퍼바이저에는 두 종류(Type1, Type2)가 있습니다.

구분 Type 1 (Bare-metal) Type 2 (Hosted)
위치 하드웨어 위에 직접 설치 범용 OS 위에 설치
예시 VMware ESXi, KVM, Xen, Hyper-V VirtualBox, VMware Workstation
성능 매우 높음 (OS 오버헤드 없음) 낮음 (OS를 한 번 더 거침)
사용처 데이터센터, 클라우드 개발자 로컬 환경
Type1 Type2 비교표
Type of Hypervisor | 출처: Geeks for Geeks

Type of Hypervisor | 출처: Geeks for Geeks

AWS, GCP, Azure 모두 Type 1 하이퍼바이저를 사용합니다. 여러분이 EC2 인스턴스를 만들면, AWS의 데이터센터 어딘가에 있는 물리 서버에서 하이퍼바이저가 여러분 전용 VM을 하나 생성하는 것입니다.

SUCCESS

💡 노트 — AWS Nitro 시스템

초기 AWS는 Xen 하이퍼바이저를 사용했습니다. 그런데 Xen 방식에는 구조적 한계가 있었습니다.

2017년부터 AWS는 자체 개발한 Nitro 하이퍼바이저로 전환하기 시작했습니다. Nitro를 이해하려면 먼저 기존 방식의 문제를 알아야 합니다.

Xen 하이퍼바이저는 소프트웨어로 구현되어 있어서 VM이 네트워크 패킷을 보내거나 디스크를 읽을 때마다 하이퍼바이저 코드가 물리 서버의 메인 CPU에서 실행됐습니다. 즉, 하이퍼바이저와 VM이 같은 CPU를 놓고 경쟁하는 구조였습니다. 고객이 EC2 인스턴스에 4 vCPU를 할당받아도, 실제로는 그 중 일부를 하이퍼바이저가 네트워크/스토리지 처리를 위해 조금씩 빼앗아 가는 셈이었죠.

Nitro의 해결책은 간단합니다. 하이퍼바이저가 하던 일을 별도의 전용 하드웨어(Nitro Card)가 대신 처리하게 만든 것입니다. 마치 사무실에서 총무팀(하이퍼바이저)이 하던 네트워크 관리, 보안 점검, 스토리지 관리 업무를 전담 외주 업체(Nitro Card)에 넘겨버린 것과 같습니다. 덕분에 메인 CPU는 고객 VM 실행에만 100% 집중할 수 있게 됐습니다.

현세대 EC2 인스턴스(m5, c5, r5 이후)가 이전 세대보다 성능이 크게 좋아진 이유 중 하나가 바로 이 Nitro 전환입니다.

VM 한계와 컨테이너의 등장

VM 덕분에 하나의 물리 서버를 여러 고객이 나눠 쓸 수 있게 됐습니다. 그런데 개발자 입장에서 새로운 문제가 생겼습니다. VM은 훌륭하지만 너무 무겁다는 것입니다.

여러분이 웹 서비스를 개발한다고 가정해봅시다. 이 서비스는 Python Flask 앱, Redis 캐시, Nginx 웹서버로 구성됩니다. 이것들을 각각 독립적으로 배포하고 싶어서 VM을 3개 만들었습니다.

앱 자체는 수십 MB인데 VM은 2~3GB입니다. 부팅만 해도 1~2분이 걸리고요. 100개의 마이크로서비스를 운영한다면 이 낭비는 더욱 심해지게 됐습니다.

1VM 방식의 문제점
2
3VM #1 (Flask 앱) VM #2 (Redis) VM #3 (Nginx)
4┌──────────────┐ ┌──────────────┐ ┌──────────────┐
5│ Flask App │ │ Redis │ │ Nginx │
6│ (50MB) │ │ (10MB) │ │ (20MB) │
7├──────────────┤ ├──────────────┤ ├──────────────┤
8│ Python, │ │ Ubuntu OS │ │ Ubuntu OS │
9│ pip 패키지 │ │ 커널, │ │ 커널, │
10│ (200MB) │ │ 기본 유틸리티 │ │ 기본 유틸리티 │
11├──────────────┤ │ (2GB) │ │ (2GB) │
12│ Ubuntu OS │ ├──────────────┤ ├──────────────┤
13│ 커널, 드라이버 │ │ 가상 하드웨어 │ │ 가상 하드웨어 │
14│ (2GB) │ └──────────────┘ └──────────────┘
15└──────────────┘
16 실제 앱: 50MB 실제 앱: 10MB 실제 앱: 20MB
17 VM 전체: ~2.5GB VM 전체: ~2GB VM 전체: ~2GB

더 근본적인 문제가 있습니다. 같은 회사 서비스끼리는 사실 OS를 굳이 따로 가질 이유가 없습니다. Ubuntu Linux 커널은 이미 물리 서버(혹은 VM 호스트)에 하나 있는데, 각 앱마다 복사본을 가져야 할 필요성에 의문이 생기기 시작했습니다.

리눅스 커널 기능 사용하기 - Namespace, cgroups

컨테이너(Container)는 VM처럼 하드웨어를 가상화하는 게 아니라, OS의 기능을 활용해서 프로세스를 격리하는 방식입니다. 핵심 기술은 Linux 커널의 Namespace 와 cgroups 입니다.

① Namespace — 독립적인 환경을 만들어주는 기술

Namespace는 프로세스가 볼 수 있는 시스템 자원의 범위를 제한합니다. 예를 들어 PID Namespace를 사용하면, 컨테이너 안에서 실행 중인 프로세스는 자기 자신을 PID 1번(마치 시스템에서 처음으로 실행된 프로세스인 것처럼)으로 인식하고, 컨테이너 밖의 다른 프로세스들은 아예 보이지 않습니다.

Namespace 격리 대상
pid 프로세스 ID 공간
net 네트워크 인터페이스, IP, 라우팅 테이블
mnt 파일시스템 마운트 포인트
uts 호스트명, 도메인명
ipc System V IPC, POSIX 메시지 큐
user 사용자/그룹 ID
cgroup cgroup 루트 디렉토리
Linux 커널이 지원하는 Namespace

② cgroups (Control Groups) — 자원 사용량을 제한하는 기술

Namespace가 "무엇을 볼 수 있는지"를 제어한다면, cgroups는 "얼마나 쓸 수 있는지"를 제어합니다. 특정 프로세스 그룹이 CPU를 최대 2코어, 메모리를 최대 4GB까지만 쓰도록 강제하는 게 cgroups입니다.

1# cgroups 예시 — 이 프로세스 그룹은 CPU 50%, 메모리 2GB 제한
2/sys/fs/cgroup/memory/my-container/memory.limit_in_bytes = 2147483648
3/sys/fs/cgroup/cpu/my-container/cpu.shares = 512 # 전체의 50%

컨테이너는 이 두 기술을 조합하여, 하나의 OS 커널을 공유하면서도 프로세스들이 완전히 격리된 것처럼 실행할 수 있도록 하는 것입니다.

1컨테이너 방식
2
3물리/가상 서버
4┌─────────────────────────────────────────────────────┐
5│ Linux 커널 │
6│ (Namespace + cgroups 지원) │
7├──────────────┬──────────────┬───────────────────────┤
8│ 컨테이너 #1 │ 컨테이너 #2 │ 컨테이너 #3 │
9│ │ │ │
10│ Flask App │ Redis │ Nginx │
11│ + Python │ (바이너리만) │ (바이너리만) │
12│ (250MB) │ (15MB) │ (25MB) │
13│ │ │ │
14│ [격리된 네트워크, 파일시스템, 프로세스 공간] │
15│ 커널은 하나만 공유 │
16└──────────────┴──────────────┴───────────────────────┘

VM과 컨테이너의 가장 큰 차이는 부팅 시간오버헤드입니다.

항목 VM 컨테이너
시작 시간 수십 초 ~ 수 분 수백 ms ~ 수 초
크기 수 GB (OS 포함) 수십 ~ 수백 MB
격리 수준 강함 (소프트웨어 격리 + 하드웨어 공유) 중간 (커널 공유, 프로세스 격리)
커널 각자 별도 커널 호스트 커널 공유
주요 사용처 다른 OS 필요 시, 강한 보안 격리 마이크로서비스, CI/CD, 빠른 배포
SUCCESS

💡 노트 - 컨테이너는 "격리된 프로세스"다

Docker를 처음 배울 때 많은 분들이 컨테이너를 "가벼운 VM"으로 이해합니다. 이 비유가 완전히 틀리진 않지만, 더 정확한 이해는 "격리된 프로세스의 묶음"입니다. 호스트에서 ps aux를 실행하면 컨테이너 안의 프로세스가 그냥 보입니다. 컨테이너는 하이퍼바이저 같은 별도의 소프트웨어 계층이 없고, 결국 커널 위에서 돌아가는 프로세스입니다. 이 사실이 보안 설계에서 중요한 의미를 가집니다.컨테이너 탈출(Container Escape) 취약점이 가능한 이유가 바로 커널을 공유하기 때문입니다.

[심화] 하이퍼바이저는 CPU를 어떻게 나눌까

하이퍼바이저가 CPU를 VM들에게 나눠줄 때 어떤 일이 발생하는지 살짝 더 깊게 들어가보겠습니다.

CPU 가상화의 두 가지 방식

- Full Virtualization

초기 하이퍼바이저는 CPU에 직접 접근하는 명령어(Privileged Instruction)를 소프트웨어상에서 가로채 실행했습니다. VM 안의 OS가 "하드웨어에 직접 명령을 내린다"고 착각하게 만든 것입니다.
동작하기는 하지만 에뮬레이션이었기 때문에 매우 느렸습니다.

- Hardware-Assisted Virtualization

Intel VT-X(Virtualization Technology) 와 AMD-V(AMD Virtualization) 같은 CPU 기능이 등장하면서 게임이 바뀌었습니다. 이 기능은 "CPU야, 너도 가상화를 직접 도와줘" 라고 하드웨어 설계 수준에서 추가한 기능이라고 생각하시면 됩니다. 즉, CPU 자체에 "게스트 모드(Guest Mode)"가 추가되어, VM 안의 OS가 별도의 에뮬레이션 없이 직접 CPU 명령을 실행할 수 있게 됐습니다. 성능이 네이티브에 가깝게 향상됐고, 현재 모든 클라우드가 이 방식을 사용합니다.

1CPU Ring 레벨 (전통적인 보안 구조)
2
3Ring 0: 커널 모드 (OS 커널, 하드웨어 직접 접근 가능)
4Ring 1~2: 거의 사용 안 함
5Ring 3: 사용자 모드 (일반 애플리케이션)
6
7하이퍼바이저 도입 후
8
9Ring -1: VMX root mode (하이퍼바이저, Intel VT-x 기준)
10Ring 0: VMX non-root mode (Guest OS 커널)
11Ring 3: VMX non-root mode (Guest 애플리케이션)
INFO

Intel VT-X와 AMD-V 는 CPU가 하이퍼바이저를 하드웨어 수준에서 직접 지원하기 위해 추가한 기능입니다.

기존 CPU의 실행모드에서는 Guest OS(VM 안의 OS)도 자기가 커널이니까 Ring0(커널모드)에서 실행되려고 했습니다. 그런데 하이퍼바이저도 Ring0이어야 하드웨어를 제어할 수 있었습니다. 즉, Ring0를 두 주체가 동시에 차지할 수 없는 구조였습니다.

위 기술은 Guest OS가 하드웨어에 접근하는 Privileged Instruction 을 실행하면, CPU가 자동으로 VM Exit를 발생시켜 하이퍼바이저로 제어권을 넘길 수 있게 하는 새로운 실행모드입니다. 하이퍼바이저가 처리를 마치면 VM Entry로 Guest OS를 재개시킵니다. 이 전환이 소프트웨어 에뮬레이션 없이 하드웨어에서 직접 일어나기 때문에 훨씬 빠릅니다.

SUCCESS

💡 노트 — vCPU와 물리 CPU의 관계
EC2에서 4 vCPU라고 표시된 인스턴스를 생성하면, 이 vCPU들이 항상 4개의 물리 CPU 코어에 고정되지 않습니다. 하이퍼바이저의 스케줄러가 물리 코어들에 vCPU를 동적으로 매핑합니다. 다만 AWS는 물리 코어보다 많은 vCPU 할당하는 오버커밋 비율을 엄격하게 관리하고, C/M/R 패밀리 같은 범용 인스턴스에서는 vCPU 1개 = 물리 하이퍼스레드 1개의 비율을 유지합니다. 반면 T-타입(t2, t3) 인스턴스는 CPU 크레딧 시스템을 사용해 버스트를 허용하는 대신 기준 성능 이하로 평소에 동작할 수 있습니다.


AWS 가 이 기술을 사용하는 방법

지금까지 물리 서버의 추상화 기본 개념을 다뤘습니다. 이 아래부터는 AWS 서비스에서 어떻게 적용되는지 간단하게 살펴보겠습니다.

EC2 - VM 그 자체

AWS 의 대표 서비스 중 하나인 EC2(Elastic Compute Cloud) 인스턴스는 AWS 데이터센터의 물리 서버 위에 Nitro 하이퍼바이저를 기반으로 구현된 VM입니다. t3.medium, m5.xlarge 같은 인스턴스 타입은 이 VM에 할당되는 vCPU 수와 메모리 크기 등에 따라 분류한 것입니다.

1AWS 서버 구조 ㅇ
2┌──────────────────────────────────────────────────────┐
3│ 물리 서버 (예: 192 vCPU, 768GB RAM) │
4├──────────────────────────────────────────────────────┤
5│ Nitro Hypervisor │
6│ ┌──────────┐ ┌───────────────┐ ┌──────────────┐ │
7│ │Nitro Card│ │ Nitro Card │ │ Nitro Card │ │
8│ │(Network) │ │ (EBS/NVMe) │ │ (Security) │ │
9│ └──────────┘ └───────────────┘ └──────────────┘ │
10├──────────────────────────────────────────────────────┤
11│ t3.medium │ m5.xlarge │ c5.4xlarge │ ... (수십 개) │
12│ (2vCPU, │ (4vCPU, │ (16vCPU, │ │
13│ 4GB) │ 16GB) │ 32GB) │ │
14│ 고객 A │ 고객 B │ 고객 C │ │
15└──────────────────────────────────────────────────────┘

ECS/EKS — 컨테이너 관리 레이어

EC2에 Docker를 직접 설치해서 컨테이너를 실행할 수도 있지만, 실제 프로덕션 환경에서는 수십~수백 개의 컨테이너를 관리해야 합니다. 어떤 서버에 배포할지, 서버가 죽으면 다른 서버로 재시작할지, 트래픽에 따라 컨테이너를 늘릴지 등에 대한 정책을 세우고 구현해야합니다. 이런 작업을 자동화하는 시스템이 컨테이너 오케스트레이션(Container Orchestration) 입니다.

- ECS (Elastic Container Service): AWS가 자체 개발한 컨테이너 오케스트레이터

- EKS (Elastic Kubernetes Service): 오픈소스 Kubernetes를 AWS에서 관리형으로 제공

Fargate — 서버를 신경 쓰기 싫을 때

ECS나 EKS를 사용할 때, 컨테이너를 실제로 실행할 EC2 서버를 직접 관리해야 하는 번거로움이 있습니다. Fargate는 이 서버 관리까지 AWS가 대신해주는 방식입니다. 컨테이너 이미지와 필요한 CPU/메모리 양만 지정하면, 컨테이너가 어떤 서버에서 실행되는지는 신경 쓰지 않아도 됩니다.

Lambda — 함수 단위로 실행

Lambda는 더 나아가 컨테이너도 아닌 함수(Function) 단위로 코드를 실행합니다. 내부적으로는 AWS가 관리하는 경량 VM(Firecracker microVM 기반) 위에서 실행됩니다.