Share Notes

chundev

View the Project on GitHub latteouka/share-notes

Nutanix AHV disk bus 線上轉換實戰:SATA → virtio-scsi

日期:2026-05-12

環境:Nutanix AOS 6.10.1.5、AHV 20230302.103014、Ubuntu 18.04 guest


TL;DR

把一台 VM 的 disk 從 SATA 換成 virtio-scsi,每台 downtime 約 15-60 秒(不是 hours,因為是 metadata-only clone 不真的搬資料)。 核心指令三步vm.disk_create bus=scsi clone_from_vmdiskvm.disk_delete disk_addr=sata.0vm.update_boot_device disk_addr=scsi.0關鍵注意:cluster 內有應用 quorum(DB / search / message queue)的 VM 要一台一台來,等 cluster 恢復 healthy 再動下一台。


為什麼要做這件事

詳見 前文:跨層除錯 Nutanix VM 偶發 read-onlyvirtio-scsi vs emulated SATA 深入

簡言之:emulated SATA 在儲存層偶發慢的情境下會 hard reset link,這在 software-defined storage 上常造成 partial write 丟失 → ext4 corruption。換 virtio-scsi 從根本拆掉這個 over-reaction。


Pre-flight 檢查(每台 VM 動之前必跑)

# 1. virtio_scsi 在 initramfs(開機初期就要有 driver 才能 mount root)
sudo lsinitramfs /boot/initrd.img-$(uname -r) | grep -iE 'virtio_scsi|virtio_blk'
# 期望:看到 virtio_scsi.ko + virtio_blk.ko

# 2. fstab 用 UUID/LVM(不能依賴 /dev/sd*)
cat /etc/fstab | grep -vE '^#|^$'
# 期望:/dev/disk/by-id/dm-uuid-LVM-... 或 /dev/disk/by-uuid/...

# 3. grub 用 lvmid 或 UUID
sudo grep '^set root' /boot/grub/grub.cfg | head -1
# 期望:set root='lvmid/...' 或 set root='hd0,gpt2'(UUID 模式)

# 4. 確認當下還是 SATA
lsblk
# 期望:sda 在 SATA bus(要看 /sys/block/sda/device/driver/ 指向 ata 才確定)

全綠才動,任一不符合都先補強(重編 initramfs 或改 fstab/grub)。


12-step 指令(單台 VM)

# === 變數準備 ===
VM=infominer-storage02
VMDISK_UUID=5248d62f-e309-4410-9745-975b9efc715e   # 從 acli vm.get 抄
TS=$(date +%Y%m%d_%H%M%S)

# === Step 1:保存 baseline(救命用) ===
acli vm.get $VM > ~/${VM}-before-$TS.txt

# === Step 2:通知應用層 stop service(你自己處理) ===
# 視應用而定,例如 systemctl stop pm2-ubuntu

# === Step 3:graceful shutdown VM ===
acli vm.shutdown $VM
# 輪詢直到 state=kOff
until acli vm.get $VM | grep -q 'state: "kOff"'; do sleep 5; done

# === Step 4:建立新 SCSI vmdisk(atomic clone of 舊 SATA) ===
# Nutanix 內部是 metadata-only,2.5TB 也只要幾秒
acli vm.disk_create $VM bus=scsi clone_from_vmdisk=$VMDISK_UUID

# === Step 5:sanity check 兩顆 disk 都在 ===
acli vm.get $VM | grep -E 'addr|bus:|index:|vmdisk_uuid'
# 應該看到 sata.0 (舊) + scsi.0 or scsi.1 (新)

# === Step 6:刪舊 SATA disk(acli 要互動確認,pipe yes) ===
echo yes | acli vm.disk_delete $VM disk_addr=sata.0

# === Step 7:明確指定從新 SCSI disk 開機 ===
echo yes | acli vm.update_boot_device $VM disk_addr=scsi.0

# === Step 8:確認最終配置 ===
acli vm.get $VM | sed -n '/boot {/,/^    }/p'
# 應該看到 disk_addr: bus="scsi" index=0

# === Step 9:power on ===
acli vm.on $VM
until acli vm.get $VM | grep -q 'state: "kOn"'; do sleep 5; done

# === Step 10:guest 內驗證 ===
ssh $VM "ls -l /sys/block/sda/device/driver/  # → 應指向 virtio_scsi
         mount | grep -E ' / | /boot'          # → rw mount OK
         sudo touch /tmp/test && echo OK"

# === Step 11:套 SCSI timeout 180s(防禦深度) ===
ssh $VM "
echo 180 | sudo tee /sys/block/sda/device/timeout
sudo tee /etc/udev/rules.d/99-storage-scsi-timeout.rules <<'EOF'
ACTION==\"add|change\", SUBSYSTEM==\"scsi\", ATTR{type}==\"0\", ATTR{vendor}==\"NUTANIX\", ATTR{timeout}==\"180\"
EOF
sudo udevadm control --reload-rules
sudo udevadm trigger --action=change --subsystem-match=scsi
"

