zoukankan      html  css  js  c++  java
  • 通过VMware vCenter利用脚本备份VM

    This artical will be published in English as well: http://www.cnblogs.com/LarryAtCNBlog/p/4613320.html

    如果你的环境里刚好有VMware vCenter,里面加了一堆ESX(i)服务器的话,那以下的脚本是有帮助的。

    vCenter本身是有计划备份的功能的,但是比较遗憾的是功能并不能满足我使用的需求,我也更偏向于自己做脚本控制。

    以下是tree的一个sample输出,脚本只有3个,都以ps1结尾。

    │  Backup-VM.ps1
    │  Starter.ps1
    │
    └─vCenter01
            _Configuration.ps1

    下面是脚本的作用描述,

    Starter.ps1 - 由task scheduler调用,在其所属的目录搜索所有_Configuration.ps1文件,然后异步调用Backup-VM.ps1,将_Configuration.ps1的目录传入
    Backup-VM.ps1 - 接收唯一参数-vCenterFolder,指向_Configuration.ps1所在的文件夹
    _Configuration.ps1 - 配置文件,可以有多个,依据其配置文件不同指向不同的vCenter server,备份不同的VM,多个vCenter的job建多个_Configuration.ps1即可

    下面是脚本内容及解释

    Starter.ps1

    # 进入script所在的文件夹
    Set-Location (Get-Item $MyInvocation.MyCommand.Definition).Directory
    
    # 搜索目录下所有的_Configuration.ps1文件
    Get-ChildItem -Filter '_Configuration.ps1' -Recurse | %{
        # 调用Backup-VM.ps1,将_Configuration.ps1的目录传入
        Start-Process -FilePath 'powershell.exe' -ArgumentList @('-File', 'Backup-VM.ps1', '-vCenterFolder', "`"$($_.DirectoryName)`"")
    }

    _Configuration.ps1

    # 该_Configuration.ps1是否启用,置成$false脚本则不会执行
    $Enable = $true
    
    # 下面只是一个VM配备格式的样本
    # VM表示该VM在vCenter里的名字
    # Host表示要把该VM往哪个ESX(i)上备份
    # Datastore表示目标ESX(i)上的storage名称
    <#
    Sample:
    $Entries = @(
    @{VM = 'VMName01'; Host = 'TargetESXiServerName02'; Datastore = 'TargetESXiServerName02:storage1'; Reserve = 1;},
    @{VM = 'VMName02'; Host = 'TargetESXiServerName01'; Datastore = 'TargetESXiServerName01:storage2'; Reserve = 1;}
    )
    #>
    
    # 实际数据填在下面,sample如上
    $Entries = @(
    
    )
    
    # vCenter服务器名或ip地址
    $vCenter = 'vCenter01'
    
    # 邮件设置部分,只有当出现需要关注的错误时,邮件部分才会被触发,正常备份完成不会触发
    $From =  "$($env:COMPUTERNAME)@test.com"
    $To = "AlertNeedsToSend@test.com"
    $Subject = "VMs backup completed with errors - $vCenter"
    $SmtpServer = 'mailgateway'

    Backup-VM.ps1 - 自动判断VM的网络,如果使用vSphere Distributed Switch,会切换成API备份,如果是普通的vSphere Standard Switch网络,用New-VM命令即可完成备份。(使用VDS后,New-VM命令会失败,除非目标ESXi服务器上的网络配置和当前ESXi包括同样的VDS)

    PARAM(
        [parameter(Mandatory=$true)]
        [string]$vCenterFolder
    )
    
    # 进入_Configuration.ps1所在的目录
    Set-Location -Path $vCenterFolder
    
    $Date = Get-Date
    $strDate = $Date.ToString("yyyy-MM-dd")
    $strLogFile = "${strDate}.log"
    
    # 载入_Configuration.ps1里的variable
    . '.\_Configuration.ps1'
    
    # 定义一个写日志的函数
    function Add-Log
    {
        PARAM(
            [String]$Path,
            [String]$Value,
            [String]$Type = 'Info'
        )
        $Type = $Type.ToUpper()
        $Date = Get-Date
        Write-Host "$($Date.ToString('[HH:mm:ss] '))[$Type] $Value" -ForegroundColor $(
            switch($Type)
            {
                'WARNING' {'Yellow'}
                'Error' {'Red'}
                default {'White'}
            }
        )
        if($Path){
            Add-Content -LiteralPath $Path -Value "$($Date.ToString('[HH:mm:ss] '))[$Type] $Value" -ErrorAction:SilentlyContinue
        }
    }
    
    Add-Log -Path $strLogFile -Value 'New backup started'
    # 判断该_Configuration.ps1是否配置为启用
    if(!$Enable)
    {
        Add-Log -Path $strLogFile -Value 'Repository disabled'
        exit
    }
    
    # $vCenter是必要信息,没有它脚本没有办法执行
    if(!$vCenter)
    {
        Add-Log -Path $strLogFile -Value 'vCenter variable is null, can not continue' -Type Error
        $Alert = $true
    }
    else
    {
        Add-Log -Path $strLogFile -Value "vCenter: [$vCenter]"
    }
    
    # 找必要的snapin,早期版本的PowerCli没有VDS的操作snapin,显示一些提示以确保运行环境正常
    if(!(Get-PSSnapin -Name '*VMware.VimAutomation.Vds*' -Registered -ErrorAction:SilentlyContinue))
    {
        Add-Log -Path $strLogFile -Value 'This script is built from [VMware vSphere PowerCLI 5.5], suggest to run on the version' -Type Error
        Add-Log -Path $strLogFile -Value 'PSSnapin [VMware.VimAutomation.Vds] is not found, which could cause backup failure' -Type Error
        Add-Log -Path $strLogFile -Value 'Installer path: [\ServerVMwarevSphereVMware-PowerCLI-5.5.0-1295336.exe]' -Type Info
        exit
    }
    
    # 载入snapin
    if(!(Get-PSSnapin '*vmware*' -ErrorAction:SilentlyContinue))
    {
        Add-PSSnapin *vmware*
        if(!$?)
        {
            Add-Log -Path $strLogFile -Value 'Failed to add vmware pssnapin' -Type Error
            Add-Log -Path $strLogFile -Value $Error[0] -Type Error
            exit
        }
    }
    
    # 连接到vCenter,假如出现错误,把$Alert置成$true为最后发出告警邮件
    Connect-VIServer -Server $vCenter -Force
    if(!$?)
    {
        Add-Log -Path $strLogFile -Value 'Failed to connect to vCenter, cause:' -Type Error
        Add-Log -Path $strLogFile -Value $Error[0] -Type Error
        $Alert = $true
    }
    
    $Tasks = @()
    # 对每一项配置的VM备份做循环
    foreach($e in $Entries)
    {
        Add-Log -Path $strLogFile -Value "Start doing backup for: [$($e.VM)]"
        # 添加新的VM名称为旧名+_ScriptBackup_+当时的日期
        $VMNew = "$($e.VM)_ScriptBackup_$strDate"
        $e.NewVM = $VMNew
        $VM = $null
        $VM = @(Get-VM -Name $e.VM -ErrorAction:SilentlyContinue)
        if(!$VM)
        {
            Add-Log -Path $strLogFile -Value 'Capture none VM, does the VM exists?' -Type Warning
            continue
        }
        if($VM.Count -ge 2)
        {
            Add-Log -Path $strLogFile -Value "Capture [$($VM.Count)] VM, duplicated VMs?: [$(($VM | %{$_.Id}) -join '], [')]" -Type Warning
            continue
        }
        $VM = $VM[0]
    
        # 执行到此处说明VM在vCenter里存在并唯一,脚本没有抓到多个VM
        $VMHost = $null
        $VMHost = @(Get-VMHost -Name $e.Host -ErrorAction:SilentlyContinue)
        if(!$VMHost)
        {
            Add-Log -Path $strLogFile -Value "Capture none VMHost, does the VMHost exists?: [$($e.Host)]" -Type Warning
            continue
        }
        if($VMHost.Count -ge 2)
        {
            Add-Log -Path $strLogFile -Value "Capture [$($VMHost.Count)] VMHost, duplicated VMHosts?: [$(($VMHost | %{$_.Id}) -join '], [')]" -Type Warning
            continue
        }
        $VMHost = $VMHost[0]
    
        # 执行到此处说明VMHost在vCenter里存在并唯一,脚本没有抓到多个VMHost
        $Datastore = $null
        $Datastore = @($VMHost | Get-Datastore -Name $e.Datastore -ErrorAction:SilentlyContinue)
        if(!$Datastore)
        {
            Add-Log -Path $strLogFile -Value "Capture none Datastore, does the Datastore exists on VMHost?: [$($e.Datastore)]" -Type Warning
            continue
        }
        if($Datastore.Count -ge 2)
        {
            Add-Log -Path $strLogFile -Value "Capture [$($Datastore.Count)] Datastore, duplicated Datastores?: [$(($Datastore | %{$_.Id}) -join '], [')]" -Type Warning
            continue
        }
        $Datastore = $Datastore[0]
    
        # 执行到此处说明Datastore在vCenter里存在并唯一,脚本没有抓到多个Datastore
        Add-Log -Path $strLogFile -Value "INFO[OLDName][NewName][Host][Datastore]: [$($VM.Name)][$VMNew][$($VMHost.Name)][$($Datastore.Name)]"
    
        # 判断VM是否使用了VDS
        $VDS = $null
        $VDS = $VM | Get-VDSwitch -ErrorAction:SilentlyContinue
        if(!$VDS)
        {
            # 如果没有使用VDS,则可以直接用New-VM命令备份
            Add-Log -Path $strLogFile -Value 'VDSwitch not found on the VM, use commandlet [New-VM] to clone'
            $Task = $null
            $Task = New-VM -Name $VMNew -VM $VM -VMHost $VMHost -Datastore $Datastore -RunAsync -ErrorAction:SilentlyContinue
            if(!$?)
            {
                Add-Log -Path $strLogFile -Value 'New-VM failed, cause:' -Type Warning
                Add-Log -Path $strLogFile -Value $Error[0] -Type Warning
                continue
            }
            Add-Log -Path $strLogFile -Value "Task launched: [$($Task.Id)]"
            $Tasks += $Task.Id
        }
        else
        {
            # 如果使用VDS,则需要使用API自行备份
            Add-Log -Path $strLogFile -Value 'VDSwitch found on the VM, need to use 2nd way to clone VM'
            Add-Log -Path $strLogFile -Value "VDS [Name][KEY]: [$(($VDS | %{$_.Name}) -join ';')][$(($VDS | %{$_.Key}) -join ';')]"
            $TargetVDS = $null
            $TargetVDS = @($VMHost | Get-VDSwitch -ErrorAction:SilentlyContinue)
            # 在目标VMHost上搜索VDS,如果目标VMHost上没有VDS,clone没有办法继续
            if(!$TargetVDS)
            {
                Add-Log -Path $strLogFile -Value 'Target VMHost server has no VDSwitch, VM which uses a VDS is unable to clone to the VMHost' -Type Warning
                continue
            }
            $TargetVDS = $TargetVDS[-1]
            # 在目标VMHost上搜索到了VDS,抓取出port数量最多的一个
            $TargetVDSGroup = @($TargetVDS | Get-VDPortgroup | Sort-Object NumPorts)[-1]
            Add-Log -Path $strLogFile -Value "Target VDS randomly picked [Name][Key]: [$($TargetVDS.Name)][$($TargetVDS.Key)]"
    
            # API必要参数,VMHost默认的Pool即可
            $Pool = $null
            $Pool = @($VMHost | Get-ResourcePool)[0]
            if(!$Pool)
            {
                Add-Log -Path $strLogFile -Value 'No resource pool found from VMHost, please use Get-ResourcePool to find resource pool on the VMhost' -Type Warning
                continue
            }
    
            # 抓取VM上的网卡对象,并对其修改,修改成为目标VMHost上的VDS
            $VMNic = $null
            $VMNic = $VM.ExtensionData.Config.Hardware.Device | ?{$_.DeviceInfo.Label -imatch 'Network adapter'}
            if($VMNic.Count -ge 2)
            {
                Add-Log -Path $strLogFile -Value 'The VM has more than 2 network adapters, not supported' -Type Warning
                continue
            }
            $VMNicBacking = $VMNic.Backing
            $VMNicBacking.Port.SwitchUuid = $TargetVDS.Key
            $VMNicBacking.Port.PortgroupKey = $TargetVDSGroup.Key
            $VMNicBacking.Port.PortKey = ''
            $VMNicBacking.Port.ConnectionCookie = ''
            
            # API必要参数
            $spec = New-Object VMware.Vim.VirtualMachineCloneSpec
            $spec.Config = New-Object VMware.Vim.VirtualMachineConfigSpec
            $nicDev = New-Object VMware.Vim.VirtualDeviceConfigSpec
            $nicDev.Operation = 'edit'
            $nicDev.Device = $VMNic
            $nicDev.Device.Backing = $VMNicBacking
            $spec.Config.DeviceChange = $nicDev
            $spec.Config.DeviceChange[0].Device.Backing.Port.PortKey = ''
            $spec.Location = New-Object VMware.Vim.VirtualMachineRelocateSpec
            $spec.Location.Host = $VMHost.ExtensionData.MoRef
            $spec.Location.Datastore = $Datastore.ExtensionData.MoRef
            $spec.Location.Pool = $Pool.ExtensionData.MoRef
            $spec.PowerOn = $false
            $spec.Template = $false
    
            Add-Log -Path $strLogFile -Value 'Trying to get datacenter object, this could potentially cause a dead loop!'
            # 为了得到下面的API必要参数$Folder,需要先得到VMHost所在的Datacenter
            $Datacenter = $VMHost.Parent
            while($Datacenter.Id -notmatch 'Datacenter')
            {
                $Datacenter = $Datacenter.Parent
            }
            # API必要参数
            $Folder = $Datacenter | Get-Folder -Name 'Discovered virtual machine'
            Add-Log -Path $strLogFile -Value 'Did not fail into a dead loop!'
    
            # 调用API触发VM clone task
            $Task = $null
            $Task = $VM.ExtensionData.CloneVM_Task($Folder.ExtensionData.MoRef, $VMNew, $spec)
            if(!$?)
            {
                Add-Log -Path $strLogFile -Value 'CloneVM_Task failed, cause:' -Type Warning
                Add-Log -Path $strLogFile -Value $Error[0] -Type Warning
                continue
            }
            Add-Log -Path $strLogFile -Value "Task launched: [$($Task.Type)-$($Task.Value)]"
            $Tasks += "$($Task.Type)-$($Task.Value)"
        }
    }
    
    $Tasks = @($Tasks | ?{$_})
    Add-Log -Path $strLogFile -Value "Waiting for tasks to complete, count: [$($Tasks.Count)]"
    while($Tasks)
    {
        # 由于clone是异步进行,脚本每5分钟进行一次跟踪,由于vCenter默认完成的Task会在15分钟后清除
        # 因此下面的sleep时间最好不要超过15分钟,否则有可能抓不到Task的成功失败的信息
        # 调试的时候可以改成10秒一检测
        Start-Sleep -Seconds 300
        $Tasks = Get-Task -Id $Tasks -ErrorAction:SilentlyContinue
        if(!$?)
        {
            Add-Log -Path $strLogFile -Value 'Failed to refresh Task states, cause:' -Type Warning
            Add-Log -Path $strLogFile -Value $Error[0] -Type Warning
        }
        $Tasks = @(
            $Tasks | %{
                if($_.State -ne 'Running')
                {
                    Add-Log -Path $strLogFile -Value "Task completed [ID][State]: [$($_.Id)][$($_.State)]"
                    if($_.State -eq 'Error')
                    {
                        Add-Log -Path $strLogFile -Value $_.ExtensionData.Info.Error.LocalizedMessage -Type Warning
                    }
                }
                else
                {
                    Add-Log -Path $strLogFile -Value "Task running [ID][% Complete]: [$($_.Id)][$($_.PercentComplete)%]"
                    $_.Id
                }
            }
        )
    }
    
    # 所有Task都完成后,脚本会开始校验VM是否成功备份
    Add-Log -Path $strLogFile -Value 'Verification start'
    foreach($e in $Entries)
    {
        if((Get-VM -Name $e.NewVM -ErrorAction:SilentlyContinue) -and $?)
        {
            # 备份在vCenter里找到,说明备份成功
            Add-Log -Path $strLogFile -Value "[VM][NewVM]: [$($e.VM)][$($e.NewVM)] -- New VM found in vCenter"
            if($e.Reserve)
            {
                # 如果设置了Reserve的值,脚本会根据该值找到当前vCenter里所有的备份,然后删掉多余的备份VM
                $VMBackups = $null
                $VMBackupsRemoval = $null
                $VMBackups = Get-VM -Name "$($e.VM)_ScriptBackup_*" | ?{$_.Name -imatch '_ScriptBackup_d{4}-d{2}-d{2}$'} -ErrorAction:SilentlyContinue
                $VMBackups = @($VMBackups | Sort-Object 'Name')
                Add-Log -Path $strLogFile -Value "Old VM backups captured: [$($VMBackups.Count)][$(($VMBackups | %{$_.Name}) -join ';')]"
                $i = $VMBackups.Count - $e.Reserve
                if($i -le 0)
                {
                    $i = 0
                    Add-Log -Path $strLogFile -Value 'No old backups available to be removeds'
                }
                if($i -gt 0)
                {
                    Add-Log -Path $strLogFile -Value "VM old backups can be removed count: [$i]"
                    $VMBackupsRemoval = $VMBackups[0..(--$i)]
                    Remove-VM -VM $VMBackupsRemoval -DeletePermanently -Confirm:$false
                }
            }
        }
        else
        {
            # 备份没有在vCenter里找到,说明备份失败
            Add-Log -Path $strLogFile -Value "[VM][NewVM]: [$($e.VM)][$($e.NewVM)] -- New VM not found in vCenter, backup failure?" -Type Warning
            $Alert = $true
        }
    }
    
    Add-Log -Path $strLogFile -Value 'All done!'
    
    # 脚本中出现了需要人工检查的错误,则会发出邮件
    if($Alert)
    {
        Send-MailMessage -To $To -From $From -SmtpServer $SmtpServer -Subject $Subject -Attachments $strLogFile
        if(!$?)
        {
            Add-Log -Path $strLogFile -Value 'Failed to send email, cause:' -Type Warning
            Add-Log -Path $strLogFile -Value $Error[0] -Type Warning
        }
    }

    API的参考如下,

    https://www.vmware.com/support/developer/vc-sdk/visdk41pubs/ApiReference/

    当然,许久之前也参考过部分VMware社区的文章~

  • 相关阅读:
    读书笔记——吴军《态度》
    JZYZOJ1237 教授的测试 dfs
    NOI1999 JZYZOJ1289 棋盘分割 dp 方差的数学结论
    [JZYZOJ 1288][洛谷 1005] NOIP2007 矩阵取数 dp 高精度
    POJ 3904 JZYZOJ 1202 Sky Code 莫比乌斯反演 组合数
    POJ2157 Check the difficulty of problems 概率DP
    HDU3853 LOOPS 期望DP 简单
    Codeforces 148D. Bag of mice 概率dp
    POJ3071 Football 概率DP 简单
    HDU4405 Aeroplane chess 飞行棋 期望dp 简单
  • 原文地址:https://www.cnblogs.com/LarryAtCNBlog/p/4613163.html
Copyright © 2011-2022 走看看