Files
pve-vm-ops-guide/GUIDE.ko.md
thug0bin bf11a3aeb4 Initial: PVE VM 원격 제어 가이드 + VirtIO 마이그레이션 함정 분석
- GUIDE.ko.md: VM 원격 제어 (화면 캡처, QGA, qm sendkey, vhd 임포트, DR 이전, 트러블슈팅)
- JOURNEY.md: SATA→VirtIO-SCSI 마이그레이션 실패 연대기 15단계
- 핵심 발견: vioscsi START_TYPE=BOOT_START(0) 승격 필수 (pnputil만으론 부족)
- screenshots: 부팅 검증 화면

검증 환경: PVE 8.1, Win10 Pro 19045, virtio-win 0.1.285
2026-05-19 18:11:26 +09:00

15 KiB

VM 원격 제어 가이드 (PVE + QEMU)

Claude Code 환경에서 Proxmox 위의 Windows/Linux VM을 화면 캡처 + 명령 실행 + GUI 조작까지 원격으로 다루는 방법.

작성일: 2026-05-18 / 검증 환경: Windows 10 Pro VM on Proxmox VE 8.1, virtio-scsi-single + iothread + discard

표기 규약

  • <PVE_HOST> <PVE_HOST_2>: Proxmox 노드 IP
  • <PVE_PASSWORD> <PVE_PASSWORD_2>: 각 노드 root 비밀번호 (또는 SSH key 사용 권장)
  • <SMB_HOST> <SMB_USER> <SMB_PASSWORD>: SMB/CIFS 파일 서버 접속 정보
  • <windows_user>: Windows 게스트 로그인 사용자
  • VMID 예시는 101, zvol 예시는 vm-101-disk-N — 본인 환경 값으로 치환

1. 인프라 개요

Proxmox 호스트

호스트 IP SSH 비고
pve5 <PVE_HOST> root / <PVE_PASSWORD> 메인 VM 호스트 (운영)
pve7 <PVE_HOST_2> root / <PVE_PASSWORD_2> 보조 / 백업 호스트

SMB 파일 서버

  • 주소: <SMB_HOST>
  • 인증: <SMB_USER> / <SMB_PASSWORD>
  • 주요 공유: vhd (VHD 디스크 이미지 보관)

접속 패턴

# pve5 SSH (모든 VM 작업은 여기서)
sshpass -p "$PVE_PASSWORD" ssh -o StrictHostKeyChecking=no root@<PVE_HOST> '<명령>'

# SMB 마운트 (pve5 또는 작업 VM에서)
mkdir -p /mnt/vhd-src
mount -t cifs //<SMB_HOST>/vhd /mnt/vhd-src \
  -o username=<SMB_USER>,password=$SMB_PASSWORD,ro,vers=3.0,iocharset=utf8,file_mode=0444,dir_mode=0555

2. VM 화면 캡처 (보기)

PVE 8.x에는 qm screendump 명령이 없음. QEMU monitor에 직접 명령 보내야 함.

한 줄 캡처 (pve5에서)

echo "screendump /tmp/vm101.ppm" | qm monitor 101

결과는 PPM(Netpbm raw bitmap) 형식. 일반 뷰어가 못 읽으므로 PNG로 변환 필요.

Claude 환경에서 보기까지 풀 흐름

# 1) pve5에서 screendump
sshpass -p "$PVE_PASSWORD" ssh root@<PVE_HOST> \
  'echo "screendump /tmp/vm101.ppm" | qm monitor 101'

# 2) PPM 가져오기
sshpass -p "$PVE_PASSWORD" scp root@<PVE_HOST>:/tmp/vm101.ppm /tmp/vm101.ppm

# 3) PNG 변환 (ffmpeg는 작업 VM에 설치돼있음, pve5에는 없음)
ffmpeg -y -i /tmp/vm101.ppm /tmp/vm101.png

# 4) Read 툴로 열기 → 멀티모달 모델이 이미지 인식

PVE API 방식 (대안)

pvesh get /nodes/pve5/qemu/101/screenshot > /tmp/vm101.png

이 엔드포인트는 PNG 직접 반환. 단 PVE 권한 토큰 설정에 따라 동작 여부 다름.


