2018-10-01

Veeam 備份 Replicated VMs 時遇到的問題與迂迴的解決辦法

朋友使用 Veeam Backup & Replication 做 Hyper-V 的 VM 備份, 卻遇到了一個解不開的問題
先說他的備份環境大致是這樣:「所有 Production 的 Hyper-V Hosts 先將 VM Replica 到一台肚子超大的 Server 裡, 然後再用 Veeam 去備份這些副本 VM」
聽起來這個策略沒什麼問題, 在副本 Host 上備份根本理所當然, 不會去影響到 Production 的效能
但當備份到容量較大的 VM, 尤其是 SQL Server 的時候卻產生了問題
Full Backup 時總會遇到以下錯誤:

Processing Mercury Error: Failed to call wmi method 'DestroySnapshot'. Wmi error: '32775' Failed to delete VM snapshot, snapshot path '%2'.

Failed to create VM recovery checkpoint (mode: Crash consistent) Details: Failed to call wmi method 'CreateSnapshot'.
Wmi error: '32775' Failed to create VM recovery snapshot,VM ID 'ooooooooooooooooooo'.

Retrying snapshot creation attempt (Failed to create production checkpoint.)
Task has been rescheduled
Queued for processing at [Date Time]
Unable to allocate processing resources. Error: Failed to call wmi method 'CreateSnapshot'.

Wmi error: '32775' Failed to create VM recovery snapshot, VM ID 'ooooooooooooooooooo'.

在備份的 Repository 裡面的確有看到 vbk 檔案, 看起來很像有備份成功
但上述錯誤發生後該 VM 會卡在 Applying Changes, 後續複寫則全部失敗
且 Hyper-V Virtual Machine Management Service 無法 Stop, 重開機指令下去後要等待兩小時左右才能重開機成功

一步一步來, Workaround 的方式如下:
  1. 遇到問題後先將 Hyper-V Virtual Machine Management Service 改為手動啟動, 不要讓他開機自動啟動
  2. Stop Hyper-V Virtual Machine Management Service
  3. 下指令 taskkill /f /im vmms.exe 將 Process Kill 掉
  4. 重開機就不需要等兩小時
  5. 開好機後在 net start vmms 指令之後緊接著快速地連續下達以下指令
    Get-VM "YourVMName" | Get-VMCheckpoint -SnapshotType Recovery | Remove-VMCheckpoint
  6. 步驟 5 最好是多開幾個視窗, 連續下指令, 錯誤沒關係, 就怕來不及, VM 又進入了卡在 Apply Changes 的狀態
  7. 這樣做完後可以至少將 VM 的狀態復原, 後續再嘗試備份
接下來要講解決辦法:

★. 在備份之前 Pause Replication

問題是一個 Backup Job 裡面有很多 VM, Veeam 又不提供在每一個 VM 備份前後執行 Script 的功能, 只能在 Backup Job 開始前、後跑 Script, 因此勢必要找其他辦法辦到此事。最後根據 Veeam 所提供的 PowerShell CMDLet 指令, 完成了以下 Script

運作邏輯是:
  1. 找出 Job 中所要備份的 VM List 存成一個 txt 檔
  2. 從第一台 VM 開始, 將其他的 VM 先設為排除 (Exclude) 項目
  3. 將 Backup Job 中留下要進行備份的 VM 暫停複寫
  4. 執行 Backup Job
  5. 完成備份後將該已完成的 VM 寫入一個 txt 檔作為已完成備份的標記
  6. 將剛剛備份完成的 VM 恢復複寫
  7. 將剛剛設為排除的所有其他 VM 加回 Backup Job
  8. 比對 VM List 與已完成 VM List 的 txt 檔比較, 找出下一台還沒備份的 VM
  9. 重複上述迴圈直到所有 VM 備份完畢
我覺得 Veeam 這點應該加強, 為何不能只指定 Backup Job 的其中一台 VM 進行備份? 而要用「將其他 VM 設為排除項目」這樣的方式來做, 實在很迂迴也容易出錯. 當然有另一種方式是把每一台 VM 都設一個 Backup Job, 但那樣的話設定實在太繁瑣了, VM 多的話根本不可行

Script 中有一段要去讀取一個 Password.txt 檔案, 是因為 Veeam Backup 一定不是安裝在 Hyper-V Host 上, 所以去把 VM 複寫暫停這件事情要透過 Remote PowerShell 去執行. 要做到這件事情要進行以下步驟
  1. 在被控端 Hyper-V-Host 執行指令: WinRM quickconfig 允許 WinRM 連入
  2. 在控制端 Veeam Backup Server 上面設定相信被控端 Hyper-V-Host
    指令: Set-Item WSMan:\localhost\Client\TrustedHosts "Hyper-V-Host"
    (我一直覺得很怪, 為什麼是控制端要去相信被控端而不是反過來呢....:Q)
  3. 步驟二執行完畢可以用以下指令檢查
    winrm s winrm/config/client '@{TrustedHosts="Hyper-V-Host"}'
