VMware虛擬磁盤編程指導(九)

還原過程的底層實現

還原虛擬機和磁盤

無法對正在使用的虛擬磁盤進行寫操作。對於完全還原,你必須通過停止虛擬機並關閉電源,確保虛擬磁盤沒有被佔用。下面的代碼演示瞭如何關閉虛擬機:

// At this point weassume that you have a ManagedObjectReference to the VM – vmMoRef.

// Power on would needa ManagedObjectReference to the host running the VM – hostMoRef.

ManagedObjectReferencetaskRef = serviceConnection.powerOffVm(vmMoRef);

對於高級傳輸模式,你還原虛擬磁盤之前,還需要創建一個虛擬機快照。如果還原時已經存在快照,你需要移除它,否則SAN模式還原會失敗。

然後,就要使用VixDiskLib來重新載入虛擬磁盤的內容,所以下面的代碼是C++而不是Java

// At this point weassume that you already have a VixDiskLib connection to the server machine.
uint8mybuffer[some_multiple_of_512];
int mylocalfile =open(“localfile”, openflags); // Contains backup copy of virtual disk
read(mylocalfile,mybuffer, sizeof mybuffer);
vixError =VixDiskLib_Open(srcConnection, path, flags, &diskHandle);
VixDiskLib_Write(diskHandle,startSector, (sizeof mybuffer) / 512, mybuffer);

對於SAN傳輸模式,還需要恢復並刪除快照。如果你忘記了恢復快照,就會因爲CID不匹配導致快照刪除失敗,所以虛擬機不能開機。如果你忘了刪除快照,這個無關的快照就會引起後續備份的還原問題。

創建虛擬機

本節描述如何創建一個虛擬機對象,這比較複雜,但是是必須的,因爲你需要把數據還原到它裏面去。創建這個對象之前,需要創建一個VirtualMachineConfigSpec來描述虛擬機,以及它支持的所有虛擬設備。大部分需要的信息都可以從虛擬機屬性config.hardware.device中得到,它是包含設備配置信息的一張表。設備間的關係由key的值表示,它是設備的唯一標識符。每一個設備都有一個controllerKey,表示設備連接的控制器的key標識符。在VirtualMachineConfigSpec中使用負整數作爲臨時的key值,以保證這些臨時key值不和服務器你分配給他們的實際值衝突。當與默認設備關聯虛擬設備時,controllerKey屬性需要重置爲控制器的key屬性值(When associating virtual deviceswith default devices, the controllerKey property should be reset with the keyproperty of the controller)。下面是用來創建虛擬機的VirutalMachineConfigSpec的簡單配置:

