soobook
BACKEND

Type 1 vs Type 2 Hypervisor

하이퍼바이저를 나누는 기준은 하나다. 하드웨어 바로 위에서 도는가, 아니면 이미 깔린 OS 위에서 도는가.

Intro

VM(virtual machine)을 만든다는 것은 CPU, 메모리, 디스크, 네트워크 카드 같은 하드웨어를 소프트웨어로 흉내 낸다는 뜻이다.

그 흉내를 내는 소프트웨어를 하이퍼바이저(hypervisor) 또는 VMM(virtual machine monitor)이라고 부른다.

하이퍼바이저는 물리 하드웨어를 여러 VM에 나눠 주고, 각 VM이 자기만 컴퓨터를 독차지한다고 믿게 만든다.

VM 안에서 도는 OS를 guest OS라고 하고, 그 아래에서 자원을 실제로 쥐고 있는 쪽을 host라고 부른다.

하나 명확히 해야할 것은, OS 가 사이에 끼어있으니 Type 2 이라고 단정지어선 안된다는 것이다.

{
  "diagram": "html-diagram",
  "variant": "explainer",
  "title": "At a glance: Type 1 vs Type 2",
  "width": 980,
  "height": 576,
  "mobileWidth": 900,
  "regions": [
    {
      "id": "type1-side",
      "label": "Type 1\\nbare-metal hypervisor",
      "x": 36,
      "y": 40,
      "width": 420,
      "height": 496
    },
    {
      "id": "type2-side",
      "label": "Type 2\\nhosted hypervisor",
      "x": 524,
      "y": 40,
      "width": 420,
      "height": 496
    }
  ],
  "nodes": [
    {
      "id": "t1-hw",
      "kind": "memory",
      "label": "Hardware",
      "caption": "CPU, RAM, Disk, NIC",
      "x": 246,
      "y": 486,
      "width": 300,
      "height": 60
    },
    {
      "id": "t1-hyp",
      "kind": "switch",
      "label": "Hypervisor",
      "caption": "runs on bare metal",
      "x": 246,
      "y": 375,
      "width": 300,
      "height": 60
    },
    {
      "id": "t1-vm-a",
      "kind": "host",
      "label": "VM A",
      "caption": "guest OS",
      "x": 176,
      "y": 265,
      "width": 130,
      "height": 60
    },
    {
      "id": "t1-vm-b",
      "kind": "host",
      "label": "VM B",
      "caption": "guest OS",
      "x": 316,
      "y": 265,
      "width": 130,
      "height": 60
    },
    {
      "id": "t1-app-a",
      "kind": "note",
      "label": "App",
      "x": 176,
      "y": 154,
      "width": 130,
      "height": 56
    },
    {
      "id": "t1-app-b",
      "kind": "note",
      "label": "App",
      "x": 316,
      "y": 154,
      "width": 130,
      "height": 56
    },
    {
      "id": "t2-hw",
      "kind": "memory",
      "label": "Hardware",
      "caption": "CPU, RAM, Disk, NIC",
      "x": 734,
      "y": 486,
      "width": 300,
      "height": 60
    },
    {
      "id": "t2-host-os",
      "kind": "host",
      "label": "Host OS",
      "caption": "Windows, macOS, Linux",
      "x": 734,
      "y": 403,
      "width": 300,
      "height": 60
    },
    {
      "id": "t2-hyp",
      "kind": "switch",
      "label": "Hypervisor",
      "caption": "runs as an app",
      "x": 734,
      "y": 320,
      "width": 300,
      "height": 60
    },
    {
      "id": "t2-vm-a",
      "kind": "host",
      "label": "VM A",
      "caption": "guest OS",
      "x": 664,
      "y": 237,
      "width": 128,
      "height": 56
    },
    {
      "id": "t2-vm-b",
      "kind": "host",
      "label": "VM B",
      "caption": "guest OS",
      "x": 804,
      "y": 237,
      "width": 128,
      "height": 56
    },
    {
      "id": "t2-app-a",
      "kind": "note",
      "label": "App",
      "x": 664,
      "y": 154,
      "width": 128,
      "height": 52
    },
    {
      "id": "t2-app-b",
      "kind": "note",
      "label": "App",
      "x": 804,
      "y": 154,
      "width": 128,
      "height": 52
    }
  ],
  "links": [
    {
      "id": "t1-hw-hyp",
      "points": [[246, 456], [246, 405]],
      "tone": "primary",
      "flow": { "speed": "normal", "emphasis": "soft" }
    },
    {
      "id": "t1-hyp-vma",
      "points": [[246, 345], [176, 295]],
      "tone": "primary",
      "flow": { "speed": "normal", "emphasis": "soft" }
    },
    {
      "id": "t1-hyp-vmb",
      "points": [[246, 345], [316, 295]],
      "tone": "primary",
      "flow": { "speed": "normal", "emphasis": "soft" }
    },
    {
      "id": "t1-vma-appa",
      "points": [[176, 235], [176, 182]],
      "tone": "primary",
      "flow": { "speed": "normal", "count": 2, "emphasis": "strong" }
    },
    {
      "id": "t1-vmb-appb",
      "points": [[316, 235], [316, 182]],
      "tone": "primary",
      "flow": { "speed": "normal", "count": 2, "emphasis": "strong" }
    },
    {
      "id": "t2-hw-host",
      "points": [[734, 456], [734, 433]],
      "tone": "primary",
      "flow": { "speed": "normal", "emphasis": "soft" }
    },
    {
      "id": "t2-host-hyp",
      "points": [[734, 373], [734, 350]],
      "tone": "muted",
      "flow": { "speed": "slow", "emphasis": "muted" }
    },
    {
      "id": "t2-hyp-vma",
      "points": [[734, 290], [664, 265]],
      "tone": "primary",
      "flow": { "speed": "normal", "emphasis": "soft" }
    },
    {
      "id": "t2-hyp-vmb",
      "points": [[734, 290], [804, 265]],
      "tone": "primary",
      "flow": { "speed": "normal", "emphasis": "soft" }
    },
    {
      "id": "t2-vma-appa",
      "points": [[664, 209], [664, 180]],
      "tone": "primary",
      "flow": { "speed": "normal", "count": 2, "emphasis": "strong" }
    },
    {
      "id": "t2-vmb-appb",
      "points": [[804, 209], [804, 180]],
      "tone": "primary",
      "flow": { "speed": "normal", "count": 2, "emphasis": "strong" }
    }
  ]
}

