밀리초 기동의 원리 — 하드웨어 가상화와 KVM
Vantisso는 에이전트 작업마다 Firecracker MicroVM을 띄우고 작업이 끝나면 디스크·네트워크까지 통째로 회수한다. 스냅샷 콜드 복원 200ms, 웜 풀 사용 시 2ms라는 기동 시간은 마법이 아니다. 현대 CPU에 내장된 하드웨어 가상화 기능과 그것을 리눅스에서 사용할 수 있게 해 주는 KVM이 있기에 가능하다. 이 글에서는 Vantisso 실행의 근간이 되는 인프라 소프트웨어 기술에 대하여 알아본다.
가상화의 근본 문제: 게스트를 어떻게 안전하게 실행할 것인가
가상 머신의 본질은 하나의 물리 머신 위에서 여러 운영체제를 서로 격리한 채 동시에 실행하는 것이다. 그런데 여기에는 근본적인 어려움이 있다.
운영체제는 원래 하드웨어를 독점적으로 제어하도록 설계되었다. 커널은 CPU의 가장 높은 특권 수준에서 실행되며 메모리 관리, 인터럽트 처리, 입출력 장치 제어 같은 민감한 작업을 직접 수행한다. CPU는 이 특권 수준을 링(Ring) 이라는 개념으로 구분하는데 전통적인 x86 아키텍처에는 링 0(가장 높은 특권)부터 링 3(가장 낮은 특권)까지 네 단계가 있고 커널은 링 0에서, 일반 응용 프로그램은 링 3에서 실행된다.
문제는 가상 머신 안의 게스트 OS이다. 이 게스트 OS도 자신이 하드웨어를 직접 제어한다고 믿고 링 0의 특권 명령을 실행하려 한다. 그러나 게스트 OS가 실제 하드웨어를 마음대로 제어하게 두면 여러 게스트 OS가 동시에 특정 하드웨어 자원을 사용하기 위해 충돌하고 이로 인해 격리 구조는 유지하기 어렵게 된다. 이를 중재하려면 하이퍼바이저가 하드웨어를 통제하는 구심점 역할을 해야 한다. 그렇다면 게스트 OS가 자신이 하드웨어를 제어한다고 믿으면서도 실제로는 하이퍼바이저의 통제 아래 안전하게 격리되도록 하려면 어떻게 해야 할까?
소프트웨어 가상화의 한계
하드웨어 가상화가 등장하기 전에는 이 문제를 소프트웨어로 해결했다.
- 이진 변환(Binary Translation) — 하이퍼바이저가 게스트 OS의 명령을 실행 전에 검사하여 위험한 특권 명령을 안전한 명령으로 바꿔치기한다. 동작 자체에는 문제가 없지만 게스트 OS의 모든 명령 흐름을 검사하고 변환해야 하므로 실행 성능이 느려질 수밖에 없다.
- 반가상화(Paravirtualization) — 게스트 OS를 가상화에 맞게 수정하는 방식이다. 성능은 개선되지만 게스트 OS의 소스 코드를 가상화 지원에 맞게 고쳐야 한다는 제약이 따른다.
Vantisso가 추구하는 밀리초 단위 기동과 수십 개 VM의 동시 실행은 이런 소프트웨어 방식으로는 성능을 보장하기 어렵다. 하드웨어 가상화가 사실상 유일한 해답인 이유이다.
하드웨어 가상화: 루트 모드와 비루트 모드
현대의 CPU에는 가상화를 위한 전용 기능이 내장되어 있다. 인텔의 VT-x와 AMD의 AMD-V가 그것이다. 기존 x86 명령어 집합에 가상화 전용 명령과 실행 모드를 더한 것이라 흔히 가상화 확장(Extension) 이라고 부른다. 리눅스에서는 CPU 플래그로 그 존재를 확인할 수 있는데 인텔은 vmx, AMD는 svm으로 표시된다.
가상화 확장을 지원하는 아키텍처에서는 기존의 링 0~3 구조와는 별개로 CPU를 다음 두 가지 모드로 동작시킨다.
- 루트 모드(root mode) — 하이퍼바이저가 실행되는 모드. 하드웨어를 실제로 통제한다.
- 비루트 모드(non-root mode) — 게스트가 실행되는 모드. 게스트 OS가 이 안에서 동작한다.
여기서 주목할 점은 모드 구분(루트/비루트)과 특권 구분(링 0~3)이 서로 간섭하지 않고 동시에 존재한다는 것이다. 그 결과 비루트 모드 안에도 링 0~3이 그대로 살아 있고, 덕분에 게스트 OS의 커널은 비루트 모드의 링 0에서 어떠한 변경도 없이 원래 의도한 그대로 실행될 수 있다. 게스트 OS는 자신이 링 0에서 하드웨어를 제어한다고 믿지만 실제로는 하이퍼바이저의 통제권인 비루트 모드 안에서 동작하는 것이다. 소프트웨어 가상화가 이진 변환으로 힘겹게 지원하던 일을 CPU가 대신 맡으면서 운영 복잡도는 획기적으로 낮아지고 성능은 거의 베어메탈 수준으로 유지된다.
위 도식처럼 루트 모드 안에도 링 0과 링 3이 모두 존재한다. 호스트 커널과 KVM 모듈은 루트 모드의 링 0에서 하드웨어를 직접 통제하고, VMM 역할을 하는 Firecracker는 루트 모드의 링 3에서 실행되며 /dev/kvm을 이용해 링 0의 KVM에 요청을 보낸다.
통제권의 전환: VM Exit과 VM Entry
게스트 OS가 비루트 모드에서 내부 연산만 하다가 하드웨어를 실제로 건드려야 하는 민감한 작업(입출력, 인터럽트 처리, 특정 특권 명령 등)을 시도하면 어떻게 될까? 이때 CPU는 자동으로 게스트 OS 실행을 멈추고 하이퍼바이저에게 통제권을 넘긴다. 비루트 모드에서 루트 모드로 넘어가는 이 전환이 VM Exit이다. 하이퍼바이저가 해당 이벤트를 대신 처리하고 나면 VM Entry로 게스트 OS에 통제권을 돌려주고 게스트 OS는 멈췄던 지점에서 실행을 이어간다.
이 VM Exit/Entry 메커니즘이 하드웨어 가상화의 가장 중요한 부분이다. 일반 연산을 할 때는 게스트가 직접 실행하고 입출력·인터럽트 처리·특권 명령처럼 개입이 필요한 순간에만 하이퍼바이저가 끼어든다. 소프트웨어 가상화처럼 모든 명령을 검사할 필요가 없으므로 게스트 OS 위에서 도는 애플리케이션은 베어메탈에 가까운 속도를 낸다.
이때 게스트 OS의 상태(레지스터, 제어 정보 등)는 CPU가 관리하는 전용 제어 구조에 저장되며 인텔은 이를 VMCS(VM Control Structure), AMD는 **VMCB(VM Control Block)**이라고 부른다. VM Exit과 VM Entry가 일어날 때마다 CPU는 이 구조를 통해 게스트 상태를 저장하고 복원한다. 스냅샷이 게스트의 전체 상태를 디스크에 저장하는 것과 비교해 보면 훨씬 더 낮은 레벨에서 CPU가 매 전환마다 게스트 상태를 다루고 있는 셈이다.
KVM: 리눅스를 하이퍼바이저로 만드는 기술
하드웨어 가상화 기능은 현대 대부분의 CPU가 지원하지만 이를 운영체제에서 사용하려면 그 기능을 다루는 시스템 소프트웨어가 필요하다. 리눅스에서 그 역할을 하는 것이 KVM(Kernel-based Virtual Machine) 이다. KVM은 리눅스 커널에 내장된 모듈로서 CPU의 하드웨어 가상화 기능(VT-x/AMD-V)을 추상화하여 사용자 공간의 프로그램이 가상 머신을 만들고 실행할 수 있는 인터페이스를 제공한다.
KVM이 로드되면 리눅스 커널 자체가 하이퍼바이저가 된다. 별도의 하이퍼바이저 운영체제를 만드는 대신 이미 검증된 리눅스 커널의 스케줄러, 메모리 관리, 장치 드라이버를 그대로 쓰면서 가상화 기능만 KVM 모듈로 얹는 구조이다. 이때 각 가상 머신은 리눅스 입장에서 하나의 일반 프로세스처럼 취급되며 리눅스 스케줄러가 다른 프로세스와 똑같이 VM을 스케줄링한다.
User Space
/dev/kvm 인터페이스 호출 (ioctl)Kernel Space
도식에서 보듯이 Firecracker는 이 **KVM 위에서 동작하는 사용자 영역 프로그램(VMM, Virtual Machine Monitor)**이다. 직접 하드웨어를 다루지 않고 KVM이 제공하는 인터페이스를 통해 가상 머신의 생성과 실행을 요청한다. 즉 하드웨어 가상화 기능의 실제 제어는 KVM이 담당하고 가상 머신의 구성과 가상 장치 에뮬레이션은 Firecracker가 담당하는 식으로 역할이 나뉜다.
/dev/kvm: 실제로 오가는 것은 ioctl
KVM이 제공하는 인터페이스는 /dev/kvm이라는 특수 장치 파일로 표현되며 모든 사용자 영역 프로그램은 이 장치 파일을 통해 KVM과 통신한다. Firecracker가 KVM에게 "가상 머신을 만들어라"라고 요청할 때 실제로 수행되는 일은 /dev/kvm 장치 파일을 열고 특정 명령을 전달하는 리눅스의 표준 메커니즘인 ioctl 시스템 콜을 호출하는 것이다. Firecracker는 다음과 같은 ioctl 명령들을 순서대로 KVM에 보낸다.
KVM_CREATE_VM // 가상 머신 생성
KVM_CREATE_VCPU // 가상 CPU 생성
KVM_SET_USER_MEMORY_REGION // 게스트 OS 메모리 영역 할당
KVM_RUN // 게스트 OS 실행 시작 (이후 VM Exit마다 제어 반환)KVM_RUN을 호출하면 게스트 OS가 비루트 모드에서 실행되기 시작하고 VM Exit이 발생할 때마다 이 호출에서 제어가 Firecracker로 돌아온다. Firecracker는 VM Exit을 유발한 이벤트를 처리한 뒤 다시 KVM_RUN을 호출하여 게스트 OS가 중단된 지점부터 실행을 이어가게 한다. 즉 vCPU 스레드는 바로 이 KVM_RUN 루프를 도는 것이다. Vantisso 데몬이 런타임에 root 권한으로 실행되는 이유도 여기에 있다. /dev/kvm으로 인터페이스하는 하드웨어 가상화 장치에 접근하려면 그에 맞는 권한이 필요하기 때문이다.
Vantisso 실행을 위한 세 가지 조건
정리하면 Vantisso가 하드웨어 가상화 기능을 활용하기 위한 조건은 세 가지로 요약된다.
- CPU가 하드웨어 가상화 기능을 지원할 것 — 인텔 VT-x 또는 AMD-V
- BIOS/UEFI 등에서 해당 기능이 활성화되어 있을 것
- Vantisso가
/dev/kvm에 접근할 권한을 가질 것
설치 전 호스트에서 다음 두 줄로 바로 확인할 수 있다.
# CPU 가상화 확장 지원 여부 (인텔: vmx, AMD: svm)
grep -Eo 'vmx|svm' /proc/cpuinfo | sort -u
# KVM 장치 파일의 존재와 권한 확인
ls -l /dev/kvm이 세 조건이 충족되면 Vantisso는 하드웨어 가상화 기술을 이용해 MicroVM을 밀리초 단위로 기동한다.