QEMU와 KVM으로 리눅스 가상 머신 구축하기 — 설치부터 성능 튜닝까지
이 글의 핵심
QEMU는 소프트웨어 에뮬레이터이고 KVM은 리눅스 커널의 하이퍼바이저 모듈로, 두 기술이 결합하면 하드웨어 가속을 활용하는 풀스피드 가상 머신을 만들 수 있습니다. 이 글은 개념 정리부터 설치, VM 생성, libvirt 관리, 네트워킹, 성능 튜닝, 트러블슈팅까지 실무에 필요한 흐름을 순서대로 다룹니다.
들어가며
리눅스 서버를 운영하다 보면 “컨테이너로는 해결이 안 되는” 상황을 반드시 만나게 됩니다. 커널 모듈을 직접 테스트해야 하거나, 호스트와 다른 커널 버전을 써야 하거나, 윈도우 게스트를 함께 띄워야 하는 경우가 그렇습니다. 이럴 때 등장하는 조합이 QEMU(Quick Emulator)와 KVM(Kernel-based Virtual Machine)입니다. 두 이름이 항상 붙어 다니다 보니 하나의 소프트웨어로 오해하기 쉽지만, 실제로는 역할이 명확히 나뉜 두 기술이 협력하는 구조입니다.
이 글은 QEMU와 KVM의 관계를 정확히 짚고, 설치부터 VM 생성, libvirt를 이용한 관리, 네트워킹 모드 선택, 성능 튜닝, 흔히 겪는 트러블슈팅까지 실무에서 바로 쓸 수 있는 흐름으로 정리합니다. Docker 같은 컨테이너 기술과 어떻게 다르고 언제 VM이 필요한지도 함께 다루므로, “가상화가 왜 필요한가”부터 “실제로 VM을 띄우고 운영하는 방법”까지 한 번에 가져갈 수 있도록 구성했습니다.
QEMU와 KVM의 관계
QEMU: 하드웨어를 흉내 내는 에뮬레이터
QEMU는 CPU, 메모리 컨트롤러, 디스크 컨트롤러, 네트워크 카드, 그래픽 카드 등 컴퓨터를 구성하는 하드웨어를 소프트웨어로 재현하는 에뮬레이터입니다. QEMU 단독으로도 동작하며, 이 모드를 흔히 TCG(Tiny Code Generator) 모드라고 부릅니다. TCG 모드에서는 게스트의 CPU 명령어 하나하나를 호스트가 이해할 수 있는 명령어로 번역(binary translation)해서 실행하기 때문에, x86 호스트에서 ARM 게스트를 돌리는 것처럼 아키텍처가 다른 시스템도 에뮬레이션할 수 있다는 강력한 장점이 있습니다. 다만 이 번역 과정 자체가 오버헤드이기 때문에 순수 QEMU만으로는 네이티브 실행 속도의 몇 분의 1 수준으로 느려집니다.
KVM: 커널에 내장된 하이퍼바이저 모듈
KVM은 리눅스 커널에 포함된 모듈로, Intel VT-x나 AMD-V 같은 CPU의 하드웨어 가상화 확장 기능을 직접 활용합니다. CPU가 이미 가상화를 위한 특권 모드(게스트 모드)를 제공하기 때문에, 게스트의 대부분 명령어는 번역 과정 없이 CPU에서 곧바로, 네이티브 속도로 실행됩니다. KVM은 /dev/kvm이라는 디바이스 파일을 통해 사용자 공간 프로그램에 가상화 기능을 노출하는데, 이 인터페이스만으로는 완전한 컴퓨터가 되지 않습니다. CPU 실행을 가속할 뿐, 디스크나 네트워크 카드 같은 나머지 하드웨어는 여전히 누군가 흉내 내야 하기 때문입니다.
두 기술이 만나는 지점
여기서 QEMU와 KVM이 결합합니다. QEMU가 디스크, 네트워크, 그래픽 등 주변 장치 전체를 에뮬레이션하면서, CPU 명령어 실행만큼은 직접 처리하지 않고 KVM에 위임합니다. qemu-system-x86_64 명령에 -enable-kvm 플래그를 주면 QEMU는 /dev/kvm을 열어 CPU 실행을 커널의 KVM 모듈에 넘기고, 자신은 I/O 에뮬레이션과 VM 전체의 라이프사이클 관리에 집중합니다. 결과적으로 “CPU는 하드웨어 속도로, 나머지 장치는 유연하게 소프트웨어로”라는 조합이 완성되어, 순수 에뮬레이션보다 훨씬 빠르면서도 완전한 컴퓨터를 구성할 수 있게 됩니다.
정리하면 QEMU는 컴퓨터 전체를 구성하는 프레임이고, KVM은 그 프레임 안에서 CPU 실행만 가속해 주는 엔진입니다. QEMU 없이 KVM만으로는 VM을 띄울 수 없고, KVM 없이 QEMU만으로는 속도가 느립니다. 둘을 함께 쓸 때 비로소 프로덕션에서 쓸 만한 가상 머신이 완성됩니다.
하이퍼바이저 유형과 다른 가상화 도구와의 비교
Type 1과 Type 2 하이퍼바이저
하이퍼바이저는 흔히 두 유형으로 나눕니다. Type 1(베어메탈) 하이퍼바이저는 운영체제 없이 하드웨어 위에 직접 설치되어 동작합니다. VMware ESXi, Xen이 대표적입니다. Type 2(호스티드) 하이퍼바이저는 일반 운영체제 위에서 하나의 애플리케이션처럼 실행됩니다. VirtualBox, VMware Workstation이 여기에 속합니다.
KVM은 이 분류를 애매하게 만드는 존재입니다. 형식적으로는 리눅스 커널 모듈이므로 “리눅스라는 OS 위에서 동작”하는 것처럼 보여 Type 2로 분류되기도 하지만, 실질적으로는 커널 자체가 하이퍼바이저의 일부가 되어 하드웨어에 직접 접근하기 때문에 Type 1에 가깝다고 설명하는 자료도 많습니다. 이 구분에 지나치게 매달리기보다는 “리눅스 커널이 곧 하이퍼바이저 역할도 겸한다”는 구조적 특징을 이해하는 편이 실용적입니다.
VMware·VirtualBox와의 간단한 비교
VirtualBox는 GUI가 직관적이고 설치가 간단해 데스크톱에서 가볍게 VM 하나를 띄워볼 때 접근성이 좋습니다. VMware Workstation/ESXi는 상용 제품답게 스냅샷, 클러스터링, 관리 도구가 잘 갖춰져 있고 엔터프라이즈 지원을 받을 수 있습니다. 반면 QEMU/KVM은 리눅스 커널에 기본 내장되어 있어 별도 커널 모듈 설치가 필요 없고, 오픈소스이므로 라이선스 비용 없이 대규모로 확장할 수 있으며, libvirt를 매개로 OpenStack·Proxmox VE·oVirt 같은 오케스트레이션 스택과 표준화된 방식으로 통합됩니다. 어느 쪽이 절대적으로 우월하다기보다, 데스크톱 개인 용도라면 VirtualBox, 리눅스 서버·클라우드 인프라 구축이라면 QEMU/KVM 쪽이 자연스러운 선택지가 되는 경우가 많습니다.
설치 및 환경 준비
CPU 가상화 지원 확인
VM을 만들기 전에 CPU가 하드웨어 가상화를 지원하는지부터 확인합니다.
# Intel은 vmx, AMD는 svm 플래그를 찾습니다. 0보다 크면 지원됩니다.
egrep -c '(vmx|svm)' /proc/cpuinfo
결과가 0이면 BIOS/UEFI에서 가상화 기능(Intel VT-x 또는 AMD-V)이 비활성화되어 있거나, 클라우드 인스턴스처럼 이미 가상화된 환경 위에서 중첩 가상화가 노출되지 않은 상태일 수 있습니다. 물리 서버라면 BIOS 설정에서 해당 옵션을 켜고 재부팅해야 합니다.
패키지 설치
Ubuntu/Debian 계열에서는 다음 패키지 묶음으로 QEMU, KVM, libvirt, GUI 관리 도구를 한 번에 설치할 수 있습니다.
sudo apt update
sudo apt install -y qemu-kvm libvirt-daemon-system libvirt-clients \
bridge-utils virtinst virt-manager cpu-checker
qemu-kvm은 QEMU 바이너리와 KVM 가속을 함께 쓸 수 있게 하는 메타패키지이고, libvirt-daemon-system은 VM 라이프사이클을 관리하는 libvirtd 데몬을, virtinst는 virt-install 같은 VM 생성 CLI 도구를, virt-manager는 GUI 관리 도구를 제공합니다. cpu-checker 패키지에는 아래에서 쓸 kvm-ok가 포함되어 있습니다.
Fedora/RHEL 계열에서는 다음과 같이 설치합니다.
sudo dnf install -y qemu-kvm libvirt virt-install virt-manager
설치 후 KVM이 실제로 활성화되어 있는지 확인합니다.
kvm-ok
# 또는
ls -l /dev/kvm
kvm-ok가 “KVM acceleration can be used”라고 출력하면 정상입니다. /dev/kvm 디바이스 파일이 존재하지 않는다면 커널 모듈(kvm_intel 또는 kvm_amd)이 로드되지 않은 것이므로 lsmod | grep kvm으로 모듈 상태를 확인합니다.
/dev/kvm 권한 설정
/dev/kvm은 보통 kvm 그룹 소유로 생성됩니다. 일반 사용자 계정으로 sudo 없이 VM을 다루려면 해당 계정을 kvm 그룹과 libvirt 그룹에 추가해야 합니다.
sudo usermod -aG kvm,libvirt $USER
# 그룹 변경을 적용하려면 로그아웃 후 재로그인하거나 newgrp 사용
newgrp kvm
이 설정을 빠뜨리면 qemu-system-x86_64 -enable-kvm 실행 시 “Could not access KVM kernel module: Permission denied” 같은 오류를 만나게 됩니다. 그룹 추가 후에도 세션을 새로 시작하지 않으면 반영되지 않는다는 점을 기억해 두면 불필요한 삽질을 줄일 수 있습니다.
libvirtd 서비스 확인
sudo systemctl enable --now libvirtd
sudo systemctl status libvirtd
libvirtd가 실행 중이어야 virsh, virt-manager 같은 관리 도구가 VM을 제어할 수 있습니다.
VM 생성 및 실행 실전
디스크 이미지 만들기
qemu-img 명령으로 VM이 사용할 가상 디스크 이미지를 먼저 만듭니다.
qemu-img create -f qcow2 ubuntu-vm.qcow2 20G
-f qcow2는 이미지 포맷을 QCOW2(QEMU Copy-On-Write v2)로 지정하는 옵션입니다. QCOW2는 씬 프로비저닝(thin provisioning)을 지원해서, 20GB로 선언했더라도 실제 데이터가 쓰인 만큼만 호스트 디스크 공간을 차지합니다. 스냅샷 기능도 내장하고 있어 VM 상태를 특정 시점으로 되돌리는 작업에도 유리합니다. 원시 포맷(raw)에 비해 약간의 오버헤드가 있지만, 유연성이 필요한 개발·테스트 환경에서는 QCOW2가 기본 선택지로 자리 잡았습니다.
qemu-system-x86_64로 VM 부팅하기
디스크와 설치 ISO를 준비했다면 다음 명령으로 VM을 부팅합니다.
qemu-system-x86_64 \
-enable-kvm \
-cpu host \
-smp 4 \
-m 4096 \
-drive file=ubuntu-vm.qcow2,format=qcow2,if=virtio \
-cdrom ubuntu-22.04-live-server-amd64.iso \
-boot d \
-netdev user,id=net0 \
-device virtio-net-pci,netdev=net0 \
-vnc :1
각 플래그가 하는 역할을 짚어보면 다음과 같습니다.
-enable-kvm: QEMU가 CPU 실행을 KVM에 위임하도록 지시합니다. 이 플래그가 없으면 순수 TCG 에뮬레이션으로 동작해 설치만으로도 체감될 만큼 느려집니다.-cpu host: 게스트에게 호스트 CPU의 모델과 명령어 집합(AVX, SSE4 등)을 그대로 노출합니다. 게스트가 호스트와 동일한 CPU 기능을 활용할 수 있어 성능상 유리하지만, 다른 CPU로 마이그레이션할 계획이 있다면 호환성 있는 CPU 모델(-cpu qemu64등)을 명시하는 편이 안전합니다.-smp 4: 게스트에 4개의 vCPU를 할당합니다.-m 4096: 게스트 메모리를 4096MiB(4GB)로 할당합니다.-drive file=...,if=virtio: 디스크를 virtio 인터페이스로 연결합니다. IDE 에뮬레이션보다 훨씬 빠른 반가상화(paravirtualized) I/O 경로를 사용하게 됩니다.-cdrom,-boot d: 설치 ISO를 CD-ROM으로 마운트하고 CD-ROM에서 먼저 부팅하도록 지정합니다. 설치가 끝난 뒤에는 이 옵션을 빼고 디스크에서 바로 부팅하면 됩니다.-netdev user,id=net0/-device virtio-net-pci,netdev=net0: 사용자 모드(NAT) 네트워킹으로 네트워크 장치를 구성합니다. 자세한 네트워킹 모드는 아래에서 별도로 다룹니다.-vnc :1: 그래픽 콘솔을 VNC로 노출합니다.vncviewer localhost:1처럼 VNC 클라이언트로 접속해 설치 화면을 볼 수 있습니다.
헤드리스 서버 환경이라 그래픽 콘솔이 필요 없다면 -nographic 옵션으로 시리얼 콘솔만 터미널에 직접 출력하도록 구성할 수도 있습니다. 이 경우 게스트 OS의 커널 파라미터에 console=ttyS0 같은 시리얼 콘솔 설정이 되어 있어야 부팅 로그를 확인할 수 있습니다.
설치 후 재부팅
설치가 끝나면 CD-ROM 옵션을 제거하고 디스크에서 바로 부팅합니다.
qemu-system-x86_64 \
-enable-kvm \
-cpu host \
-smp 4 \
-m 4096 \
-drive file=ubuntu-vm.qcow2,format=qcow2,if=virtio \
-netdev user,id=net0 \
-device virtio-net-pci,netdev=net0 \
-vnc :1
매번 이런 긴 명령을 손으로 입력하는 것은 비효율적이므로, 실무에서는 다음 절에서 다루는 libvirt로 VM 정의를 XML로 저장해 재사용하는 방식을 훨씬 더 많이 씁니다.
libvirt와 virsh로 VM 관리하기
virt-install로 VM 정의하기
virt-install은 QEMU 명령을 직접 조립하는 대신, libvirt가 이해하는 VM 정의(도메인)를 생성해 주는 도구입니다.
virt-install \
--name ubuntu-vm \
--ram 4096 \
--vcpus 4 \
--disk path=/var/lib/libvirt/images/ubuntu-vm.qcow2,size=20,format=qcow2 \
--os-variant ubuntu22.04 \
--network network=default \
--graphics vnc \
--cdrom /path/to/ubuntu-22.04-live-server-amd64.iso
이렇게 생성한 VM은 libvirtd가 관리하는 도메인(domain)으로 등록되며, 호스트가 재부팅되어도 정의가 유지되고 필요할 때 다시 시작할 수 있습니다.
virsh 기본 명령
virsh는 libvirt가 관리하는 VM을 다루는 CLI입니다.
# 실행 중인 VM 목록
virsh list
# 정지된 VM까지 포함한 전체 목록
virsh list --all
# VM 시작 / 정상 종료 / 강제 종료
virsh start ubuntu-vm
virsh shutdown ubuntu-vm
virsh destroy ubuntu-vm
# VM 자동 시작 등록(호스트 부팅 시 함께 기동)
virsh autostart ubuntu-vm
# VM 콘솔 접속
virsh console ubuntu-vm
shutdown은 게스트 OS에 ACPI 종료 신호를 보내는 정상 종료이고, destroy는 이름과 달리 VM을 삭제하는 것이 아니라 전원을 강제로 끄는 동작입니다(물리 서버의 전원 버튼 길게 누르기에 해당). 실제로 VM 정의 자체를 지우려면 별도로 virsh undefine을 사용해야 합니다.
스냅샷으로 상태 저장하기
# 스냅샷 생성
virsh snapshot-create-as ubuntu-vm snapshot1 "설치 직후 상태"
# 스냅샷 목록
virsh snapshot-list ubuntu-vm
# 특정 스냅샷으로 복원
virsh snapshot-revert ubuntu-vm snapshot1
스냅샷은 커널 모듈 테스트, 배포 전 상태 백업, 위험한 설정 변경 전 체크포인트로 요긴하게 쓰입니다. QCOW2 포맷의 카피-온-라이트 구조 덕분에 스냅샷 생성 자체는 매우 빠르지만, 스냅샷을 여러 개 쌓아두면 체인이 길어져 디스크 I/O 성능이 저하될 수 있으므로 주기적으로 정리하는 습관이 필요합니다.
네트워킹 모드 비교
VM의 네트워크 구성은 용도에 따라 크게 세 가지 모드 중 하나를 선택하게 됩니다.
사용자 모드(NAT)
-netdev user로 지정하는 방식으로, QEMU 프로세스 자체가 간이 NAT 라우터 겸 DHCP 서버 역할을 합니다. 설정이 거의 필요 없고 root 권한도 요구하지 않아 개발용 VM을 빠르게 띄울 때 편리하지만, 호스트 바깥에서 VM으로 직접 접속하기 어렵고(포트 포워딩 설정이 필요) 성능도 다른 모드보다 떨어집니다. “일단 인터넷만 되면 된다”는 요구에 적합합니다.
브리지(bridge) 모드
호스트의 물리 네트워크 인터페이스에 가상 브리지를 만들고 VM의 가상 NIC를 그 브리지에 연결하는 방식입니다. VM이 물리 네트워크상에서 별도의 IP를 할당받아 마치 독립된 물리 머신처럼 동작하므로, 같은 네트워크의 다른 장비에서 VM에 직접 접속해야 하는 서버 용도에 적합합니다. libvirt의 기본 NAT 네트워크(network=default)를 쓰지 않고 브리지를 직접 구성하려면 netplan이나 /etc/network/interfaces에서 브리지 인터페이스를 정의하고, libvirt 네트워크 XML에서 <forward mode="bridge"/>와 함께 해당 브리지를 지정합니다.
macvtap
호스트의 물리 NIC에 VM의 가상 NIC를 직접 바인딩하되, 브리지 인터페이스를 별도로 만들지 않는 방식입니다. 브리지보다 설정이 간단하고 오버헤드도 조금 낮지만, 기본 설정(VEPA 모드)에서는 호스트와 게스트 사이의 통신이 되지 않는다는 특유의 제약이 있어 처음 접하면 당황하기 쉽습니다. 이 제약이 문제가 된다면 bridge 모드의 macvtap(private이 아닌 bridge submode)을 사용하거나, 애초에 일반 리눅스 브리지 방식을 선택하는 편이 무난합니다.
세 모드 중 무엇을 고를지 판단하기 어렵다면, 개발·테스트 환경에서는 NAT로 시작해서 필요할 때 포트 포워딩을 추가하고, VM을 네트워크상의 독립된 서버처럼 운영해야 한다면 브리지를 선택하는 것이 실무에서 가장 흔한 선택 기준입니다.
성능 튜닝 포인트
virtio 드라이버 사용
디스크(if=virtio 또는 virtio-blk), 네트워크(virtio-net-pci), 발룬 메모리(virtio-balloon) 등 가능한 모든 장치를 IDE나 e1000 같은 완전 에뮬레이션 장치 대신 virtio 반가상화 드라이버로 구성하는 것이 성능 튜닝의 첫걸음입니다. virtio는 게스트와 호스트가 가상화되어 있다는 사실을 서로 알고 있다는 전제 위에서 설계된 인터페이스이기 때문에, 실제 하드웨어를 흉내 내는 오버헤드 없이 훨씬 빠른 I/O 경로를 제공합니다. 최신 리눅스 배포판은 대부분 virtio 드라이버를 기본 커널에 포함하고 있어 별도 드라이버 설치 없이 바로 사용할 수 있습니다.
hugepages
기본적으로 리눅스는 4KB 단위의 페이지로 메모리를 관리하지만, VM처럼 큰 메모리 블록을 다루는 워크로드에서는 이 단위가 TLB(Translation Lookaside Buffer) 미스를 늘려 성능을 떨어뜨릴 수 있습니다. Hugepages는 2MB 또는 1GB 단위의 큰 페이지를 사용해 페이지 테이블 엔트리 수를 줄이고 TLB 효율을 높입니다.
# 부팅 시 2MB hugepage 1024개(총 2GB) 예약
echo 1024 | sudo tee /proc/sys/vm/nr_hugepages
libvirt VM 정의 XML에 <memoryBacking><hugepages/></memoryBacking>을 추가하면 해당 VM이 hugepage 풀에서 메모리를 할당받게 됩니다. 메모리 집약적인 데이터베이스나 대규모 애플리케이션을 게스트에서 돌린다면 체감 가능한 성능 향상을 기대할 수 있습니다.
CPU 피닝(CPU pinning)
기본적으로 vCPU 스레드는 호스트의 여러 물리 코어를 오가며 스케줄링됩니다. 이 과정에서 CPU 캐시가 매번 새로 채워지는 비용이 발생할 수 있습니다. CPU 피닝은 특정 vCPU를 특정 물리 코어에 고정해 캐시 지역성을 높이고 스케줄링 지터를 줄이는 기법입니다.
virsh vcpupin ubuntu-vm 0 2
virsh vcpupin ubuntu-vm 1 3
위 명령은 게스트의 vCPU 0번을 호스트 물리 코어 2번에, vCPU 1번을 3번 코어에 고정합니다. 지연 시간에 민감한 워크로드(실시간 처리, 고빈도 거래 시스템 등)에서는 CPU 피닝과 더불어 호스트 운영체제 프로세스가 사용하지 않도록 코어를 격리(isolcpus 커널 파라미터)하는 구성까지 함께 고려하는 경우가 많습니다. 다만 일반적인 웹 서버나 개발 환경에서는 이 정도까지 세밀한 튜닝이 필요하지 않은 경우가 대부분이므로, 실제로 지연 시간이 문제가 되는 상황에서만 도입하는 것이 합리적입니다.
디스크 캐시 모드
-drive 옵션의 cache 파라미터도 성능에 영향을 줍니다. cache=none은 호스트 페이지 캐시를 우회해 데이터 정합성이 중요한 프로덕션 환경에 적합하고, cache=writeback은 호스트 캐시를 적극 활용해 처리량은 높지만 호스트가 비정상 종료될 경우 데이터 손실 위험이 커집니다. 상황에 맞는 트레이드오프를 이해하고 선택하는 것이 중요합니다.
컨테이너(Docker)와 VM, 언제 무엇을 써야 하는가
Docker 완벽 가이드와 Linux namespace·cgroups에서 다뤘듯, 컨테이너는 호스트 커널을 공유하면서 namespace와 cgroups로 프로세스를 논리적으로 격리하는 기술입니다. 커널을 새로 부팅하지 않기 때문에 시작이 수 초 이내로 빠르고 이미지도 가볍지만, 근본적으로 같은 커널 위에서 동작한다는 한계가 있습니다.
VM은 이와 반대로 CPU, 메모리, 디스크, 네트워크까지 하드웨어 수준에서 에뮬레이션하고 그 위에 완전히 독립된 커널을 부팅합니다. 그래서 다음과 같은 상황에서는 컨테이너로 대체할 수 없고 VM이 꼭 필요합니다.
- 다른 커널 버전이나 다른 운영체제를 실행해야 하는 경우(예: 리눅스 호스트 위에서 윈도우 게스트, 또는 호스트와 다른 커널 버전의 리눅스)
- 커널 모듈 개발·디버깅처럼 커널 자체를 건드리는 작업 — 커널 패닉이 나도 호스트에는 영향이 없습니다
- 강한 신뢰 경계(trust boundary)가 필요한 멀티테넌트 환경 — 컨테이너 격리가 뚫리면 커널 취약점을 통해 호스트까지 영향을 받을 수 있지만, VM은 하이퍼바이저 계층이 추가로 격리막 역할을 합니다
- 레거시 시스템 재현이나 특정 하드웨어 구성을 흉내 내야 하는 테스트 환경
반대로 마이크로서비스 배포, 빠른 스케일 인/아웃, CI 파이프라인의 빌드 환경처럼 “같은 커널 위에서 애플리케이션만 격리하면 충분한” 경우라면 컨테이너 쪽이 훨씬 가볍고 효율적입니다. 실무에서는 이 둘을 대립 관계로 보기보다, VM 위에 컨테이너 오케스트레이션(쿠버네티스 노드)을 올리는 것처럼 계층적으로 함께 쓰는 구성이 오히려 일반적입니다.
흔한 트러블슈팅
KVM을 지원하지 않는 CPU 또는 비활성화된 상태
egrep -c '(vmx|svm)' /proc/cpuinfo가 0을 반환하면 물리 서버는 BIOS/UEFI에서 VT-x/AMD-V를 켜야 하고, 클라우드 인스턴스라면 애초에 중첩 가상화를 지원하는 인스턴스 타입인지 클라우드 제공자 문서를 확인해야 합니다. -enable-kvm 없이 QEMU를 실행하면 TCG로 폴백되어 동작 자체는 하지만 체감상 몇 배에서 몇십 배까지 느려질 수 있습니다.
권한 문제: Permission denied on /dev/kvm
앞서 설치 절에서 다룬 것처럼 사용자 계정이 kvm 그룹에 속해 있는지, 그리고 그룹 변경 후 세션을 새로 시작했는지 확인합니다. id $USER 명령으로 현재 세션에 적용된 그룹 목록을 확인하면 그룹 추가가 실제로 반영되었는지 바로 알 수 있습니다.
중첩 가상화가 동작하지 않는 경우
cat /sys/module/kvm_intel/parameters/nested
# Y가 아니라면 활성화 필요
Intel의 경우 /etc/modprobe.d/kvm.conf에 options kvm_intel nested=1을 추가하고 kvm_intel 모듈을 재로드해야 합니다. AMD는 kvm_amd 모듈에 동일한 방식으로 nested=1을 설정합니다. 클라우드 인스턴스 위에서는 이 값을 게스트가 직접 바꿀 수 없는 경우가 많으므로, 중첩 가상화가 필요한 CI 환경이라면 이를 명시적으로 지원하는 인스턴스 타입을 선택해야 합니다.
VM이 부팅되지 않거나 콘솔이 멈춘 경우
-boot d 옵션을 빼먹은 채 빈 디스크로 부팅을 시도하면 부트로더를 찾지 못해 멈춘 것처럼 보일 수 있습니다. -nographic로 실행했는데 화면이 아무것도 출력되지 않는다면, 게스트 커널 부팅 파라미터에 시리얼 콘솔(console=ttyS0,115200)이 설정되어 있는지 확인합니다. libvirt로 관리하는 VM이라면 virsh console <이름> 대신 virt-viewer 또는 virt-manager의 GUI 콘솔로 접속해 그래픽 부팅 화면 자체를 확인하는 편이 원인 파악에 더 유리한 경우도 많습니다.
네트워크가 붙지 않는 경우
NAT 모드에서 VM은 인터넷에는 나갈 수 있지만 호스트 바깥에서 VM으로 들어오는 접속은 기본적으로 차단됩니다. 외부에서 접속해야 한다면 -netdev user,id=net0,hostfwd=tcp::2222-:22처럼 포트 포워딩 규칙을 추가하거나, 애초에 브리지 모드로 전환하는 것이 근본적인 해결책입니다. libvirt 기본 네트워크(virbr0)를 쓰는 경우 virsh net-list --all과 virsh net-start default로 가상 네트워크 자체가 활성화되어 있는지 먼저 확인합니다.
마무리
QEMU와 KVM은 “QEMU가 컴퓨터 전체를 구성하고, KVM이 그중 CPU 실행만 하드웨어 속도로 가속한다”는 역할 분담을 이해하면 나머지 개념들이 자연스럽게 연결됩니다. 설치 단계에서는 /dev/kvm 권한과 kvm-ok 확인을, VM 생성 단계에서는 qemu-img와 qemu-system-x86_64의 주요 플래그를, 운영 단계에서는 virsh와 libvirt XML 정의를 손에 익혀두면 개발용 임시 VM부터 프로덕션에 가까운 인프라까지 같은 도구 체인으로 다룰 수 있습니다.
컨테이너가 빠르고 가볍다는 이유로 모든 워크로드를 컨테이너로 옮기려는 시도를 종종 보게 되지만, 커널 자체를 다루거나 강한 격리 경계가 필요한 상황에서는 VM이 여전히 대체 불가능한 선택지입니다. QEMU/KVM은 오픈소스이면서도 libvirt를 통해 OpenStack, Proxmox VE 같은 성숙한 오케스트레이션 생태계와 연결되어 있으므로, 한 번 익혀두면 개인 개발 환경부터 회사의 프라이빗 클라우드 인프라까지 폭넓게 활용할 수 있는 기술입니다.