# === Step 12:應用層服務啟動 + cluster healthy 驗證 ===
# 視應用而定

重點觀念:clone_from_vmdisk 是 metadata-only。2.5TB 跟 100GB 一樣快(幾秒),因為 storage 平台內部用 redirect-on-write,沒真的搬資料。


VM 有多顆 disk 的情境(boot disk 在 SATA、data disk 在 SCSI)

有些 VM 同時有 SATA + SCSI 兩顆 disk(例如歷史殘留)。要做的是只動 SATA 那顆

# 看現況
acli vm.get $VM | grep -E 'bus:|index:|vmdisk_uuid'
# 假設:sata.0 = 200GB OS root, scsi.0 = 100GB data

# 從 SATA clone(不指定 index → 自動放下個可用 SCSI slot,會是 scsi.1)
acli vm.disk_create $VM bus=scsi clone_from_vmdisk=<sata-vmdisk-uuid>

# 刪舊 SATA
echo yes | acli vm.disk_delete $VM disk_addr=sata.0

# 明確指定 boot 從新 SCSI(記得是 scsi.1 不是 scsi.0)
echo yes | acli vm.update_boot_device $VM disk_addr=scsi.1

陷阱:如果不明確 set boot device,BIOS 可能從 scsi.0(原本的 data disk)試開,找不到 bootloader 就失敗。


Cluster-aware 順序操作(多 node application)

如果 VM 是 cluster 成員(RethinkDB / ElasticSearch / Consul / RabbitMQ 等),絕對不要同時下兩台

操作骨架(3-node cluster 範例):

for VM in node1 node2 node3; do
  # 1. 改 VM bus
  do_bus_change $VM

  # 2. 等該 VM 應用層完全 rejoin cluster
  wait_until_cluster_green   # ElasticSearch: cluster health = green
                              # RabbitMQ: cluster_status partitions=[]
                              # Consul: members alive
                              # RethinkDB: client port LISTEN + cluster connected

  # 3. 再進入下一台
done

各 cluster 的「healthy」訊號:

Cluster 檢查指令 健康訊號
ElasticSearch curl localhost:9200/_cluster/health status: green + number_of_nodes 不變
RabbitMQ rabbitmqctl cluster_status partitions: [] + running_nodes = 全部
Consul consul members 所有 server alive
RethinkDB ss -tln \| grep 28015 port LISTEN

為什麼:分散式 cluster 同時下兩台 = quorum 喪失 = service unavailable + 可能 data inconsistency。


Rollback 策略

兩條路:

路 A — 從 snapshot 還原(最完整)

操作前用 PE UI 或 ncli 拍 snapshot:

ncli pd add-one-time-snapshot name=<PD-name>

若 VM 改完不正常,PE UI → Data Protection → Snapshot → Restore VM。 5-15 分鐘救回(會建一台 restored-<name> 的新 VM,把 NIC 換到原 IP 即可)。

路 B — 反向 bus change(建新 SATA 從快照 clone 回來)

acli vm.shutdown $VM
# 從快照拿 vmdisk_uuid(PE UI → snapshot detail)
acli vm.disk_create $VM bus=sata index=0 clone_from_vmdisk=<snapshot-vmdisk-uuid>
echo yes | acli vm.disk_delete $VM disk_addr=scsi.0
echo yes | acli vm.update_boot_device $VM disk_addr=sata.0
acli vm.on $VM

實測 downtime(10 台真實案例)

操作這天的紀錄(從 shutdown 到 power on):

Disk size Downtime
100 GB 12-21 秒
200 GB 21 秒
700 GB 1-4 分鐘(第一台 4 min 含 grace shutdown wait)
2.5 TB 17-25 秒
3 TB 17 秒

Downtime 跟 disk size 無關(因為 clone 是 metadata-only),跟「graceful shutdown service 要花多久」相關。

應用層 rejoin cluster 才是大頭:


學到的事

  1. clone_from_vmdisk 是 metadata-only,幾 TB 跟幾 GB 一樣快,不要被 disk size 嚇到不敢做
  2. acli 互動式確認要 pipe yesecho yes | acli vm.disk_delete ...,否則 stdin 一斷會死循環印 prompt
  3. VM 有多顆 disk 時,clone 完別忘了明確 set boot device:BIOS 預設掃 disk 順序可能不是你要的
  4. 3-node cluster 千萬不要同時下兩台:等該 node 應用層完全 rejoin 再動下一台
  5. SCSI device timeout 180s 是免費的防禦深度:udev rule 一行,重開機也持久,沒副作用
  6. Pre-flight check 比實際操作更重要:fstab 不用 UUID、grub 寫死 /dev/sda 才是真陷阱,現代 distro 預設都不會踩,但要先確認

參考資料

接續閱讀(建議從上往下讀):