3. QGA (QEMU Guest Agent)로 명령 실행

전제 조건

  • VM config에 agent: 1
  • 게스트 OS에 qemu-ga 서비스 설치/실행 중
  • 확인: qm agent <vmid> ping (응답 없으면 = OK)

핵심 명령

# 상태 확인
qm agent 101 ping
qm agent 101 get-osinfo
qm agent 101 get-host-name
qm agent 101 get-users

# 명령 실행 (동기 / 결과 회수)
qm guest exec 101 --timeout 30 -- cmd.exe /c whoami
qm guest exec 101 --timeout 30 -- powershell.exe -NoProfile -Command "<script>"
qm guest exec 101 --timeout 30 -- C:\Path\To\program.exe arg1 arg2

# 비동기 (PID만 받고 나중에 결과)
PID=$(qm guest exec 101 --no-wait -- powershell.exe -Command "Start-Sleep 60" | jq -r .pid)
qm guest exec-status 101 $PID    # 진행 상태/exit code/stdout 회수

파일 송수신

# 게스트로 파일 쓰기 (base64 또는 raw content)
qm guest file-write 101 'C:\temp\script.ps1' --content "$(cat local.ps1)"

# 게스트에서 파일 읽기
qm guest file-read 101 'C:\temp\output.txt'

⚠️ 권한과 세션

  • QGA로 실행된 프로세스는 NT AUTHORITY\SYSTEM (Session 0, 비대화형 서비스 세션) 으로 동작
  • GUI를 띄워도 사용자 데스크톱에 안 보임 (Windows 보안상 분리)
  • 시스템 권한이라 거의 모든 작업 가능 (레지스트리, 서비스, 파일)
  • 단 사용자 프로필(%USERPROFILE%)이 SYSTEM 계정 기준임에 주의

4. qm sendkey로 GUI 조작

키보드 이벤트를 QEMU에 직접 주입. 현재 포커스 있는 곳(=로그인 사용자 데스크톱) 에 입력됨.

기본 사용

qm sendkey 101 <key>

주요 키 이름

이름
Enter ret 또는 kp_enter
Esc esc
윈도우 키 meta_l / meta_r
Ctrl+Alt+Del ctrl-alt-delete
대문자 A shift-a
화살표 up, down, left, right
특수기호 exclam(!), at(@), minus(-), slash(/), dot(.) 등

패턴: Win+R → 프로그램 실행

sshpass -p "$PVE_PASSWORD" ssh root@<PVE_HOST> '
qm sendkey 101 esc                # 떠있는 다이얼로그 닫기
sleep 0.3
qm sendkey 101 meta_l-r           # 실행창 열기
sleep 0.5
for c in n o t e p a d; do qm sendkey 101 $c; sleep 0.05; done
qm sendkey 101 ret
'

제약

  • 키보드 입력만 가능 (마우스 )
  • 한글/IME 입력 매우 어려움 — 영문 명령어/경로 위주로 설계
  • 화면 상태 모르고 보내면 의도와 다른 곳에 들어감 → screendump으로 확인 권장
  • 결과 회수 = screendump 또는 QGA로 프로세스 확인

5. ★ 최강 조합: QGA + schtasks로 사용자 세션에서 실행

QGA의 결과 회수성 + 사용자 세션에서의 GUI 가시성을 모두 얻는 방법.

qm guest exec 101 --timeout 30 -- powershell.exe -NoProfile -Command "
  schtasks /create /tn RunInUser /tr 'C:\path\program.exe arg' /sc once /st 23:59 /it /ru '<windows_user>' /f
  schtasks /run /tn RunInUser
  Start-Sleep 1
  schtasks /delete /tn RunInUser /f
"
  • /it = interactive (사용자 데스크톱에 GUI 보임)
  • /ru '<windows_user>' = 지정 사용자의 세션 컨텍스트
  • schtasks /run은 일정 시각과 무관하게 즉시 실행
  • 결과 파일을 게스트에 쓰게 한 다음 qm guest file-read로 회수 → 비동기 + 가시성 + 회수성

6. VM 라이프사이클 명령 (자주 쓰는 것만)