Type 1은 하이퍼바이저가 하드웨어를 직접 딛고 선다. Type 2는 이미 깔린 OS 위에 하이퍼바이저가 하나의 프로그램처럼 얹힌다.

Hypervisor

하이퍼바이저가 하는 일의 핵심은 특권 명령(privileged instruction)의 가로채기다.

guest OS는 자기가 진짜 하드웨어를 다룬다고 믿는다. 그래서 커널이 하는 것처럼 CPU의 특권 명령(privileged instruction)을 그냥 실행하려 든다.

페이지 테이블을 건드리거나, 인터럽트를 끄거나, I/O 포트에 직접 쓰는 명령들이다.

문제는 그 명령이 진짜로 실행되면 VM들끼리 서로의 상태를 짓밟는다는 것이다.

그래서 하이퍼바이저는 guest가 특권 명령을 실행하려는 순간에 제어를 가로챈다. 이를 trap이라 하고, 가로챈 뒤 하이퍼바이저가 대신 안전하게 처리한 다음 guest로 돌려보내는 방식을 trap-and-emulate라고 부른다.

즉 하이퍼바이저는 “여러 guest가 각자 하드웨어를 가진 척하게 두되, 위험한 순간에만 끼어드는” 얇은 중재자다.

Type 1인지 Type 2인지는 이 중재자를 어디에 두느냐의 문제일 뿐, 하는 일 자체는 같다.

최신 x86 CPU는 이 가로채기를 하드웨어로 돕는다. Intel VT-x, AMD-V 같은 확장이 그것이다. 덕분에 소프트웨어만으로 흉내 내던 시절보다 오버헤드가 크게 줄었고, Type 1과 Type 2의 성능 격차도 예전보다는 좁아졌다.

Type 1: bare-metal

Type 1 하이퍼바이저는 하드웨어 위에서 가장 먼저 부팅된다. 그 아래에 다른 OS가 없다.

하이퍼바이저 자신이 스케줄러와 메모리 관리자를 가진, 아주 얇고 목적이 뚜렷한 OS에 가깝다. 그 위에 올라가는 모든 것은 guest VM이다.

대표적인 예로 VMware ESXi, Xen, Microsoft Hyper-V, 그리고 리눅스의 KVM이 있다. 서버, 데이터센터, 퍼블릭 클라우드의 VM은 거의 다 이 계열이다.