//beginning ofVirtualMachineConfigSpec, ends serval pages later
{
       dynamicType = <unset>,
       changeVersion = <unset>,
//This is the displayname of the VM
       name = “My New VM”,
       version = “vmx-04”,
       uuid = <unset>,
       instanceUuid = <unset>,
       npivWorldWideNameType = <unset>,
       npivDesiredNodeWwns = <unset>,
       npivDesiredPortWwns = <unset>,
       npivTemporaryDisabled = <unset>,
       npivOnNonRdmDisks = <unset>,
       npivWorldWideNameOp = <unset>,
       locationId = <unset>,
// this is advisory,the disk determines the O/S
       alternameGuestName = “Microsoft WindowsServer 2008, Enterprise Edition”,
       annotation = <unset>,
       files = (vim.vm.FileInfo) {
              dynamicType = <unset>,
              vmPathName = “[plat004-local]”,
              snapshotDirectory =“[plat004-local]”,
              suspendDirectory = <unset>,
              logDirectory = <unset>,
       },
       tools = (vim.vm.ToolsConfigInfo) {
              dynamicType = <unset>,
              toolsVersion = <unset>,
              afterPowerOn = true,
              afterResume = true,
              beforeGuestStandby = true,
              beforeGuestShutdown = true,
              beforeGuestReboot = true,
              toolsUpgradePolicy =<unset>,
              pendingCustomization =<unset>,
              syncTimeWithHost = <unset>,
       },
       flags = (vim.vm.FlagInfo) {
              dynamicType = <unset>,
              disableAcceleration =<unset>,
              enableLoggin = <unset>,
              useToe = <unset>,
              runWithDebugInfo = <unset>,
              monitorType = <unset>,
              htSharing = <unset>,
              snapshotDisabled = <unset>,
              snapshotLocked = <unset>,
              diskUuidEnabled = <unset>,
              virtualMmuUsage = <unset>,
              snapshotPowerOffBehavior =“powerOff”,
              recordRelayEnabled =<unset>,
       },
       consolePreferences =(vim.vm.ConsolePreperences) null,
       powerOpInfo = (vim.vm.DefaultPowerOpInfo){
              dynamicType = <unset>,
              powerOffType = “preset”,
              suspendType = “preset”,
              resetType = “preset”,
              defaultPowerOffType =<unset>,
              defaultSuspendType =<unset>,
              defaultResetType = <unset>,
              standbyAction = “powerOnSuppend”,
       },
// the number of cups
       numCPUs = 1,
// the number ofmemory megabytes
       memoryMB = 256,
       memoryHotAddEnabled = <unset>,
       cpuHotAddEnabled = <unset>,
       cpuHotRemoveEnabled = <unset>,
       deviceChange =(vim.vm.device.VirtualDeviceSpec) {
              (vim.vm.device.VirtualDeviceSpec){
                     dynamicType =<unset>,
                     operation = “add”,
                     fileOperation =<unset>,
//CDRom
                     device =(vim.vm.device.VirtualCdrom) {
                            dynamicType =<unset>,
//key number of CDROM
                            key = -42,
                            deviceInfo =(vim.Description)null,
                            backing =(vim.vm.device.VirtualCdrom.RemotePassthroughBackingInfo) {
                                   dynamicType =<unset>,
                                   deviceName =“”,
                                   useAutoDetect= <unset>,
                                   exclusive =false,
                            },
                            connectable =(vim.vm.device.VirtualDevice.ConnecInfo) {
                                   dynamicType =<unset>,
                                   startConnected= false,
                                   allowGuestControl= true,
                                   connected =false,
                            },
//connects to thiscontroller
                            controllerKey = 200,
                            unitNumber = 0,
                     },
              },
              (vim.vm.device.VirtualDeviceSpec){
                     dynamicType =<unset>,
                     operation = “add”,
                     fileOperation =<unset>,
//SCSI controller
                     device =(vim.vm.device.VirtualLsiLogicController) {
                            dynamicType =<unset>,
// key num of the SCSIcontroller
                            key = -44,
                            deviceInfo =(vim.Description) null,
                            backing =(vim.vm.device.VirtualDevice.BackingInfo) null,
                            connectable =(vim.vm.device.VirtualDevice.Connection) null,
                            controllerKey =<unset>,
                            unitNumber =<unset>,
                            busNumber = 0,
                            hotAddRemove =<unset>,
                            sharedBus =“noSharing”,
                            scsiCtlrUnitNumber =<unset>,
                     },
              },
              (vim.vm.device.VirtualDeviceSpec){
                     dynamicType =<unset>,
                     operation = “add”,
                     fileOperation =<unset>,
//Network controller
                     divice =(vim.vm.device.VirtualPCNet32) {
                            dynamicType =<unset>,
       //key number of network controller
                                   key = -48,
                                   deviceInfo =(vim.Description) null,
                                   backing =(vim.vm.device.VirtualEthernetCard.NetworkBackingInfo) {
                                          dynamicType= <unset>,
                                          deviceName= “Virtual Machine Network”,
                                          useAutoDetect= <unset>,
                                          network= <unset>,
                                          inPassthroughMode= <unset>,
                                   },
                                   connectable =(vim.vm.device.VirtualDevice.ConnectInfo) {
                                          dynamicType= <unset>,
                                          startConected= true,
                                          allowGuestControl= true,
                                          connected= true,
                                   },
                                   controllerKey= <unsert>,
                                   unitNumber =<unset>,
                                   addressType =“generated”,
                                   macAddress =<unset>,
                                   wakeOnLanEnabled= true,
                            },
                     },
                     (vim.vm.device.VirtualDeviceSpec){
                            dynamicType =<unset>,
                            operation = “add”,
                            fileOperation =“create”,
              //SCSI disk one
                            device = (vim.vm.device.VirtualDisk){
                                   dynamicType =<unset>,
              //key number of the scsi disk one
                                   key =-1000000,
                                   deviceInfo =(vim.Description) null,
                                   backing =(vim.vm.device.VirtualDisk.FlatVer2BacingInfo) {
                                          dynamicType= <unset>,
                                          filename= “”,
                                          datastore= <unset>,
                                          diskMode= “persistent”,
                                          split= false,
                                          writeThrough= false,
                                          thinProvisioned= <unset>,
                                          eagerlyScrub= <unset>,
                                          uuid =<unset>,
                                          connectId= <unset>,
                                          changeId= <unset>,
                                          parent= (vim.vm.device.VirtualDisk.FlagVer2BackingInfo) null,
                                   },
                                   connectable =(vim.vm.device.VirtualDevice.ConnectInfo) {
                                          dynamicType= <unset>,
                                          startConected= true,
                                          allowGuestControl= false,
                                          connected= true,
                                   },
       //controller for scsi disk one
                                   controllerKey= -44,
                                   unitNumber =0,
       // sizte in MB scsi disk one
                                   capacityInKB= 52428,
                                   committedSpace= <unset>,
                                   shares =(vim.SharesInfo) null,
                            },
                     },
                     (vim.vm.device.VirtualDeviceSpec){
                            dynamicType =<unset>,
                            operation = “add”,
                            fileOperation =“create”,
       //scsi disk two
                            device =(vim.vm.device.VirtualDisk) {
                                   dynamicType =<unset>,
       //key num of scsi disk two
                                   key = -100,
                                   deviceInfo =(vim.Description) null,
                                   backing =(vim.vm.device.VirtualDisk.FlagVer2BackingInfo) {
                                          dynamicType= <unset>,
                                          filename= “”,
                                          datastore= <unset>,
                                          diskMode= “persistent”,
                                          split= false,
                                          writeThrough= false,
                                          thinProvisioned= <unset>,
                                          eagerlyScrub= <unset>,
                                          uuid =<unset>,
                                          connectId= <unset>,
                                          changeId= <unset>,
                                          connectable= (vim.vm.device.VirtualDevice.ConnectInfo) {
                                                 dynamicType= <unset>,
                                                 startConnected= true,
                                                 allowGuestControl= false,
                                                 connected= true,
                                          },
       //controller for scsi disk two
                                          controllerKey= -44,
                                          unitNumber= 1,
       //size in MB SCSI disk two
                                          capacityInKB= 131072,
                                          committedSpace= <unset>,
                                          shares= (vim.SharesInfo) null,
                                   },
                            },
                     },
                     cpuAllocation =(vim.ResourceAllocationInfo) {
                            dynamicType =<unset>,
                            reservation = 0,
                            expandableReservation= <unset>,
                            limit =<unset>,
                            shares =(vim.SharesInfo) {
                                   dynamicType =<unset>,
                                   shares = 100,
                                   level =“nomal”,
                            },
                            overheadLimit =<unset>,
                     },
                     memoryAllocation =(vim.ResourceAllocationInfo) {
                            dynamicType =<unset>,
                            reservation = 0,
                            expandableReservation= <unset>,
                            limit =<unset>,
                            shares =(vim.SharesInfo) {
                                   dynamicType =<unset>,
                                   shares = 100,
                                   level =“normal”,
                            },
                            overheadLimit =<unset>,
                     },
                     cpuAffinity =(vim.vm.AffinityInfo) null,
                     memoryAffinity =(vim.vm.AffinityInfo) null,
                     networkShaper = (vim.vm.NetworkShaperInfo)null,
                     swapPlacement =<unset>,
                     swapDirectory =<unset>,
                     preserveSwapOnPowerOff =<unset>,
                     bootOptions =(vim.vm.BootOptions) null,
                     appliance =(vim.vService.ConfigSpec) null,
                     ftInfo =(vim.vm.FaultToleranceConfigInfo) null,
                     applianceConfigRemoved =<unset>,
                     vAssertsEnabled =<unset>,
                     changeTranckingEnabled =<unset>,
              }
              //end of VirtualMachineConfigSpec

上面的信息非常複雜,但是很多輸入都是由系統指定的默認值。剩下的信息可以從config.hardware.device表中得到,這是由PropertyCollector返回的。從SDK示例代碼中借鑑如下代碼,建立配置細節:

// Duplicate virtualmachine configuration
VirtualMachineConfigSpecconfigSpec = new VirtualMachineConfigSpec();
// Set the VM values
configSpec.setName(“MyNew VM”);
configSpec.setVersion(“vmx-04”);
configSpec.setGuestId(“winNetStandardGuest”);
configSpec.setNumCPUs(1);
configSpec.setMemoryMB(256);
// Set up file storageinfo
VirtualMachineFileInfovmfi = new VirtualMachineFileInfo();
vmfi.setVmPathName(“[plat004-local]”);
configSpec.setFiles(vmfi);
vmfi.setSnapshotDirectory(“[plat004-local]”);
// Set up tools configinfo
ToolsConfigInfo tools= new ToolsConfigInfo();
configSpec.setTools(tools);
tools.setAfterPowerOn(newBoolean(true));
tools.setAfterResume(newBoolean(true));
tools.setBeforeGuestStandby(newBoolean(true));
tools.setBeforeGuestShutdown(newBoolean(true));
tools.setBeforeGuestReboot(newBoolean(true));
// Set flags
VirtualMachineInfoflags = new VirtualMachineFlagInfo();
configSpec.setFlags(flags);
flags.setSnapshotPowerOffBehavior(“powerOff”);
// Set power op info
VirtualMachineDefaultPowerOpInfopowerInfo = new VirtualMachineDefaultPowerOpInfo();
configSpec.setPowerOpInfo(powerInfo);
powerInfo.setPowerOffType(“preset”);
powerInfo.setSuspendType(“preset”);
powerInfo.setRetType(“preset”);
powerInfo.setStandbyAction(“poerOnSuspend”);
// Now add in thedevices
VirtualDeviceConfigSpec[]deviceConfigSpec = new VirtualDeviceConfigSpec[5];
configSpec.setDeviceChange(deviceConfigSpec);
// Formulate the CDROM
deviceConfigSpec[0].setOperation(VirtualDeviceConfigSpecOperation.add);
VirtualCdrom cdrom =new VirtualCdrom();
VirtualCdromIsoBacingInfocdDeviceBacking = new VirtualCdromRemotePassthroughBackingInfo();
cdDeviceBacking.setDatastore(datastoreRef);
cdrom.setBacking(cdDeviceBacking);
cdrom.setKey(-42);
cdrom.setControllerKey(newInteger(-200)); // Older java required type for optional properties
cdrom.setUnitNumer(newInteger(0));
deviceConfigSpec[0].setDevice(cdrom);
// Formulate the SCSIcontroller
deviceConfigSpec[1].setOperation(VirtualDeviceConfigSpecOperation.add);
VirtualLsiLogicControllerscsiCtrl = new VirtualLsiLogicController();
scsiCtrl.setBusnumber(0);
deviceConfigSpec[1].setDevice(scsiCtrl);
scsiCtrl.setKey(-44);
scsiCtrl.setSharedBus(VirtualSCSISharing.noSharing);
// Formulate SCSI diskone
deviceConfigSpec[2].setFileOperation(VirtualDeviceConfigSpecFileOperation.ceate);
deviceConfigSpec[2].setOperation(VirtualDeviceConfigSpecOperation.add);
VirtualDisk disk = newVirtualDisk();
VirtualDiskFlatVer2BackingInfodiskfileBacking = new VirtualDiskFlatVer2BacingInfo();
diskfileBacking.setDatastore(datastoreRef);
diskfileBacking.setFileName(volumeName);
diskfileBacking.setDiskMode(“persistent”);
diskfileBacing.setSplit(newBoolean(false));
diskfileBacking.setWriteThrough(newBoolean(false));
disk.setKey(-1000000);
disk.setCotrollerKey(newInteger(-44));
disk.setUnitNumber(newInteger(0));
disk.setBacking(diskfileBacking);
disk.setCapacityInKB(524288);
deviceConfigSpec[2].setDevice(disk);
// Formulate SCSI disktwo
deviceConfigSpec[3].setFileOperation(VirtualDeviceConfigSpecFileOperation.create);
deviceConfigSpec[3].setOperation(VirtualDeviceConfigSpecOperation.add);
VirtualDisk disk2=  new VirtualDisk(); 
VirtualDiskFlatVer2BackingInfodiskfileBacking2 = new VirtualDiskFlatVer2BackingInfo();
diskfileBacking2.setDatastore(datastoreRef);
diskfileBacking2.setFileName(volumeName);
diskfileBacking2.setDiskMode("persistent");
diskfileBacking2.setSplit(newBoolean(false));
diskfileBacking2.setWriteThrough(newBoolean(false)); 
disk2.setKey(-100);disk2.setControllerKey(new Integer(-44));
disk2.setUnitNumber(newInteger(1));
disk2.setBacking(diskfileBacking2);
disk2.setCapacityInKB(131072);
deviceConfigSpec[3].setDevice(disk2);
// Finally, formulatethe NIC
deviceConfigSpec[4].setOperation(VirtualDeviceConfigSpecOperation.add);
com.VMware.vim.VirtualEthernetCardnic =  new VirtualPCNet32(); 
VirtualEthernetCardNetworkBackingInfonicBacking = new VirtualEthernetCardNetworkBackingInfo();
nicBacking.setNetwork(networkRef);
nicBacking.setDeviceName(networkName);
nic.setAddressType("generated");
nic.setBacking(nicBacking);
nic.setKey(-48); 
deviceConfigSpec[4].setDevice(nic);
// Now that it is allput together, create the virtual machine. 
// Note that folderMo,resourcePool, and hostMo, are moRefs to the Folder, ResourcePool, and Host 
// where the VM is tobe created 
ManagedObjectReferencetaskMoRef = serviceConnection.getService().createVM_Task(folderMo, configSpec,resourcePool, hostMo);

使用VirtualMachineConfigInfo

備份程序還可以利用VirtualMachineConfigInfo包含的信息。如果備份保存了描述虛擬機的VirtualMachineConfigInfo的詳細信息,那麼還原還原時創建虛擬機時,就以可以傳遞更多信息到VirtualMachineConfigSpec中。然而,VirtualMachineConfigInfo中的一些信息則是不需要的,如果在Spec中使用的話,虛擬機創建就會失敗。例如,VirtualMachineConfigSpec中叫做“默認設備”包含的信息就會創建失敗。默認設備包括:

vim.vm.device.VirtualIDEController

vim.vm.device.VirtualPS2Controller

vim.vm.device.VirtualPCIController

vim.vm.device.VirtualSIOController

vim.vm.device.VirtualKeybord

vim.vm.device.VirtualVMCIDevice

vim.vm.device.VirtualPointingDevice

但是,其他的控制器和設備必須在VirtualMachineConfigSpec中指明。設備的一些信息不是必須的,並且如果提供了話可能會出問題。每個控制器都有一個vim.vm.device.VirtualController.device域,它是一個所有向該控制器報告的設備數組(which is an array ofdevices that report to the controller)。服務器在創建虛擬機時,使用提供的設備key編號(負數)重建這個數組。使用負數key值表示的控制器和設備的關係,必須和VirtualMachineConfigInfo中硬件數組中的關係一致(The relationship betweencontroller and device must be preserved using negative key numbers in the samerelationship as in the hardware array of VirtualMachineConfigInfo)

虛擬磁盤backing信息的父屬性必須設置爲null。在創建虛擬機的示例代碼中,可查看vim.vm.device.VirtualDisk.FlatVer2BacingInfo。需要設置爲null,因爲備份前快照導致了父屬性指向了基本磁盤(The null setting isrequired because the pre-backup snapshot causes the parent property to bepopulated with a reference to the base disk)

其他一些配置信息需要被替代。VirtualMachineConfigInfo包含cpuFeatureMask域,它是一組HostCpuIdInfo。這些數組項需要轉換爲包含VirtualMachineCpuIdInfoSpecArrayUpdateSpec項,轉換時使用一個包含ArrayUpdateOperation::add值得“operation”域(The array entries must beconverted to ArrayUpdateSpec entries containg the VirtualMachineCpuIdInfoSpecalong with the “operation” field, which must contain the valueArrayUpdateOperation::add)VirtualMachineCpuIdInfoSpec還包含一個HostCupIdInfo數組,可以從VirtualMachineConfigInfocpuFeatureMask數組複製得到。這在示例代碼中沒有反映到。

其他所有信息都可以從VirtualMachineConfigInfo數據中獲得。

總結:還原虛擬磁盤創建虛擬機時:

VirtualMachineConfigSpec中排除默認設備以及VirtualController.device

將父虛擬磁盤backing信息(VirtualDisk.FlatVer2BackingInfo)設置爲null

HostCpuIdInfo數組項轉換爲ArrayUpdateSpec項,插入ArrayUpdateOperation::add,並從VirtualMachineConfigInfo中複製cupFeatureMaskHostCupIdInfo

編輯、刪除設備

如果備份客戶端想要刪除或編輯設備,它們需要服務器提供的key來引用已存在的設備。對於key的定義,請查看“創建虛擬機”一節。例如,在源代碼中CDROMkeyControllerKey的值。key唯一標識了一個設備,而controllerKey則唯一標識了它所連接的控制器。

還原虛擬磁盤數據

“還原過程的底層實現”中,VixDiskLib函數提供的接口,能夠在本地或遠程將數據寫入到虛擬磁盤中。

原始設備映射(RDM)磁盤

使用CreateVM_Task創建RDM磁盤時,用到了一個沒有被佔用且可用的LUN。開發者有時會使用configInfo對象中的LUN uuid,這可能導致錯誤,因爲LUN uuid是數據存儲指定的。

調用QueryConfigTarget獲得ConfigTarget.ScsiDisk.Disk.CanonicalName屬性,設置到VirtualDiskRawDiskMappingVer1BackInfo.deviceName屬性中。調用QueryConfgiTarget獲得ConfigTarget.ScsiDisk.Disk.uuid並設置到VirtualDiskRawDiskMappingVer1BackInfo.lunUuid屬性中。創建虛擬機時,避免使用configInfo中的主機相關的屬性,這些屬性需要設置爲當前正在還原的主機的相關配置。

還原增量備份數據

有時你可能需要還原“虛擬磁盤修改塊跟蹤”一節中獲取的虛擬磁盤數據。主要的步驟包括:

1 關閉虛擬機。

2 使用客戶機操作系統最後一個狀態的VirtualMachineConfigInfo來創建虛擬機,正如“使用VirtualMachineConfigInfo”一節中描述的那樣。

3 使用完全備份重新還原基本虛擬磁盤。

4 創建一個快照,這在SAN模式下還原是必須的。

5 對於SAN模式還原,禁用修改塊跟蹤,當啓用時SAN寫操作時不可用的。

6 依次還原增量備份數據。你可以向前或向後還原。如果你向前還原,還原時一些扇區可能不止寫一次。如果你向後還原,必須記錄哪些扇區已經還原過了,以避免再次寫入舊的數據。

       a 從備份記錄中獲取將要還原的增量備份的changeId。你的軟件同樣必須保存修改塊信息,這樣就可以知道要還原虛擬磁盤的哪些扇區。一旦開始還原虛擬磁盤,修改跟蹤機制就會誤報。

       b 僅還原快照標識的修改區域到虛擬磁盤(Restore only changed areas to the virtual diskreferred to by the snapshot)。這會保證不會將數據寫到快照創建的重寫日誌中。當還原一個薄的置備(稀疏)磁盤(thin provisioned(sparse) disk),使用星號“*”修改ID避免向未分配塊寫入零值。

       c 重複步驟a和步驟b依次還原增量數據。

7 如果可以的話,恢復到基本虛擬磁盤,從而消除了快照。

ESXi主機直連還原失敗

有時你必須直接在ESXi主機上還原虛擬機,例如當vCenter服務器在ESXi上作爲一個虛擬機運行時進行的災難恢復。vSphere5有一個新的特性,就是當ESXi主機由vCenter管理時,就會阻止這種操作。爲了規避這點以還原虛擬機,必須切斷主機和vCenter之間的關聯。在早期的版本中,vCenter管理保存了很少的狀態,但是可以從vCenter撤銷(vCenter management had lessstate but was revocable only from vCenter)

1使用vSphere客戶端直接連接到ESXi5.0或更新版本的主機。

2在存儲庫(Inventory)左邊欄中,選擇主機,在右邊選擇“簡介(Summary)”。

3在標題爲“主機管理(Host Management)”的對話框中,選擇“取消主機到vCenter服務器的關聯”。你不需要將虛擬機置爲維護模式。

4vCenter服務器還原成功並運行後,使用它重新獲取主機。

當前並沒有取消主機和vCenter服務器之間的關聯的API

技巧和最佳實踐

VDDK5.0包含兩個新的VixDiskLib調用(PrepareForAccess以及EndAccess),用來在備份時禁用和啓用存儲遷移。這避免了在執行備份時,虛擬機移動了它的存儲導致舊的磁盤鏡像被遺留了下來(This prevent staledisk images from being left behind if a virtual machine has its storage movedwhile a backup is taking palce)VMware強烈建議使用這兩個調用。

ESX/ESXi主機由vCenter服務器管理時,vSphere API不能直接和主機聯繫,它們必須通過vCenter。如果需要的話,特別是在災難恢復時,在能夠直接和主機進行通信前,必須取消ESXi主機和vCenter服務器之間的關聯。

高級傳輸允許程序以最高效的方式傳輸數據。SAN模式只有在物理機有SAN訪問時纔可用。HotAdd在應用模式下可用,備份在虛擬機內部完成。HotAdd要求虛擬機的數據存儲能夠被備份應用訪問。當你只能選擇通過網絡進行備份時,NBDSSL是一個安全備選。

SAN傳輸僅在物理機上支持,而HotAdd傳輸只有虛擬機能支持。SAN需要一個物理代理和ESXi主機共享一個數據存儲依賴的LUN,使得能夠直接訪問原始的數據,並繞過主機的I/O操作。HotAdd需要將一個虛擬磁盤附加到備份代理,就像將一個磁盤附加到虛擬機一樣。

SAN傳輸的最佳實踐

對於基於陣列的存儲,SAN傳輸通常都是運行在物理代理上的備份程序的最佳性能選擇。在虛擬機內部是被禁用的,所以在虛擬代理上使用SCSI HotAdd代替。

SAN傳輸並不總是還原的最佳選擇。它在厚磁盤(thick disks)上提供最佳性能,但是在薄磁盤(thin disks)上具有最差的性能,原因是磁盤管理API的往返,AllocateBlock以及ClearLazyZero。對於薄磁盤的還原,NBDSSL通常更快,並且NBD更快。SAN還原時必須禁用修改塊跟蹤(CBT)SAN傳輸不支持寫入重寫日誌(快照或子磁盤),僅能還原基本磁盤。

vSphere5.5以前,還原寫入SAN時,磁盤大小必須是底層VMFS塊大小的整數倍,否足寫入磁盤的最後一塊將會失敗。例如,如果虛擬磁盤的塊大小是1MB,而數據存儲是16.3MB,最後0.3MB會寫入失敗,除非還原軟件填充0.7MB的零值。這在ESXi5.5這種已經修改了。

SAN模式中打開一個本地虛擬磁盤,可能讀沒有問題(磁盤爲空),但是寫會拋出錯誤。儘管程序使用NULL作爲參數調用VixDiskLib_ConnectEx()以接受默認的傳輸模式,但是如果ESXi主機連接了SAN存儲,就會選擇SAN模式。VixDiskLib應該但是不會檢查SAN在打開時是否可用。對於本地磁盤,程序必須顯式指明要求NBDNBDSSL模式。

HotAdd傳輸的最佳實踐

將代理部署到VMFS-5卷或大塊的VMFS-3捲上,可以備份非常大的虛擬磁盤(Deploy the proxy on VMFS-5volumes, or on VMFS-3 volumes capable of large block size so that the proxy canbackup very large virtual disks)

在基本磁盤相同的數據存儲上,將會爲HotAdded磁盤創建重寫日誌。當HotAdded磁盤仍然處於附加狀態時,不要移除目標虛擬機(正在備份的虛擬機)。如果移除掉的話,HotAdd無法正確清除重寫日誌,所以虛擬磁盤必須從備份應用中手動移除。在清除之前,也不能移除快照。刪除快照會導致未合併的重寫日誌。

HotAdd是一個SCSI特性,不適用於IDE磁盤。半虛擬化的SCSI控制器(PVSCSI)不支持HotAdd,使用LSI控制器代替。

通過vSphere客戶端已從控制器上的所有磁盤,同樣也會移除控制器。你可能需要在代碼包含一些檢查並檢測這些,並重新配置以添加控制器。

HotAdd備份或還原在Windows上創建的虛擬磁盤需要有和原始磁盤不同的磁盤簽名。NBD模式下在後臺重新讀入和寫入磁盤的第一個扇區。

在刪除快照之前,需要使用VixDiskLib_Cleanup()釋放HotAdded磁盤。清除可能導致修改跟蹤(ctk)文件的不當移除,你可以通過重新啓動虛擬機修復這個問題。

SAN存儲上運行WindowsServer2008代理的客戶,需要將SAN策略設置爲onlineAll

NBDSSL傳輸的最佳實踐

不同版本的ESX/ESXi有不同的默認超時。在ESXi5.0以前沒有默認的網絡文件拷貝(NFC)超時,默認的NFC超時值在未來的版本會可能發生變化。

VMware建議在VixDiskLib配置文件中指明默認的NFC超時。如果你沒有設置一個超時,老版本的ESX/ESXi會無限期的保持磁盤打開狀態,知道vpxa或者hostd被重啓。但是如果你設置了一個超時值,你就需要執行一些“keepalive”操作,避免磁盤在服務器端被關閉了。定期的讀取第0塊是一種比較好的保持連接的操作。

開始時,建議設置接受和請求的超時爲3分鐘,讀爲1分鐘,寫爲10分鐘,nfcF***vrnfcF***vrWrite沒有超時(0)

通用備份和還原

對於虛擬磁盤的增量備份,通常需要在第一個快照之前啓用修改塊跟蹤(CBT)。當進行虛擬磁盤的完全還原時,在還原期間需要禁用CBT。基於文件的還原影響修改塊跟蹤,但是部分還原時禁用CBT是可選的,除非是SAN傳輸。SAN傳輸寫時需要禁用CBT,因爲文件系統需要統計薄磁盤(thin-disk)的分配以及延遲清零(clear-lazy-zero)操作。

備份軟件需要忽略無法快照的獨立磁盤,這些磁盤不適用於備份。如果創建快照,它們會拋出錯誤。

要備份一個厚磁盤(thick disk),代理的數據存儲必須有足夠的可用空間,存儲備份虛擬機的最大配置磁盤大小。薄置備磁盤(thin-provisioneddisk)通常備份很快。厚磁盤在數據存儲中佔用了所有的分配大小。爲了節省空間,你可以選擇薄置備(think-provisioned)磁盤,它只消耗實際包含數據的空間。

對於vSphere5.1及以後版本中的SSL認證檢查,備份代理中必須配置DNS服務,否則SSL_Verify就會返回“找不到主機”的錯誤。

如果你針對禁用CBT的延遲清零的厚磁盤執行完全備份,軟件讀取所有扇區,將空扇區(延遲清零,lazy-zero)中的數據轉換爲真正的零值。還原時,這個完全備份的數據就會產生貪婪清零(eager-zeroed)的厚磁盤。這也是VMware推薦在第一個快照前啓用CBT的一個原因。

備份、還原薄置備(Thin-Provisioned)磁盤

薄置備磁盤在第一次寫時創建。相比於厚磁盤,針對薄置備磁盤的第一次寫操作會導致額外的開銷,不論是使用NBDNBDSSL,或者HotAdd。這是由於塊分配的開銷,而不是VDDK高級傳輸的開銷。但是一旦薄置備磁盤創建成功,性能和厚磁盤相似。

當程序對薄置備磁盤上的未分配區域執行隨機的I/O或寫操作時,後續的備份可能比期望的大,儘管CBT已經啓用。有時,磁盤整理可能會減少備份的大小。

虛擬機配置

不要逐字拷貝配置文件,它可能發生改變。例如,.vmx文件的項指向了快照,而不是基本磁盤。.vmx文件包含了虛擬機當前磁盤相關的信息,嘗試還原這個信息可能會失敗。需要使用PropertyCollector並保存ConfigInfo結構。

關於修改塊跟蹤

QueryChangedDiskAreas(“*”)返回虛擬磁盤被使用、分配的區域。當前的實現依賴於VMFS的屬性,類似於SAN傳輸模式用來定位SCSI LUN上的數據的屬性。依賴於虛擬磁盤的未分配區域(file holes),以及VMFS塊的延遲清零定義(designation)。因此,修改塊跟蹤只有在VMFS上產生有意義的結果。在其他存儲類型上,它要麼失敗,要麼返回包含所有磁盤的單個內容。

你應該按照“啓用修改塊跟蹤”一節中講到的順序啓用修改塊跟蹤。第一次調用QueryChangedDiskAreas(“*”)時,它會返回虛擬磁盤上已經分配的區域。後續的調用返回修改的區域,而不是已分配區域。如果在啓用修改塊跟蹤之前,在一個快照後調用QueryChangedDiskAreas,它同樣會返回虛擬磁盤的未分配區域。對於薄置備磁盤,這會包含大量的零值數據。

客戶端系統並不知道修改塊跟蹤的存在。當虛擬機向虛擬磁盤寫入一塊數據時,這個塊就被認爲正在使用。如果修改塊跟蹤已經啓用,“*”請求返回的信息會被計算,並且.ctk文件已經被已分配的塊填充。這種機制在修改塊跟蹤被啓用前不能報告針對虛擬磁盤所在的修改。

WindowsLinux實現

以下的章節討論的一些問題,依賴於虛擬機是運行Windows還是Linux

使用Microsoft陰影拷貝

Microsoft陰影拷貝,也叫做卷快照服務(Volume Snapshot Service, VSS),是一個Windows服務器的數據備份特性,用來創建數據在指定時間點的一致性副本(叫做陰影拷貝)

創建Windows虛擬機快照時執行VSS靜止操作(Performing VSS quiescing)VMware工具就會生成一個vss-manifest.zip文件,包含備份組件文檔(BCD)writer清單。主機代理會將這個清單文件保存到虛擬機的snapshotDir中。備份程序需要獲得這個vss-mainfest.zip文件並保存到備份介質中。有幾種方式可以獲得這個文件:

  • 使用數據存儲的HTTPS訪問協議,例如,通過瀏覽到https://<server-or-host>/folder/並繼續進入快照目錄,直到發現vss-mainfest.zip文件。

  • 調用vSphereAPI中的CopyDatastoreFile_Task方法。該方法使用上面組成的HTTPS URL或者一個數據存儲路徑。(CopyVirtualDisk_Task針對VMDK文件。)

  • vMAvCLI中使用vifs命令行

  • PowerCLI中使用Copy-DatastoreItem命令(需要PowerShell以及VMware組件)

使用的靜止類型(type of quiescing)依賴於備份虛擬機的操作系統,如表7-4所示。ESX/ESXi4.1使用應用層的靜止來支持Windows2008(ESX/ESXi 4.1 addedsupport for Windows 2008 guests using application level quiescing)

                             wKioL1PsKYChrZFnAAHH3Xpny1U521.jpg

還原必須使用備份應用的客戶端代理來完成。數據保護的vSphere API沒有提供主機代理來支持這種應用。使用SSPI認證的應用無法正確工作,因爲HTTP訪問需要一個用戶名和密碼,除非會話最近已經認證過了。

Windows2008應用層靜止使用硬件快照提供者執行。虛擬機靜止後,硬件快照提供者針對每個磁盤創建了兩個重寫日誌:一個用於運行的虛擬機的寫操作,另一個用於客戶機中的VSSwriters在快照操作後修改磁盤(another for the VSS and writersin the guest to modify the disks after the snapshot operation as part of thequiescing operations)

快照配置信息將第二個重寫日誌作爲快照的一部分。這個重寫日誌記錄了客戶機中所有應用程序的靜態狀態(This redo logrepresented the quiesced state of all the applications in the guest)。備份時必須使用VDDK1.2或更新版本才能打開這個重寫日誌。舊的VDDK1.1軟件不能打開第二個重寫日誌進行備份。

Windows2008虛擬機上的應用程序的一致性(application consistent quiescing),僅在這些寫虛擬機由vSphere4.1或更新版本中創建時才起作用。vSphere4.0創建的虛擬機通過修改虛擬機的enableUUID屬性,可以啓用應用程序的一致性。

關於VSS的信息,可以查看Microsoft TechNet文章,《How Volume Shadow Copy ServiceWorks》。關於安全支持提供者接口(SSPI)的信息,可以查看MSDN網站。

啓用Windows2008虛擬機應用程序一致性

1打開vSphere客戶端,並登陸到vCenter Server

2選擇“虛擬機和模板”,並點擊“虛擬機”選項卡。

3右鍵點擊你將要設置磁盤UUID屬性的Windows2008虛擬機,選擇“電源 > 關閉”。等待虛擬機關閉。

4右鍵點擊虛擬機,選擇“編輯設置”。

5點擊“選項”欄,選擇“通用”入口。

6點擊“配置參數”,將會出現配置參數窗口。

7點擊“添加行”。

8在名稱列中填入“disk.EnableUUID”,在值列中寫入“TRUE”。

9點擊“確定”並“保存”。

10打開虛擬機電源。

UUID屬性啓用後,虛擬機的應用程序一致性就可用了。

備份和還原的程序一致性

下面是執行應用一致性備份和還原的近似過程:

1調用CreateSnapshot_Task函數,並設置quiescent標識爲true

2使用VDDK打開磁盤的葉節點,讀取基本VMD和快照數據。

3刪除第一步中創建的快照。

4還原時,創建新的虛擬機。

5使用VDDK將數據邪惡如VMDK磁盤。它包含基本和靜態信息。

備份時,如果將quiesce標識設置爲TRUE並創建快照,那麼所有的靜態條件都滿足了,快照創建時調用了VSS,快照磁盤呈現了客戶機系統的應用程序的一致性狀態。你可以通過以下操作來確認這點:下載並解壓VSS manifest.zip文件來檢查是否備份了組件文檔(在這種情況下文件系統執行了靜默操作(file sysem quiescingwas performed))或者同樣檢查writer mainfests文件(這時執行了應用程序的靜默操作(application quiescing wasperformed))

靜默操作(quiescing)調用了Microsoft設計的VSS機制,關於VSS備份、還原的驗證,可以參考Microsoft提供的VSS文檔。VMware提供一個vss-manifest.zip文件,包含了備份、writer組件的詳細信息。這是備份後VSS機制生成的文件。根據Microsoft VSS文檔驗證這些備份/writer組件的細節,你可以驗證一個具體的應用程序一致性操作是否成功執行。

VMware工具作爲VSS請求者負責初始化VSS快照。用戶發送一個請求到主機(hostd),要求虛擬機的靜默快照。這個請求通過主機傳遞到VMware工具並執行VSS快照。VSS快照完成後,無論失敗還是成功,都將會和主機進程進行通信。VSS快照創建時會生成vss-mainfest文件,或者出錯時沒有該文件。

VSS請求者建立備份操作的所有配置信息,包括快照是否在組件模式下執行,快照是否包含可啓動的系統狀態,以及快照是用於完全備份還是差異備份。如果執行應用程序一致性操作,將會涉及所有的writer和組件。

VMware VSS的實現

Windows Server 2008上,磁盤UUID必須啓用,才能創建VSS靜默快照。如果虛擬機從硬件版本4升級而來,磁盤UUID也可能沒有啓用。

VMwareVSS不支持使用IDE磁盤的虛擬機,也不支持沒有可用SCSI插槽(with an insufficient number offree SCSI slots)的虛擬機。

vSphere5.1之前,恢復到一個可寫的快照,有時會留下系統沒有移除的孤立虛擬磁盤。在vSphere5.1版本中,可寫快照作爲兄弟快照被正確的記錄(writable snapshotsare corredcly accounted for as sibling snapshots)。這允許更乾淨的管理,因爲磁盤鏈匹配快照結構,並避免了孤立磁盤。Linux備份軟件創建只讀的快照,所有它並不受影響。在Windows上,VSS備份軟件可能創建連個快照,一個通過設置quiesce標識爲true調用CreateSnapshot_Task創建爲可寫快照。

要添加更新的應用程序控制,需要指明:

  • 是否調用冷凍和解凍(pre-freeze and post-thaw)腳本。

  • 是否是靜默操作(quiescing)

  • VSS快照上下文(應用程序,文件系統靜默)

  • VSS備份上下文(完全,差異,增量)

  • 靜默操作時涉及的writer和組件

  • 當一個writer靜默操作失敗時,退出或繼續靜默操作

  • 重試次數

A VSS quiesced snapshot reports as VSS_BT_COPY to VSS, hence no log truncationVSS manifest文件可以通過HTTP下載。默認情況下,所有VSS writer都會涉及,但是也存在排除已存在writer的機制。

Linux HotAdd以及SCSI控制器ID

當使用HotAdd備份時,通常按數字順序向Linux虛擬機添加SCSI控制器。

Linux系統缺乏一個接口來通知SCSI控制器被分配到哪個總線ID,所以HotAdd假設SCSI控制器的唯一ID和它的總線ID相關。這個假設可能不成立。例如,如果Linux虛擬機的第一個SCSI控制器非配到總線ID 0,但是你添加了一個SCSI控制器並將它分配到總線ID 3HotAdd高級傳輸模式可能會失敗,因爲它期望唯一ID1。爲避免出現問題,當向虛擬機添加SCSI控制器時,必須按順序分配下一個可用的總線編號。

還需要注意的是,如果新添加的虛擬磁盤引用了一個還不存在的控制器,VMware爲隱式的添加一個SCSI控制器來完成bus:disk分配。例如,如果磁盤0:00:1已經存在,添加一個磁盤1:0沒有問題,但是添加磁盤3:0就會打破總線ID的順序,隱式的創建了一個順序外的SCSI控制器3.爲了避免HotAdd問題,還需要按數字順序添加虛擬磁盤。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章