Files
pve-vm-ops-guide/JOURNEY.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

382 lines
13 KiB
Markdown

# 실패의 연대기 (Failure Journal)
> Windows VM의 디스크 컨트롤러를 **SATA → VirtIO-SCSI**로 마이그레이션하는 과정에서 만난 모든 함정의 기록.
> 진짜 원인 — `vioscsi START_TYPE=0 (BOOT_START)` 누락 — 을 찾기까지 수 시간을 소비했다.
> 같은 길을 가는 사람이 이 글을 읽고 30분에 끝낼 수 있길.
작성일: 2026-05-19
작업 환경: Proxmox VE 8.1.3 / Windows 10 Pro 19045 / virtio-win-0.1.285
---
## 출발점
목표:
- SMB에 보관된 `vm100.vhd` (120GiB virt, 106GiB sparse) Windows 디스크 이미지를 Proxmox VM에 활용
- 가능하면 SATA 에뮬레이션 대신 **VirtIO-SCSI 컨트롤러**로 운영해서 성능/효율 확보 (멀티큐, iothread, discard/TRIM)
도구:
- `qm importdisk` (vhd → raw zvol 변환)
- ZFS pool `pve-zfs` (vol storage)
- `pnputil` (Windows 드라이버 등록)
- `qm sendkey`, QEMU monitor screendump (원격 시각 확인)
---
## Step 1 — vhd → raw zvol 변환
```bash
qm importdisk <VMID> /mnt/smb/vm100.vhd pve-zfs --format raw
```
결과: `pve-zfs:vm-<VMID>-disk-1` 생성 (REFER 73.4GB sparse). config에 `unused0:`로 자동 등록.
**관찰**: 변환 후 zvol 크기가 120GiB + ~1MB 패딩. fdisk가 `GPT PMBR size mismatch` 경고. 이때는 대수롭지 않게 넘어감.
---
## Step 2 — sata1로 attach + 부팅 시도
```bash
qm set <VMID> --sata1 pve-zfs:vm-<VMID>-disk-1
qm set <VMID> --boot 'order=sata1;sata0;ide0'
qm start <VMID>
```
**결과**: Windows 10 데스크톱 정상 진입! 사용자도 다 보임. 데이터 모두 살아있음.
**우리가 잘못 알았던 것**: "vhd 변환이 잘 됐구나" — 사실은 **sata0 (기존에 갖고 있던 vm-101-disk-0 zvol)에서 부팅된 것**이었음. `Get-Disk`로 확인 안 했으니 모름.
---
## Step 3 — 안정화: efidisk 추가, VirtIO 드라이버 등록
부팅이 잘 되니 마스터화 준비. 두 가지 작업 진행.
### 3-1. efidisk0 추가 (OVMF NVRAM 영구화)
```bash
qm set <VMID> --efidisk0 pve-zfs:1,efitype=4m,pre-enrolled-keys=0
```
이전엔 `WARN: no efidisk configured` 떠서 매 부팅마다 OVMF가 NVRAM 새로 만들고 fallback path로 부팅했음. 영구화하면 부팅 엔트리 저장됨.
### 3-2. VirtIO 드라이버 사전 설치
virtio-win.iso를 attach하고 임시 SCSI 디스크도 같이 띄워 게스트가 새 PCI 디바이스 인식하게 만든 다음:
```cmd
pnputil /add-driver F:\vioscsi\w10\amd64\vioscsi.inf /install
pnputil /add-driver F:\viostor\w10\amd64\viostor.inf /install
pnputil /add-driver F:\NetKVM\w10\amd64\netkvm.inf /install
```
`pnputil`이 INF 파일을 driver store에 등록 + `/install` 옵션이 매칭되는 PnP 디바이스에 즉시 설치. `Get-PnpDevice`에서 "Red Hat VirtIO SCSI pass-through controller" Status=OK 보임. `Get-Service vioscsi` → RUNNING.
**우리가 잘못 알았던 것**: "드라이버 등록 완료, 이제 부팅 디스크 인터페이스 바꿔도 BSOD 안 나겠지" — **틀렸다**.
---
## Step 4 — 인터페이스 교체: sata1 → scsi0
깨끗한 마스터를 위해 sata1 detach, scsi0로 같은 zvol attach:
```bash
qm set <VMID> --scsi0 pve-zfs:vm-<VMID>-disk-1,iothread=1,discard=on,ssd=1
qm set <VMID> --delete sata1
qm set <VMID> --boot 'order=scsi0'
qm start <VMID>
```
**결과**: **BSOD 0xc0000225** (Recovery / "필요한 장치가 연결되어 있지 않거나 장치에 액세스할 수 없습니다") — `\Windows\system32\config\system` 위치 못 찾음.
이때 처음으로 뭔가 잘못됐다고 느낌. 그러나 원인을 GPT signature 충돌로 오해.
---
## Step 5 — 오해의 시작: "GPT signature 충돌이 원인이다"
`Get-Disk`로 보니 두 디스크 GUID 완전 동일:
```
Disk 0: SATA, GUID {4bca652d-2f35-4bd7-8175-8951593eb8c1}, 120GB
Disk 1: SAS, GUID {4bca652d-2f35-4bd7-8175-8951593eb8c1}, 120GB (1MB padding 더 큼)
```
호스트에서 `fdisk -l /dev/zvol/...`로 검증 — 두 zvol의 Disk identifier 동일, 모든 partition UUID 동일.
**가설 (틀린 것)**: Windows가 동일 GPT signature 두 디스크 보면 한쪽을 자동 Offline 처리 → sata0 detach 후 scsi0 단독으로는 BCD device path 매칭 실패.
이 가설로 BCD 수정 시도들이 시작됨.
---
## Step 6 — BCD 수정 시도 1: device/osdevice → "boot" 키워드
```cmd
bcdedit /export C:\bcd-backup-20260519.bcd
bcdedit /set {bootmgr} device boot # 거부됨 (정상)
bcdedit /set {default} device boot # 성공
bcdedit /set {default} osdevice boot # 성공
```
`boot` 키워드 의미: "이 BCD가 로드된 partition을 동적으로 추적해서 사용". 매혹적이지만 **잘못된 적용**.
**검증**: shutdown → sata0 detach → 부팅 → **여전히 BSOD 0xc0000225**.
**왜 안 먹혔나**: `osdevice = boot`는 사실 잘못된 의미. winload는 "boot device = system partition (EFI 100MB FAT32)"으로 해석해서 거기서 `\Windows\system32\winload.efi` 찾으려고 함. EFI partition에 \Windows 없으니 fail.
---
## Step 7 — BCD 수정 시도 2: bcdboot으로 새 BCD 생성
scsi0의 EFI partition을 마운트해서 BCD 재구성:
```cmd
diskpart 스크립트로 Disk 1 Partition 2에 S: 할당
bcdboot C:\Windows /s S: /f UEFI /v
```
bcdboot이 정상 동작:
```
BFSVC: Creating Recovery directory.
BFSVC: Creating OsLoader object.
BFSVC: Setting {default} to {bfc003a5-...}
부팅 파일을 만들었습니다.
```
새 BCD enum:
```
{default}.device = partition=\Device\Harddisk0\Partition4
{default}.osdevice = partition=\Device\Harddisk0\Partition4
```
**검증**: shutdown → sata0 detach → 부팅 → **다른 BSOD 0xc0000017** (system registry file missing).
이제 한 단계 더 진행됨: OVMF→boot manager→winload까지 OK, OS partition 도달, 거기서 SYSTEM hive 못 읽음.
---
## Step 8 — NTFS 무결성 검사: ntfsfix
호스트에서 NTFS 마운트:
```bash
ntfs-3g /dev/zvol/pve-zfs/vm-<VMID>-disk-1-part4 /mnt/winc -o ro
ls /mnt/winc/Windows/System32/config/SYSTEM # 24.5MB 존재
hexdump -C -n 16 /mnt/winc/Windows/System32/config/SYSTEM
# 00000000 72 65 67 66 ... ← "regf" 시그니처 OK
umount /mnt/winc
ntfsfix /dev/zvol/pve-zfs/vm-<VMID>-disk-1-part4
# Processing of $MFT and $MFTMirr completed successfully.
# Checking the alternate boot sector... OK
# NTFS partition was processed successfully.
```
**검증**: 부팅 시도 → **BSOD 0xc0000017 그대로**.
SYSTEM hive 파일은 존재 + 헤더 정상 + NTFS metadata clean. 그러나 부팅 실패.
---
## Step 9 — NVRAM 초기화 시도
가설: OVMF NVRAM에 옛 부트 엔트리(detach된 디스크 가리키는 것)가 캐시되어 fallback 실패.
```bash
qm set <VMID> --delete efidisk0
qm set <VMID> --efidisk0 pve-zfs:1,efitype=4m,pre-enrolled-keys=0
qm start <VMID>
```
**결과**: 새 NVRAM 만들고 부팅 → **BSOD 0xc0000017 그대로**.
---
## Step 10 — WinRE Startup Repair 시도
BSOD 화면에 "F1 to enter Recovery Environment" 안내가 있음. `qm sendkey <VMID> f1`로 시도.
**결과**: WinRE 진입 안 됨. vhd 백업본에 WinRE WIM이 비활성/누락 상태. 막다른 길.
---
## Step 11 — SSD 마이그레이션식 클론 시도
> 사용자 제안: "보통은 디스크 두 개 만들고 1:1 복사하는 도구로 옮기잖아, SSD 마이그레이션처럼"
ZFS send/recv로 vhd 변환본을 새 zvol에 클론:
```bash
zfs snapshot pve-zfs/vm-<VMID>-disk-1@clone-master
time (zfs send pve-zfs/vm-<VMID>-disk-1@clone-master | zfs recv pve-zfs/vm-<VMID>-disk-4)
# real 10m50.003s
```
config 교체: 기존 vhd 변환본 detach + 클론을 scsi0로 attach → boot order=scsi0 단독.
**결과**: **BSOD 0xc0000017 — 클론도 동일 증상**.
이로써 가설이 확정적으로 깨짐: GPT 충돌이 단독 원인이라면 단일 디스크 상태에서 해결됐어야 함. 그런데 안 됨.
---
## Step 12 — 결정적 진단: sata0 단독 부팅 검증
```bash
qm set <VMID> --delete scsi0
qm set <VMID> --sata0 pve-zfs:vm-<VMID>-disk-0,size=120G
qm set <VMID> --boot 'order=sata0'
qm start <VMID>
```
**결과**: ✅ **정상 부팅** (Windows 10 잠금화면, 0bin 사용자).
이게 결정적. **sata0 단독은 부팅 가능 + vhd 변환본 단독은 부팅 불가**. 두 zvol 데이터가 GPT signature는 같지만 어딘가 다르다 — 또는 컨트롤러 차이 자체가 원인.
게스트 상태 확인:
```
Get-Disk → Disk 0 SATA Online
Get-Partition → DiskNumber=0 IsBoot=True
Get-Service vioscsi → RUNNING
sc qc vioscsi → START_TYPE: 3 DEMAND_START ← 🔥 발견!
```
**진짜 원인 발견**: vioscsi 서비스가 등록은 되어 있지만 **boot critical(=0)이 아니라 수동 시작(=3)**. winload가 부팅 시점에 vioscsi.sys를 로드하지 않음 → VirtIO-SCSI 디스크에서 부팅 시 디스크 접근 못 함 → BSOD.
`pnputil /add-driver vioscsi.inf /install`은 드라이버 파일과 PnP 매칭만 등록. **서비스의 Start 값을 자동으로 boot critical로 승격하지 않는다**.
---
## Step 13 — 진짜 해결: vioscsi boot critical 승격
```cmd
sc config vioscsi start= boot
```
검증:
```
sc qc vioscsi → START_TYPE: 0 BOOT_START
reg query HKLM\SYSTEM\CurrentControlSet\Services\vioscsi /v Start
→ Start REG_DWORD 0x0
```
---
## Step 14 — Windows 정상 종료 + ZFS 클론 + 마스터화
```bash
# Windows ACPI shutdown
qm shutdown <VMID> --timeout 180
# 검증된 sata0 디스크를 새 zvol로 클론
zfs snapshot pve-zfs/vm-<VMID>-disk-0@master-final
time (zfs send pve-zfs/vm-<VMID>-disk-0@master-final | zfs recv pve-zfs/vm-<VMID>-disk-5)
# real 28m43.568s
# config 교체
qm set <VMID> --delete sata0
qm set <VMID> --scsi0 pve-zfs:vm-<VMID>-disk-5,iothread=1,discard=on,ssd=1
qm set <VMID> --scsihw virtio-scsi-single
qm set <VMID> --boot 'order=scsi0'
qm start <VMID>
```
---
## Step 15 — 검증: 진짜 마스터 완성
```
Get-Disk → Disk 0 QEMU QEMU HARDDISK BusType=SAS 120GB Online
Get-Partition → DiskNumber=0 IsBoot=True
sc query vioscsi → RUNNING
sc qc vioscsi → START_TYPE: 0 BOOT_START
```
**단일 디스크 + virtio-scsi-single + iothread + discard + boot critical** = 마스터 완성. Windows 10 잠금화면 정상 진입.
---
## 교훈 (lessons learned)
### 진짜 원인은 vioscsi의 START_TYPE
다른 모든 가설(GPT signature 충돌, BCD 손상, NVRAM 캐시, vhd 변환 미세 손상)은 부분적으로 그럴듯했지만 **단독 원인이 아니었다**. 진짜 원인은:
```
vioscsi Service Start=3 (DEMAND_START)
winload가 부팅 시점에 vioscsi.sys 안 로드
virtio-scsi 디스크에서 부팅 시 디스크 IO 핸들러 부재
INACCESSIBLE_BOOT_DEVICE → BSOD 0xc0000017 또는 0xc0000225
```
### pnputil의 한계
- `pnputil /add-driver vioscsi.inf /install`
- 드라이버 파일과 PnP 디바이스 매칭은 잘 한다
- 그러나 **서비스의 boot priority는 건드리지 않는다**
- 그래서 매뉴얼하게 `sc config vioscsi start= boot` 또는 레지스트리 직접 수정 필요
### "vhd → raw 변환본"은 부팅 보증 안 된다
- `qm importdisk`로 vhd → raw 변환 시 사이즈에 패딩 추가, GPT backup 위치 미세 차이
- 검증된 sata 원본을 ZFS 클론하는 게 더 안전
- vhd는 "백업본"이지 "라이브 부팅 가능 디스크"가 아닐 수 있음
### 동일 GPT signature 디스크 두 개를 동시 attach하지 말 것
- Windows가 한쪽을 자동 Offline 처리하지만 NT namespace 매핑/BCD path 종속성으로 detach 시 BSOD
- 마이그레이션 작업은 단계별로 단독 attach 검증 권장
### bcdedit `osdevice = boot`은 함정
- "boot manager가 로드된 partition을 동적 추적"이라는 매혹적인 표현
- 그러나 winload는 그걸 "system partition (EFI)"으로 해석 → \Windows 못 찾음 → BSOD
- 잘못된 BCD 수정 패턴. **사용하지 말 것**.
### bcdboot은 깔끔하지만 만능 아님
- BCD store 새로 생성 + 자기 자신 가리키도록 설정
- 그러나 partition UUID는 정확히 같은 zvol이라야 매칭됨
- 동일 GPT signature 디스크가 같이 있을 때는 효과 제한적
---
## 부록: 알아두면 좋은 진단 명령
```cmd
:: 서비스 시작 유형
sc qc vioscsi | findstr START_TYPE
reg query HKLM\SYSTEM\CurrentControlSet\Services\vioscsi /v Start
:: VirtIO 디바이스 인식
powershell "Get-PnpDevice | Where Manufacturer -like '*Red Hat*' | Format-Table -AutoSize"
:: 부팅 디스크
powershell "Get-Disk | Format-Table -AutoSize Number,FriendlyName,BusType,Size"
powershell "Get-Partition | Where IsBoot -eq \$true | Format-List"
:: BCD 진단
bcdedit /enum {bootmgr}
bcdedit /enum {default}
:: driverstore의 OEM INF 등록 상태
pnputil /enum-drivers | findstr /i /c:vioscsi /c:viostor /c:netkvm
```
```bash
# 호스트 (PVE)에서 게스트 디스크 NTFS 검증
mount -t ntfs-3g -o ro /dev/zvol/<pool>/vm-<VMID>-disk-N-part4 /mnt/winc
ls /mnt/winc/Windows/System32/config/SYSTEM
hexdump -C -n 16 /mnt/winc/Windows/System32/config/SYSTEM # "regf" 시그니처
umount /mnt/winc
ntfsfix /dev/zvol/<pool>/vm-<VMID>-disk-N-part4
# 두 zvol 비교
fdisk -l /dev/zvol/<pool>/vm-<VMID>-disk-N
```