还原过程的底层实现

还原虚拟机和磁盘

无法对正在使用的虚拟磁盘进行写操作。对于完全还原,你必须通过停止虚拟机并关闭电源,确保虚拟磁盘没有被占用。下面的代码演示了如何关闭虚拟机:

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

                             VMware虚拟磁盘编程指导(九)_第1张图片

还原必须使用备份应用的客户端代理来完成。数据保护的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问题,还需要按数字顺序添加虚拟磁盘。