完成上述步驟後, 要把用來連線被控端 Hyper-V-Host 所使用帳號的密碼先存成一個加密的檔案 (Password.txt), 不然無法自動連線執行, 要把密碼存成檔案要用以下指令
  • Read-Host -Prompt 'Enter Password' -AsSecureString | ConvertFrom-SecureString | Out-File Password.txt

以下為 Powershell Script:


#----- 設定開始

$JobName = "VMBackupJob";

#----- 程式開始

$DateTimeString = Get-Date -format yyyyMMdd_HHmmss
$TranscriptLog = (Get-Item -Path ".\" -Verbose).FullName + "\Logs\" + ("CertRenew_" + $DateTimeString + ".log")
start-transcript -path $TranscriptLog

Add-PsSnapin -Name VeeamPSSnapIn -ErrorAction SilentlyContinue;

$AllVM_Text = 'AllVM.txt';
$ProcessedVM_Text = 'ProcessedVM.txt';

write-host "";
Write-Host "Get All VMs in Job";
write-host "";

$Job = Get-VBRJob -Name $JobName;
$AllVM = $Job.GetObjectsInJob().name;

if ((Test-Path $AllVM_Text) -eq $True) {
 Remove-Item $AllVM_Text;
};
if ((Test-Path  $ProcessedVM_Text) -eq $True) {
 Remove-Item $ProcessedVM_Text;
};
$AllVM | Out-File $AllVM_Text;
New-Item $ProcessedVM_Text -ItemType File | out-null;

foreach ($VM in $AllVM) {
 $Done = 0
 $ProcessedVM = @();
 $ProcessedVM = Get-Content $ProcessedVM_Text;
 foreach ($VMed in $ProcessedVM) {
  if ($VMed -eq $VM) {
   $Done = 1;
  };
 };
 if ($Done -eq 0) {

  write-host "";
  Write-Host "Check Excluded VMs and Add Back to Job";
  write-host "";
  
  $ExcludeObjs = $Job.GetObjectsInJob() | where {$_.Type -eq "Exclude"};
  Add-VBRHvJobObject -job $Job -Entities (Find-VBRHvEntity -Name $ExcludeObjs.Name) | out-null;
  $ExcludeObjs.Delete();
  
  write-host "";
  Write-Host "Exclude VMs other than $VM";
  write-host "";
  
  Remove-VBRJobObject -Objects ($Job.GetObjectsInJob() | where {$_.Name -ne $VM});

  # Read-Host -AsSecureString | ConvertFrom-SecureString | Out-File 'Password.txt'
  $Password = Get-Content 'Password.txt' | ConvertTo-SecureString
  $Cred = new-object -TypeName System.Management.Automation.PSCredential -argumentlist "Hyper-V-Host\Administrator", $Password

  write-host "";
  Write-Host "Suspend-VMReplication on VM: $VM";
  write-host ""
  
  Invoke-Command -ComputerName "Hyper-V-Host" -Authentication Negotiate -Credential $Cred {
   $VM = $args[0];
   Suspend-VMReplication -ComputerName "Hyper-V-Host" -VMName $VM -Confirm:$False
  } -ArgumentList $VM;

  write-host "";
  Write-Host "Start Backup Job on VM: $VM";
  write-host "";
  
  Start-VBRJob -Job $Job 
  
  # Mark VM as Backuped
  Add-Content $ProcessedVM_Text $VM
  
  write-host "";
  Write-Host "Resume-VMReplication on VM: $VM";
  write-host "";
  
  Invoke-Command -ComputerName "Hyper-V-Host" -Authentication Negotiate -Credential $Cred {
   $VM = $args[0];
   Resume-VMReplication -ComputerName "Hyper-V-Host" -VMName $VM -Confirm:$False
  } -ArgumentList $VM;
  
  write-host "";
  Write-Host "Add Excluded VMs Back to Job";
  write-host "";
  
  $ExcludeObjs = $Job.GetObjectsInJob() | where {$_.Type -eq "Exclude"};
  Add-VBRHvJobObject -job $Job -Entities (Find-VBRHvEntity -Name $ExcludeObjs.Name) | out-null;
  $ExcludeObjs.Delete();
 };
};

Remove-Item $AllVM_Text
Remove-Item $ProcessedVM_Text

write-host "";
Write-Host "ackup Job Finished";
write-host ""

Stop-Transcript

exit;

沒有留言:

張貼留言