还原过程的底层实现
还原虚拟机和磁盘
无法对正在使用的虚拟磁盘进行写操作。对于完全还原,你必须通过停止虚拟机并关闭电源,确保虚拟磁盘没有被占用。下面的代码演示了如何关闭虚拟机:
// 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。这些数组项需要转换为包含VirtualMachineCpuIdInfoSpec的ArrayUpdateSpec项,转换时使用一个包含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数组,可以从VirtualMachineConfigInfo的cpuFeatureMask数组复制得到。这在示例代码中没有反映到。
其他所有信息都可以从VirtualMachineConfigInfo数据中获得。
总结:还原虚拟磁盘创建虚拟机时:
从VirtualMachineConfigSpec中排除默认设备以及VirtualController.device。
将父虚拟磁盘backing信息(VirtualDisk.FlatVer2BackingInfo)设置为null
将HostCpuIdInfo数组项转换为ArrayUpdateSpec项,插入ArrayUpdateOperation::add,并从VirtualMachineConfigInfo中复制cupFeatureMask到HostCupIdInfo。
编辑、删除设备
如果备份客户端想要删除或编辑设备,它们需要服务器提供的key来引用已存在的设备。对于key的定义,请查看“创建虚拟机”一节。例如,在源代码中CDROM的key和ControllerKey的值。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服务器的关联”。你不需要将虚拟机置为维护模式。
4当vCenter服务器还原成功并运行后,使用它重新获取主机。
当前并没有取消主机和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在打开时是否可用。对于本地磁盘,程序必须显式指明要求NBD或NBDSSL模式。
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***vr和nfcF***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)磁盘
薄置备磁盘在第一次写时创建。相比于厚磁盘,针对薄置备磁盘的第一次写操作会导致额外的开销,不论是使用NBD,NBDSSL,或者HotAdd。这是由于块分配的开销,而不是VDDK高级传输的开销。但是一旦薄置备磁盘创建成功,性能和厚磁盘相似。
当程序对薄置备磁盘上的未分配区域执行随机的I/O或写操作时,后续的备份可能比期望的大,尽管CBT已经启用。有时,磁盘整理可能会减少备份的大小。
虚拟机配置
不要逐字拷贝配置文件,它可能发生改变。例如,.vmx文件的项指向了快照,而不是基本磁盘。.vmx文件包含了虚拟机当前磁盘相关的信息,尝试还原这个信息可能会失败。需要使用PropertyCollector并保存ConfigInfo结构。
关于修改块跟踪
QueryChangedDiskAreas(“*”)返回虚拟磁盘被使用、分配的区域。当前的实现依赖于VMFS的属性,类似于SAN传输模式用来定位SCSI LUN上的数据的属性。依赖于虚拟磁盘的未分配区域(file holes),以及VMFS块的延迟清零定义(designation)。因此,修改块跟踪只有在VMFS上产生有意义的结果。在其他存储类型上,它要么失败,要么返回包含所有磁盘的单个内容。
你应该按照“启用修改块跟踪”一节中讲到的顺序启用修改块跟踪。第一次调用QueryChangedDiskAreas(“*”)时,它会返回虚拟磁盘上已经分配的区域。后续的调用返回修改的区域,而不是已分配区域。如果在启用修改块跟踪之前,在一个快照后调用QueryChangedDiskAreas,它同样会返回虚拟磁盘的未分配区域。对于薄置备磁盘,这会包含大量的零值数据。
客户端系统并不知道修改块跟踪的存在。当虚拟机向虚拟磁盘写入一块数据时,这个块就被认为正在使用。如果修改块跟踪已经启用,“*”请求返回的信息会被计算,并且.ctk文件已经被已分配的块填充。这种机制在修改块跟踪被启用前不能报告针对虚拟磁盘所在的修改。
Windows和Linux实现
以下的章节讨论的一些问题,依赖于虚拟机是运行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文件。)
在vMA或vCLI中使用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)。
还原必须使用备份应用的客户端代理来完成。数据保护的vSphere API没有提供主机代理来支持这种应用。使用SSPI认证的应用无法正确工作,因为HTTP访问需要一个用户名和密码,除非会话最近已经认证过了。
Windows2008应用层静止使用硬件快照提供者执行。虚拟机静止后,硬件快照提供者针对每个磁盘创建了两个重写日志:一个用于运行的虚拟机的写操作,另一个用于客户机中的VSS和writers在快照操作后修改磁盘(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 truncation。VSS 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 3,HotAdd高级传输模式可能会失败,因为它期望唯一ID为1。为避免出现问题,当向虚拟机添加SCSI控制器时,必须按顺序分配下一个可用的总线编号。
还需要注意的是,如果新添加的虚拟磁盘引用了一个还不存在的控制器,VMware为隐式的添加一个SCSI控制器来完成bus:disk分配。例如,如果磁盘0:0和0:1已经存在,添加一个磁盘1:0没有问题,但是添加磁盘3:0就会打破总线ID的顺序,隐式的创建了一个顺序外的SCSI控制器3.为了避免HotAdd问题,还需要按数字顺序添加虚拟磁盘。