Type 1을 쓰는 이유는 두 가지로 요약된다.

첫째는 성능이다. guest와 하드웨어 사이에 범용 OS 한 층이 없으니 I/O 경로가 짧다. guest의 요청이 하이퍼바이저를 거쳐 곧바로 하드웨어에 닿는다.

둘째는 격리다. 하이퍼바이저는 오직 VM을 돌리는 일만 하도록 만들어졌다. 공격 표면(attack surface)이 좁고, 한 VM이 터져도 다른 VM으로 번지기 어렵다.

Type 2: hosted

Type 2 하이퍼바이저는 이미 부팅된 host OS 위에서 하나의 응용 프로그램으로 실행된다.

노트북에서 Windows나 macOS를 쓰다가 그 안에 리눅스 VM 하나를 띄우는 상황이 정확히 이 그림이다.

host OS가 먼저 있고, 그 위에 하이퍼바이저 앱이 있고, 그 앱이 VM을 만든다.

VMware Workstation, VMware Fusion, Oracle VirtualBox, Parallels Desktop이 여기에 속한다.

Type 2의 장점은 편의성이다. 평소 쓰던 OS를 그대로 두고, 앱 하나 설치하듯 가상화 환경을 얻는다.

전용 서버를 따로 마련할 필요도, 부팅 디스크를 갈아엎을 필요도 없다.

대신 guest의 요청이 하드웨어에 닿으려면 하이퍼바이저를 지나 host OS의 커널까지 한 번 더 통과해야 한다.

이 추가 경유 때문에 I/O가 무거운 작업에서 Type 1보다 불리하고, host OS가 흔들리면 그 위의 VM도 같이 흔들린다.

그래서 Type 2는 개발, 테스트, 학습, 데스크톱용 격리처럼 “성능보다 손쉬움이 중요한” 자리에 어울린다.

Performance and Isolation

두 방식의 차이는 결국 guest가 하드웨어에 닿기까지 지나는 층의 수로 정리된다.

Type 1에서 요청이 지나는 층은 대략 이렇다.

 

guest OShypervisorhardware\text{guest OS} \rightarrow \text{hypervisor} \rightarrow \text{hardware}

 

Type 2에서는 host OS가 한 층 더 끼어든다.

 

guest OShypervisorhost OShardware\text{guest OS} \rightarrow \text{hypervisor} \rightarrow \text{host OS} \rightarrow \text{hardware}

 

층이 하나 더 있다는 것은 context switch와 데이터 복사가 그만큼 더 생긴다는 뜻이다.

디스크와 네트워크처럼 자잘한 I/O가 많이 일어나는 작업일수록 이 차이가 쌓여서 드러난다.

격리도 같은 논리다. Type 2의 host OS는 VM만 돌리는 기계가 아니라 온갖 프로그램이 함께 사는 범용 OS다.

그 안의 어떤 취약점이든 그 위의 VM까지 위태롭게 만들 수 있다. Type 1은 애초에 그 범용 층이 없다.

KVM and Hyper-V

여기까지는 교과서적인 얘기고, 실제로는 생각만큼 깔끔하지 않다.

가장 자주 논쟁이 되는 것이 KVM이다. KVM은 리눅스 커널 안에 들어가는 모듈이다.

modprobe kvm

위 명령어로 이 모듈을 켜는 순간, Linux Kernel 자체가 하이퍼바이저가 되어 하드웨어를 직접 다루는데, 그 아래에 다른 OS가 없으므로 Type 1로 본다.

위에서 “OS 가 사이에 끼어있으면 Type 1 이라고 단정지어선 안된다” 고 말한 이유가 이것인데, OS 가 있다고 무조건 Type 2 인것은 아니다.

따라서 정확하게는, 하이퍼바이저가 하드웨어와 직접 소통하는지, OS 를 거치는지로 봐야한다.

Microsoft Hyper-V도 비슷하다. 평소에는 Windows 위에서 하이퍼바이저를 켜는 것처럼 보인다.

하지만 Hyper-V를 활성화하면 기존 Windows조차 하이퍼바이저 아래의 특별한 VM(부모 파티션)으로 내려간다. 즉 켜는 순간 구조가 Type 1로 재편된다.

그래서 “이 제품은 Type 몇이냐”를 외우는 것보다, “하이퍼바이저 아래에 범용 OS가 깔려 있느냐”를 따지는 편이 낫다.

References