qm status 101                    # 상태 (running/stopped/...)
qm start 101                     # 시작
qm stop 101                      # 강제종료 (전원 끊기)
qm shutdown 101                  # ACPI shutdown (OS에 종료 신호)
qm reboot 101                    # ACPI 재시작
qm reset 101                     # 하드 리셋
qm config 101                    # config 보기
qm list                          # 전체 VM 목록
qm set 101 --boot order=sata1;sata0;ide0   # 부팅 순서
qm set 101 --sata1 pve-zfs:vm-101-disk-1   # 디스크 attach
qm set 101 --delete unused0                 # 디스크 detach

7. VHD/디스크 임포트 패턴 (실전: SMB → ZFS raw zvol)

# 1) SMB 마운트 (pve5에서)
mkdir -p /mnt/vhd-src
mount -t cifs //<SMB_HOST>/vhd /mnt/vhd-src \
  -o username=<SMB_USER>,password=$SMB_PASSWORD,ro,vers=3.0,iocharset=utf8

# 2) vhd 메타데이터 확인
qemu-img info /mnt/vhd-src/vm100.vhd

# 3) qm importdisk 한방 (vhd → raw zvol on pve-zfs)
#    nohup으로 백그라운드 권장 (106GB 약 30-40분 소요)
nohup qm importdisk 101 /mnt/vhd-src/vm100.vhd pve-zfs --format raw \
  > /tmp/import.log 2>&1 &

# 4) 완료 후 unused0 → sata1 (또는 원하는 슬롯)
qm set 101 --sata1 pve-zfs:vm-101-disk-1

# 5) 부팅 순서 변경
qm set 101 --boot 'order=sata1;sata0;ide0'

# 6) SMB 정리
umount /mnt/vhd-src

디스크 검증 (부팅 전)

fdisk -l /dev/zvol/pve-zfs/vm-101-disk-1
# Windows UEFI 정상 = part1(Recovery) + part2(EFI System) + part3(MSR) + part4(NTFS)

mount -o ro /dev/zvol/pve-zfs/vm-101-disk-1-part2 /mnt/efi
ls /mnt/efi/EFI/Microsoft/Boot/bootmgfw.efi    # 있으면 부팅 가능
umount /mnt/efi

8. 흔한 트러블슈팅

WARN: no efidisk configured! Using temporary efivars disk.

OVMF 부팅 VM에 NVRAM 저장 디스크가 없음. 매 부팅 시 EFI 부트 엔트리가 초기화됨. 영구 NVRAM 만들려면 VM 종료 후:

qm set 101 --efidisk0 pve-zfs:1,efitype=4m,pre-enrolled-keys=0

fallback boot path(\EFI\Microsoft\Boot\bootmgfw.efi, \EFI\Boot\bootx64.efi)가 있으면 efidisk 없어도 부팅은 됨.

GPT PMBR size mismatch 경고

zvol 크기가 vhd 가상 크기보다 약간 큰 경우. Windows 부팅에는 영향 없음. 수정하려면 게스트에서 diskpart로 GPT 백업 위치 재기록.

qm sendkey 입력이 안 먹힘

  • 화면 잠겨있거나 다른 모달 다이얼로그 떠있는지 screendump으로 확인
  • 키 사이 간격 너무 짧으면 일부 키 손실 → sleep 0.05 삽입
  • IME가 한글모드라면 영문으로 전환 필요 (shift + 한/영 키는 표준 매핑 없음, OS GUI에서 미리 영문으로)

qm guest exec가 "guest agent not running"

  • VM config에 agent: 1 있는지 확인 (qm config 101 | grep agent)
  • 게스트 안에서 서비스 확인: Get-Service QEMU-GA (Windows) / systemctl status qemu-guest-agent (Linux)
  • VM이 막 부팅됐다면 agent 서비스 시작까지 30~60초 대기

qemu-ga가 SYSTEM이라 사용자 환경변수가 다름

%USERPROFILE%, 매핑 드라이브, 사용자 설치 프로그램 등에 접근 못 함. 사용자 세션 컨텍스트 필요하면 § 5 schtasks 패턴 사용.

Windows SATA → VirtIO-SCSI 디스크 컨트롤러 전환 (마스터화)

