virsh 명령을 손으로 하나씩 치면서 VM을 만들고 지우는 건 한두 대일 때는 괜찮지만, 스토리지 풀 준비·도메인 정의·스냅샷·정리까지 반복해야 하는 상황이라면 스크립트로 묶는 게 낫다. 이 글에서는 virt-install로 도메인 XML을 자동 생성하고, virsh로 정의·기동·스냅샷·해제까지 이어지는 흐름을 실제로 실행해 정리한다. 게스트 OS 설치 자체보다는 VM 생명주기 관리를 스크립트 하나로 묶는 자동화에 초점을 맞춘다.
스토리지 풀 준비
VM 디스크를 담을 스토리지 풀부터 정의한다. 로컬 디렉터리 기반 dir 타입이 가장 단순하다.
$ mkdir -p ~/vmpool
$ virsh pool-define-as demo-pool dir --target ~/vmpool
Pool demo-pool defined
$ virsh pool-start demo-pool
Pool demo-pool started
$ virsh pool-autostart demo-pool
Pool demo-pool marked as autostarted
$ virsh pool-list --all
Name State Autostart
---------------------------------
demo-pool active yes
볼륨 생성과 도메인 XML 자동 생성
디스크 볼륨을 만들고, virt-install --print-xml로 실제 정의 없이 도메인 XML만 뽑아낸다 — 이 옵션이 자동화의 핵심이다. 대화형 설치 마법사 없이, 파라미터만 바꿔가며 XML을 스크립트로 찍어낼 수 있다.
$ virsh vol-create-as demo-pool demo-vm.qcow2 1G --format qcow2
Vol demo-vm.qcow2 created
$ virt-install \
--name demo-vm --memory 512 --vcpus 1 \
--disk vol=demo-pool/demo-vm.qcow2 \
--os-variant generic --network none --graphics none \
--print-xml 1 > demo-vm.xml
$ head -8 demo-vm.xml
<domain type="qemu">
<name>demo-vm</name>
<uuid>fb08b1f2-1574-4de7-aafc-a56a9501c316</uuid>
<memory>524288</memory>
<currentMemory>524288</currentMemory>
<vcpu>1</vcpu>
<os>
<type arch="x86_64" machine="pc-i440fx-noble-v2">hvm</type>
--graphics none --network none은 이 실습을 최소 구성으로 유지하려는 선택이다. 실제 운영에서는 --network bridge=br0처럼 브리지를 지정하면 된다.
정의 → 기동 → 상태 확인
$ virsh define demo-vm.xml
Domain 'demo-vm' defined from demo-vm.xml
$ virsh list --all
Id Name State
--------------------------
- demo-vm shut off
$ virsh start demo-vm
Domain 'demo-vm' started
$ virsh list --all
Id Name State
-------------------------
1 demo-vm running
부팅 가능한 디스크 이미지가 없어도 QEMU 프로세스 자체는 정상적으로 running 상태가 된다 — 이 실습은 게스트 OS 설치 여부와 무관하게 도메인 생명주기가 자동화 스크립트로 관리 가능함을 보여주는 데 목적이 있다.
스냅샷
실행 중인 VM 상태 그대로 스냅샷을 뜰 수 있다. 위험한 변경 전에 자동으로 스냅샷을 찍어두는 배치 스크립트에 쓰기 좋다.
$ virsh snapshot-create-as demo-vm snap1 --description "before risky change"
Domain snapshot snap1 created
$ virsh snapshot-list demo-vm
Name Creation Time State
----------------------------------------------
snap1 2026-08-02 23:00:39 +0900 running
정리
--remove-all-storage를 붙이면 도메인 정의를 해제하면서 연결된 볼륨까지 함께 지운다.
$ virsh destroy demo-vm
Domain 'demo-vm' destroyed
$ virsh undefine demo-vm --remove-all-storage --snapshots-metadata
Domain 'demo-vm' has been undefined
Volume 'hda'(/home/noble/vmpool/demo-vm.qcow2) removed.
$ virsh list --all
Id Name State
--------------------
$ virsh vol-list demo-pool
Name Path
--------------
하나의 스크립트로 묶기
지금까지의 명령을 이름·메모리·vCPU·디스크 크기를 인자로 받는 스크립트 하나로 합치면, VM을 만들고 스냅샷을 찍고 지우는 과정 전체를 재현 가능하게 반복할 수 있다.
#!/usr/bin/env bash
set -euo pipefail
NAME=${1:-demo-vm}
MEM=${2:-512}
VCPUS=${3:-1}
DISK_SIZE=${4:-1G}
POOL=demo-pool
virsh pool-info "$POOL" >/dev/null 2>&1 || {
mkdir -p ~/vmpool
virsh pool-define-as "$POOL" dir --target ~/vmpool
virsh pool-start "$POOL"
virsh pool-autostart "$POOL"
}
virsh vol-create-as "$POOL" "${NAME}.qcow2" "$DISK_SIZE" --format qcow2
virt-install \
--name "$NAME" --memory "$MEM" --vcpus "$VCPUS" \
--disk "vol=${POOL}/${NAME}.qcow2" \
--os-variant generic --network none --graphics none \
--print-xml 1 > "${NAME}.xml"
virsh define "${NAME}.xml"
virsh start "$NAME"
virsh dominfo "$NAME"스토리지 풀이 이미 있으면 건너뛰도록 pool-info로 존재 여부부터 확인해, 여러 번 실행해도 안전하게(idempotent) 동작하도록 했다.
주의사항
- KVM 가속 여부 확인:
/dev/kvm이 없는 환경(중첩 가상화 미지원 등)에서는 QEMU가 소프트웨어 에뮬레이션(TCG)으로 동작해 실제 OS 부팅은 크게 느려진다.ls /dev/kvm으로 먼저 확인할 것. - dir 타입 풀의 한계: 로컬 디렉터리 기반 풀은 호스트 하나에 묶여 있다. 여러 호스트 간 마이그레이션이 필요하면 NFS나 Ceph 같은 공유 스토리지 풀을 써야 한다.
- undefine –remove-all-storage는 되돌릴 수 없다: 볼륨까지 함께 삭제되므로, 실제 데이터가 있는 VM에는 스냅샷이나 백업 없이 쓰지 않는다.
- 대규모 자동화라면 API 바인딩 고려: VM 수가 많아지고 조건 분기가 복잡해지면
virsh쉘 스크립트보다libvirt-python바인딩으로 에러 처리를 명시적으로 하는 편이 안정적이다.
마무리
virt-install --print-xml과 virsh만으로도 VM 생성부터 스냅샷, 정리까지 전 과정을 스크립트로 재현할 수 있다. 여기서는 로컬 dir 풀과 최소 구성으로 흐름만 다뤘지만, 같은 패턴에 브리지 네트워크와 실제 설치 미디어를 끼워 넣으면 그대로 운영 환경의 VM 프로비저닝 스크립트가 된다.