가장 흔한 함정: pnputil /add-driver vioscsi.inf /install로 등록만 하면 BSOD 0xc0000017 / 0xc0000225 발생. 이유는 vioscsi 서비스가 DEMAND_START(=3) 으로 등록되어 boot critical이 아니기 때문. winload가 vioscsi.sys를 부팅 시점에 로드하지 않아 OS 디스크 접근 실패.

해결책: 게스트 안에서 vioscsi를 BOOT_START(=0)으로 명시 승격.

sc config vioscsi start= boot
reg query HKLM\SYSTEM\CurrentControlSet\Services\vioscsi /v Start
# 결과가 0x0 이어야 함

검증된 마스터화 절차 (현재 SATA 부팅 → VirtIO-SCSI 단독 마스터):

  1. virtio-win.iso 마운트 + pnputil /add-driver 로 vioscsi/viostor/netkvm INF 등록
  2. sc config vioscsi start= boot ← 핵심
  3. Windows ACPI shutdown (NTFS dirty + hive transaction log 정리)
  4. ZFS 클론 (같은 풀 내):
    zfs snapshot pve-zfs/vm-<vmid>-disk-0@master
    zfs send pve-zfs/vm-<vmid>-disk-0@master | zfs recv pve-zfs/vm-<vmid>-disk-NEW
    zfs destroy pve-zfs/vm-<vmid>-disk-0@master
    zfs destroy pve-zfs/vm-<vmid>-disk-NEW@master
    
  5. config 교체:
    qm set <vmid> --delete sata0
    qm set <vmid> --scsi0 pve-zfs:vm-<vmid>-disk-NEW,iothread=1,discard=on,ssd=1
    qm set <vmid> --scsihw virtio-scsi-single
    qm set <vmid> --boot 'order=scsi0'
    
  6. 부팅 검증

주의 — 동일 GPT signature 디스크 두 개 동시 attach 시 함정: SATA 디스크와 그 raw 변환본/클론을 동시에 attach하면 NT namespace 매핑 이상으로 BSOD 발생. 한쪽 detach 후 단독으로 검증.

vhd → raw 변환본이 부팅 안 될 때 (sata 원본은 부팅 OK)

원인 가능성: qm importdisk로 vhd → raw 변환 시 disk size에 1MB 패딩 추가됨 (vmcfg sata1: ...size=122881M 같이 +1 표기). 이 padding이 GPT backup header 위치를 미세하게 흔들 수 있음. 또는 우리 작업 경험상 SYSTEM hive 손상 가능성도 있음. 해결책:

  • 부팅 가능한 sata 원본을 ZFS 클론해서 그걸 마스터로 사용 (vhd 변환본 폐기)
  • 또는 sata 원본 자체를 in-place로 virtio-scsi 인터페이스 변경 (drive number는 NT 동적이라 OK)

9. 참고 명령 치트시트

# 화면 한 번 보기 (pve5에서 + Claude 환경으로 회수)
sshpass -p "$PVE_PASSWORD" ssh root@<PVE_HOST> 'echo "screendump /tmp/s.ppm" | qm monitor 101'
sshpass -p "$PVE_PASSWORD" scp root@<PVE_HOST>:/tmp/s.ppm /tmp/s.ppm && ffmpeg -y -i /tmp/s.ppm /tmp/s.png

# 게스트 정보 한 번에
sshpass -p "$PVE_PASSWORD" ssh root@<PVE_HOST> '
  qm agent 101 get-osinfo
  qm agent 101 get-host-name
  qm agent 101 get-users
'

# 게스트에서 PowerShell 한 줄
sshpass -p "$PVE_PASSWORD" ssh root@<PVE_HOST> \
  'qm guest exec 101 --timeout 30 -- powershell.exe -NoProfile -Command "Get-Process | Select Name,Id"'

# 게스트에 Python 스크립트 던지고 실행
LOCAL_PY=/tmp/probe.py
cat > $LOCAL_PY <<'EOF'
import os, json
print(json.dumps({"cwd": os.getcwd(), "env_user": os.environ.get("USERNAME")}))
EOF
sshpass -p "$PVE_PASSWORD" ssh root@<PVE_HOST> "
  qm guest file-write 101 'C:\\temp\\probe.py' --content \"\$(cat)\" <<< \"\$(cat $LOCAL_PY)\"
  qm guest exec 101 --timeout 30 -- python.exe 'C:\\temp\\probe.py'
"

10. DR 이전 (다른 PVE 노드로 옮기기)

원본 정지 후 다른 노드에서 켜는 시나리오. pve5 ↔ pve7 모두 같은 PBS(<PBS_STORAGE_A> / <PBS_STORAGE_B>)에 연결돼있어 PBS 경로가 가장 깔끔.

사전 점검 (이전하기 좋은 상태 만들기)

  • efidisk0 설정됨 (없으면 매 부팅 NVRAM 초기화 — qm set <vmid> --efidisk0 <stor>:1,efitype=4m,pre-enrolled-keys=0)
  • VirtIO 드라이버 설치 상태 확인 (Get-PnpDevice | Where Manufacturer -like "*Red Hat*")
  • 현재 인터페이스(sata/e1000 등)를 대상 PVE도 지원하는지 — 동일 인터페이스 유지가 가장 안전
  • cpu: host면 대상 노드 CPU 세대가 같은지 확인 (다르면 cpu: x86-64-v2-AES 등 호환 모델로 사전 변경)
  • 불필요한 디스크 detach (사용 안 하는 zvol/qcow2 정리)

절차 (PBS 백업 → 다른 PVE에서 restore)

# 1) 원본 PVE에서 백업 (실행 중에도 가능, snapshot 모드)
ssh root@pve5 'vzdump 101 --storage <PBS_STORAGE_A> --mode snapshot --compress zstd'

# 2) 백업 ID 확인
ssh root@pve5 'pvesm list <PBS_STORAGE_A> --vmid 101 | tail -5'

# 3) 대상 PVE에서 restore (새 VMID 또는 동일 VMID)
ssh root@pve7 '
  pvesm list <PBS_STORAGE_B> --vmid 101 | tail -5    # 같은 PBS면 보임
  qmrestore <BACKUP_VOLID> <NEW_VMID> --storage local-lvm
  # 예: qmrestore <PBS_STORAGE_B>:backup/vm/101/2026-05-19T... 201 --storage local-lvm
'

# 4) 원본 정지 + 대상 시작
ssh root@pve5 'qm shutdown 101'
ssh root@pve7 'qm start <NEW_VMID>'

절차 (vzdump → scp → qmrestore, PBS 없을 때)

ssh root@pve5 'vzdump 101 --dumpdir /var/lib/vz/dump --mode snapshot --compress zstd'
scp root@pve5:/var/lib/vz/dump/vzdump-qemu-101-*.vma.zst root@pve7:/var/lib/vz/dump/
ssh root@pve7 'qmrestore /var/lib/vz/dump/vzdump-qemu-101-*.vma.zst 201 --storage local-lvm'

스토리지 차이 주의

  • pve5 = pve-zfs (ZFS zvol, raw)
  • pve7 = local-lvm (LVM-thin, raw)
  • 둘 다 raw 기반이라 restore 자동 변환됨. qcow2 dir로 가면 qcow2로 변환.

MAC/UUID 충돌 (동시 운영 시에만)

원본을 정지하고 옮기면 충돌 없음. 동시 운영이면:

qm set <NEW_VMID> --net0 e1000=AUTO,bridge=vmbr0   # MAC 자동 재생성
qm set <NEW_VMID> --vmgenid 1                       # vmgenid 새로 (Windows AD/DC 인식 변경)
qm set <NEW_VMID> --smbios1 uuid=auto              # SMBIOS uuid 새로

11. 보안 메모

  • QGA는 SYSTEM 권한이라 게스트 OS 안에서 사실상 할 수 있는 게 무제한. 신뢰되는 게스트에만 사용.
  • pve5 SSH 키 또는 적어도 ssh-agent 사용을 권장 (현재는 sshpass + 평문 패스워드)
  • SMB 마운트는 ro 기본, 쓰기 작업 필요할 때만 rw
  • qm sendkey로 비밀번호 같은 민감 정보 입력 시 screendump에 남을 수 있음 — 캡처 파일 즉시 삭제