388 lines
17 KiB
PowerShell
388 lines
17 KiB
PowerShell
<#
|
|
Script name: ha-vcenter-deploy.ps1
|
|
Created on: 30/06/2017
|
|
Author: Sam McGeown, @sammcgeown
|
|
Description: The purpose of the script is to deploy vCenter in High Availability mode, using the advanced method. See https://www.definit.co.uk/2017/06/powershell-deploying-vcenter-high-availability-in-advanced-mode/
|
|
Dependencies: None known
|
|
#>
|
|
param(
|
|
[Parameter(Mandatory=$true)] [String]$configFile,
|
|
[switch]$deployActive,
|
|
[switch]$licenseVCSA,
|
|
[switch]$addSecondaryNic,
|
|
[switch]$prepareVCHA,
|
|
[switch]$clonePassiveVM,
|
|
[switch]$cloneWitnessVM,
|
|
[switch]$configureVCHA,
|
|
[switch]$resizeWitness,
|
|
[switch]$createDRSRule
|
|
)
|
|
|
|
if($psboundparameters.count -eq 1) {
|
|
# Only the configFile is passed, set all steps to true
|
|
$deployActive = $true
|
|
$licenseVCSA = $true
|
|
$addSecondaryNic = $true
|
|
$prepareVCHA = $true
|
|
$clonePassiveVM = $true
|
|
$cloneWitnessVM = $true
|
|
$configureVCHA = $true
|
|
$resizeWitness = $true
|
|
$createDRSRule = $true
|
|
}
|
|
|
|
# Import the PowerCLI and DNS modules
|
|
Get-Module -ListAvailable VMware*,DnsServer | Import-Module
|
|
if ( !(Get-Module -Name VMware.VimAutomation.Core -ErrorAction SilentlyContinue) ) {
|
|
throw "PowerCLI must be installed"
|
|
}
|
|
# Written by Sam McGeown @sammcgeown - www.definit.co.uk
|
|
# Hat tips and thanks go to...
|
|
# William Lam http://www.virtuallyghetto.com/2016/11/vghetto-automated-vsphere-lab-deployment-for-vsphere-6-0u2-vsphere-6-5.html
|
|
# http://www.virtuallyghetto.com/2017/01/exploring-new-vcsa-vami-api-wpowercli-part-1.html
|
|
|
|
# Get the folder location
|
|
$ScriptLocation = Split-Path -Parent $PSCommandPath
|
|
|
|
# Import the JSON Config File
|
|
$podConfig = (get-content $($configFile) -Raw) | ConvertFrom-Json
|
|
|
|
# Path to VCSA Install Sources
|
|
$VCSAInstaller = "$($podConfig.sources.VCSAInstaller)"
|
|
|
|
# Log File
|
|
$verboseLogFile = $podConfig.general.log
|
|
|
|
$StartTime = Get-Date
|
|
|
|
Function Write-Log {
|
|
param(
|
|
[Parameter(Mandatory=$true)]
|
|
[String]$message,
|
|
[switch]$Warning,
|
|
[switch]$Info
|
|
)
|
|
$timeStamp = Get-Date -Format "dd-MM-yyyy hh:mm:ss"
|
|
Write-Host -NoNewline -ForegroundColor White "[$timestamp]"
|
|
if($Warning){
|
|
Write-Host -ForegroundColor Yellow " WARNING: $message"
|
|
} elseif($Info) {
|
|
Write-Host -ForegroundColor White " $message"
|
|
}else {
|
|
Write-Host -ForegroundColor Green " $message"
|
|
}
|
|
$logMessage = "[$timeStamp] $message" | Out-File -Append -LiteralPath $verboseLogFile
|
|
}
|
|
|
|
function Get-VCSAConnection {
|
|
param(
|
|
[string]$vcsaName,
|
|
[string]$vcsaUser,
|
|
[string]$vcsaPassword
|
|
)
|
|
$existingConnection = $global:DefaultVIServers | where-object -Property Name -eq -Value $vcsaName
|
|
if($existingConnection -ne $null) {
|
|
return $existingConnection;
|
|
} else {
|
|
$connection = Connect-VIServer -Server $vcsaName -User $vcsaUser -Password $vcsaPassword -WarningAction SilentlyContinue;
|
|
if($connection -ne $null) {
|
|
return $connection;
|
|
} else {
|
|
throw "Unable to connect to $($vcsaName)..."
|
|
}
|
|
}
|
|
}
|
|
|
|
function Close-VCSAConnection {
|
|
param(
|
|
[string]$vcsaName
|
|
)
|
|
if($vcsaName.Length -le 0) {
|
|
if($Global:DefaultVIServers -ne $null) {
|
|
Disconnect-VIServer -Server $Global:DefaultVIServers -Confirm:$false -ErrorAction SilentlyContinue
|
|
}
|
|
} else {
|
|
$existingConnection = $global:DefaultVIServers | where-object -Property Name -eq -Value $vcsaName
|
|
if($existingConnection -ne $null) {
|
|
Disconnect-VIServer -Server $existingConnection -Confirm:$false;
|
|
} else {
|
|
Write-Warning -Message "Could not find an existing connection named $($vcsaName)"
|
|
}
|
|
}
|
|
}
|
|
|
|
function Get-PodFolder {
|
|
param(
|
|
$vcsaConnection,
|
|
[string]$folderPath
|
|
)
|
|
$folderArray = $folderPath.split("/")
|
|
$parentFolder = Get-Folder -Server $vcsaConnection -Name vm
|
|
foreach($folder in $folderArray) {
|
|
$folderExists = Get-Folder -Server $vcsaConnection | Where-Object -Property Name -eq -Value $folder
|
|
if($folderExists -ne $null) {
|
|
$parentFolder = $folderExists
|
|
} else {
|
|
$parentFolder = New-Folder -Name $folder -Location $parentFolder
|
|
}
|
|
}
|
|
return $parentFolder
|
|
}
|
|
|
|
|
|
Close-VCSAConnection
|
|
|
|
if($deployActive) {
|
|
Write-Log "#### Deploying Active VCSA ####"
|
|
$pVCSA = Get-VCSAConnection -vcsaName $podConfig.target.server -vcsaUser $podConfig.target.user -vcsaPassword $podConfig.target.password
|
|
$pCluster = Get-Cluster -Name $podConfig.target.cluster -Server $pVCSA
|
|
$pDatastore = Get-Datastore -Name $podConfig.target.datastore -Server $pVCSA
|
|
$pPortGroup = Get-VDPortgroup -Name $podConfig.target.portgroup -Server $pVCSA
|
|
$pFolder = Get-PodFolder -vcsaConnection $pVCSA -folderPath $podConfig.target.folder
|
|
|
|
Write-Log "Disabling DRS on $($podConfig.target.cluster)"
|
|
$pCluster | Set-Cluster -DrsEnabled:$true -DrsAutomationLevel:PartiallyAutomated -Confirm:$false | Out-File -Append -LiteralPath $verboseLogFile
|
|
|
|
|
|
Write-Log "Creating DNS Record"
|
|
Add-DnsServerResourceRecordA -Name $podConfig.active.name -ZoneName $podConfig.target.network.domain -AllowUpdateAny -IPv4Address $podConfig.active.ip -ComputerName "192.168.1.20" -CreatePtr -ErrorAction SilentlyContinue
|
|
|
|
Write-Log "Deploying VCSA"
|
|
$config = (Get-Content -Raw "$($VCSAInstaller)\vcsa-cli-installer\templates\install\embedded_vCSA_on_VC.json") | convertfrom-json
|
|
$config.'new.vcsa'.vc.hostname = $podConfig.target.server
|
|
$config.'new.vcsa'.vc.username = $podConfig.target.user
|
|
$config.'new.vcsa'.vc.password = $podConfig.target.password
|
|
$config.'new.vcsa'.vc.datacenter = @($podConfig.target.datacenter)
|
|
$config.'new.vcsa'.vc.datastore = $podConfig.target.datastore
|
|
$config.'new.vcsa'.vc.target = @($podConfig.target.cluster)
|
|
$config.'new.vcsa'.vc.'deployment.network' = $podConfig.target.portgroup
|
|
$config.'new.vcsa'.os.'ssh.enable' = $podConfig.general.ssh
|
|
$config.'new.vcsa'.os.password = $podConfig.active.rootPassword
|
|
$config.'new.vcsa'.appliance.'thin.disk.mode' = $true
|
|
$config.'new.vcsa'.appliance.'deployment.option' = $podConfig.active.deploymentSize
|
|
$config.'new.vcsa'.appliance.name = $podConfig.active.name
|
|
$config.'new.vcsa'.network.'system.name' = $podConfig.active.hostname
|
|
$config.'new.vcsa'.network.'ip.family' = "ipv4"
|
|
$config.'new.vcsa'.network.mode = "static"
|
|
$config.'new.vcsa'.network.ip = $podConfig.active.ip
|
|
$config.'new.vcsa'.network.'dns.servers'[0] = $podConfig.target.network.dns
|
|
$config.'new.vcsa'.network.prefix = $podConfig.target.network.prefix
|
|
$config.'new.vcsa'.network.gateway = $podConfig.target.network.gateway
|
|
$config.'new.vcsa'.sso.password = $podConfig.active.sso.password
|
|
$config.'new.vcsa'.sso.'domain-name' = $podConfig.active.sso.domain
|
|
$config.'new.vcsa'.sso.'site-name' = $podConfig.active.sso.site
|
|
|
|
Write-Log "Creating VCSA JSON Configuration file for deployment"
|
|
|
|
$config | ConvertTo-Json | Set-Content -Path "$($ENV:Temp)\active.json"
|
|
if((Get-VM | Where-Object -Property Name -eq -Value $podConfig.active.name) -eq $null) {
|
|
Write-Log "Deploying OVF, this may take a while..."
|
|
Invoke-Expression "$($VCSAInstaller)\vcsa-cli-installer\win32\vcsa-deploy.exe install --no-esx-ssl-verify --accept-eula --acknowledge-ceip $($ENV:Temp)\active.json"| Out-File -Append -LiteralPath $verboseLogFile
|
|
$vcsaDeployOutput | Out-File -Append -LiteralPath $verboseLogFile
|
|
Write-Log "Moving $($podConfig.active.name) to $($podConfig.target.folder)"
|
|
if((Get-VM | where {$_.name -eq $podConfig.active.name}) -eq $null) {
|
|
throw "Could not find VCSA VM. The script was unable to find the deployed VCSA"
|
|
}
|
|
Get-VM -Name $podConfig.active.name | Move-VM -Destination $pFolder | Out-File -Append -LiteralPath $verboseLogFile
|
|
} else {
|
|
Write-Log "VCSA exists, skipping" -Warning
|
|
}
|
|
Close-VCSAConnection
|
|
}
|
|
|
|
|
|
if($licenseVCSA) {
|
|
Write-Log "#### Configuring VCSA ####"
|
|
Write-Log "Getting connection to the new VCSA"
|
|
$nVCSA = Get-VCSAConnection -vcsaName $podConfig.active.ip -vcsaUser "administrator@$($podConfig.active.sso.domain)" -vcsaPassword $podConfig.active.sso.password
|
|
|
|
Write-Log "Installing vCenter License"
|
|
$serviceInstance = Get-View ServiceInstance -Server $nVCSA
|
|
$licenseManagerRef=$serviceInstance.Content.LicenseManager
|
|
$licenseManager=Get-View $licenseManagerRef
|
|
$licenseManager.AddLicense($podConfig.license.vcenter,$null) | Out-File -Append -LiteralPath $verboseLogFile
|
|
$licenseAssignmentManager = Get-View $licenseManager.LicenseAssignmentManager
|
|
Write-Log "Assigning vCenter Server License"
|
|
try {
|
|
$licenseAssignmentManager.UpdateAssignedLicense($nVCSA.InstanceUuid, $podConfig.license.vcenter, $null) | Out-File -Append -LiteralPath $verboseLogFile
|
|
}
|
|
catch {
|
|
$ErrorMessage = $_.Exception.Message
|
|
Write-Log $ErrorMessage -Warning
|
|
}
|
|
Close-VCSAConnection -vcsaName $podConfig.active.ip
|
|
}
|
|
|
|
if($addSecondaryNic) {
|
|
Write-Log "#### Adding HA Network Adapter ####"
|
|
$pVCSA = Get-VCSAConnection -vcsaName $podConfig.target.server -vcsaUser $podConfig.target.user -vcsaPassword $podConfig.target.password
|
|
|
|
if((Get-VM -Server $pVCSA -Name $podConfig.active.name | Get-NetworkAdapter).count -le 1) {
|
|
Write-Log "Adding HA interface"
|
|
Get-VM -Server $pVCSA -Name $podConfig.active.name | New-NetworkAdapter -Portgroup (Get-VDPortgroup -Name $podConfig.target."ha-portgroup") -Type Vmxnet3 -StartConnected | Out-File -Append -LiteralPath $verboseLogFile
|
|
}
|
|
Close-VCSAConnection
|
|
|
|
$nVCSA = Get-VCSAConnection -vcsaName $podConfig.active.ip -vcsaUser "administrator@$($podConfig.active.sso.domain)" -vcsaPassword $podConfig.active.sso.password
|
|
|
|
Write-Log "Configuring HA interface"
|
|
$CisServer = Connect-CisServer -Server $podConfig.active.ip -User "administrator@$($podConfig.active.sso.domain)" -Password $podConfig.active.sso.password
|
|
|
|
$ipv4API = (Get-CisService -Name 'com.vmware.appliance.techpreview.networking.ipv4')
|
|
$specList = $ipv4API.Help.set.config.CreateExample()
|
|
$createSpec = [pscustomobject] @{
|
|
address = $podConfig.active."ha-ip";
|
|
default_gateway = "";
|
|
interface_name = "nic1";
|
|
mode = "is_static";
|
|
prefix = "29";
|
|
}
|
|
$specList += $createSpec
|
|
$ipv4API.set($specList)
|
|
Close-VCSAConnection
|
|
}
|
|
|
|
if($prepareVCHA) {
|
|
Write-Log "#### Preparing vCenter HA mode ####"
|
|
|
|
$nVCSA = Get-VCSAConnection -vcsaName $podConfig.active.ip -vcsaUser "administrator@$($podConfig.active.sso.domain)" -vcsaPassword $podConfig.active.sso.password
|
|
|
|
Write-Log "Preparing vCenter HA"
|
|
$ClusterConfig = Get-View failoverClusterConfigurator
|
|
|
|
$PassiveIpSpec = New-Object VMware.Vim.CustomizationFixedIp
|
|
$PassiveIpSpec.IpAddress = $podConfig.cluster."passive-ip"
|
|
|
|
$PassiveNetwork = New-object VMware.Vim.CustomizationIPSettings
|
|
$PassiveNetwork.Ip = $PassiveIpSpec
|
|
$PassiveNetwork.SubnetMask = $podConfig.cluster."ha-mask"
|
|
|
|
$PassiveNetworkSpec = New-Object Vmware.Vim.PassiveNodeNetworkSpec
|
|
$PassiveNetworkSpec.IpSettings = $PassiveNetwork
|
|
|
|
$WitnessIpSpec = New-Object VMware.Vim.CustomizationFixedIp
|
|
$WitnessIpSpec.IpAddress = $podConfig.cluster."witness-ip"
|
|
|
|
$WitnessNetwork = New-object VMware.Vim.CustomizationIPSettings
|
|
$WitnessNetwork.Ip = $WitnessIpSpec
|
|
$WitnessNetwork.SubnetMask = $podConfig.cluster."ha-mask"
|
|
|
|
$WitnessNetworkSpec = New-Object VMware.Vim.NodeNetworkSpec
|
|
$WitnessNetworkSpec.IpSettings = $WitnessNetwork
|
|
|
|
$ClusterNetworkSpec = New-Object VMware.Vim.VchaClusterNetworkSpec
|
|
$ClusterNetworkSpec.WitnessNetworkSpec = $WitnessNetworkSpec
|
|
$ClusterNetworkSpec.PassiveNetworkSpec = $PassiveNetworkSpec
|
|
|
|
$PrepareTask = $ClusterConfig.prepareVcha_task($ClusterNetworkSpec)
|
|
|
|
Close-VCSAConnection
|
|
}
|
|
|
|
if($clonePassiveVM) {
|
|
Write-Log "#### Cloning VCSA for Passive Node ####"
|
|
|
|
$pVCSA = Get-VCSAConnection -vcsaName $podConfig.target.server -vcsaUser $podConfig.target.user -vcsaPassword $podConfig.target.password
|
|
$pVMHost = Get-Random (Get-VMhost -Location $podConfig.target.cluster)
|
|
$pFolder = Get-PodFolder -vcsaConnection $pVCSA -folderPath $podConfig.target.folder
|
|
|
|
$activeVM = Get-VM -Name $podConfig.active.name
|
|
$CloneSpecName = "vCHA_ClonePassive"
|
|
|
|
Write-Log "Creating customization spec"
|
|
# Clean up any old spec
|
|
Get-OSCustomizationSpec -Name $CloneSpecName -ErrorAction SilentlyContinue | Remove-OSCustomizationSpec -Confirm:$false -ErrorAction SilentlyContinue | Out-File -Append -LiteralPath $verboseLogFile
|
|
New-OSCustomizationSpec -Name $CloneSpecName -OSType Linux -Domain $podConfig.target.network.domain -NamingScheme fixed -DnsSuffix $podConfig.target.network.domain -NamingPrefix $podConfig.active.hostname -DnsServer $podConfig.target.network.dns -Type NonPersistent | Out-File -Append -LiteralPath $verboseLogFile
|
|
Get-OSCustomizationNicMapping -OSCustomizationSpec $CloneSpecName | Set-OSCustomizationNicMapping -IpMode UseStaticIP -IpAddress $podConfig.active.ip -SubnetMask $podConfig.target.network.netmask -DefaultGateway $podConfig.target.network.gateway | Out-File -Append -LiteralPath $verboseLogFile
|
|
New-OSCustomizationNicMapping -OSCustomizationSpec $CloneSpecName -IpMode UseStaticIP -IpAddress $podConfig.cluster."passive-ip" -SubnetMask $podConfig.cluster."ha-mask" -DefaultGateway $podConfig.target.network.gateway | Out-File -Append -LiteralPath $verboseLogFile
|
|
|
|
Write-Log "Cloning Active VCSA to Passive VCSA"
|
|
$passiveVM = New-VM -Name $podConfig.cluster."passive-name" -VM $activeVM -OSCustomizationSpec $CloneSpecName -VMhost $pVMHost -Server $pVCSA -Location $pFolder | Start-VM | Out-File -Append -LiteralPath $verboseLogFile
|
|
|
|
# Ensure the network adapters are connected
|
|
$passiveVM | Get-NetworkAdapter | Set-NetworkAdapter -Connected:$true -Confirm:$false
|
|
|
|
Write-Log "Waiting for VMware Tools"
|
|
$passiveVM | Wait-Tools
|
|
|
|
Close-VCSAConnection
|
|
}
|
|
|
|
if($cloneWitnessVM) {
|
|
Write-Log "#### Cloning VCSA for Witness Node ####"
|
|
|
|
$pVCSA = Get-VCSAConnection -vcsaName $podConfig.target.server -vcsaUser $podConfig.target.user -vcsaPassword $podConfig.target.password
|
|
$pVMHost = Get-Random (Get-VMhost -Location $podConfig.target.cluster)
|
|
$pFolder = Get-PodFolder -vcsaConnection $pVCSA -folderPath $podConfig.target.folder
|
|
|
|
$activeVM = Get-VM -Name $podConfig.active.name
|
|
$CloneSpecName = "vCHA_CloneWitness"
|
|
|
|
Write-Log "Creating customization spec"
|
|
# Clean up any old spec
|
|
Get-OSCustomizationSpec -Name $CloneSpecName -ErrorAction SilentlyContinue | Remove-OSCustomizationSpec -Confirm:$false -ErrorAction SilentlyContinue | Out-File -Append -LiteralPath $verboseLogFile
|
|
New-OSCustomizationSpec -Name $CloneSpecName -OSType Linux -Domain $podConfig.target.network.domain -NamingScheme fixed -DnsSuffix $podConfig.target.network.domain -NamingPrefix $podConfig.active.hostname -DnsServer $podConfig.target.network.dns -Type NonPersistent | Out-File -Append -LiteralPath $verboseLogFile
|
|
New-OSCustomizationNicMapping -OSCustomizationSpec $CloneSpecName -IpMode UseStaticIP -IpAddress $podConfig.cluster."witness-ip" -SubnetMask $podConfig.cluster."ha-mask" -DefaultGateway $podConfig.target.network.gateway | Out-File -Append -LiteralPath $verboseLogFile
|
|
|
|
Write-Log "Cloning Active VCSA to Witness VCSA"
|
|
$witnessVM = New-VM -Name $podConfig.cluster."witness-name" -VM $activeVM -OSCustomizationSpec $CloneSpecName -VMhost $pVMHost -Server $pVCSA -Location $pFolder | Start-VM | Out-File -Append -LiteralPath $verboseLogFile
|
|
|
|
# Ensure the network adapters are connected
|
|
$witnessVM | Get-NetworkAdapter | Set-NetworkAdapter -Connected:$true -Confirm:$false
|
|
|
|
Write-Log "Waiting for VMware Tools"
|
|
$witnessVM | Wait-Tools
|
|
|
|
Close-VCSAConnection
|
|
}
|
|
|
|
if($configureVCHA) {
|
|
Write-Log "#### Configuring vCenter HA mode ####"
|
|
|
|
$nVCSA = Get-VCSAConnection -vcsaName $podConfig.active.ip -vcsaUser "administrator@$($podConfig.active.sso.domain)" -vcsaPassword $podConfig.active.sso.password
|
|
|
|
$ClusterConfig = Get-View failoverClusterConfigurator
|
|
$ClusterConfigSpec = New-Object VMware.Vim.VchaClusterConfigSpec
|
|
$ClusterConfigSpec.PassiveIp = $podConfig.cluster."passive-ip"
|
|
$ClusterConfigSpec.WitnessIp = $podConfig.cluster."witness-ip"
|
|
$ConfigureTask = $ClusterConfig.configureVcha_task($ClusterConfigSpec)
|
|
Write-Log "Waiting for cluster configuration task"
|
|
Start-Sleep -Seconds 30
|
|
|
|
Close-VCSAConnection -vcsaName $podConfig.active.ip
|
|
}
|
|
|
|
if($resizeWitness) {
|
|
Write-Log "#### Resizing Witness Node ####"
|
|
$pVCSA = Get-VCSAConnection -vcsaName $podConfig.target.server -vcsaUser $podConfig.target.user -vcsaPassword $podConfig.target.password
|
|
|
|
$witnessVM = Get-VM -Name $podConfig.cluster."witness-name"
|
|
Write-Log "Waiting for Witness node to shut down"
|
|
$witnessVM | Stop-VMGuest -Confirm:$false | Out-File -Append -LiteralPath $verboseLogFile
|
|
do {
|
|
Start-Sleep -Seconds 3
|
|
$witnessVM = Get-VM -Name $podConfig.cluster."witness-name"
|
|
} until($witnessVM.PowerState -eq "Poweredoff")
|
|
Write-Log "Setting CPU and Memory"
|
|
$witnessVM | Set-VM -MemoryGB 1 -NumCpu 1 -Confirm:$false | Out-File -Append -LiteralPath $verboseLogFile
|
|
Write-Log "Starting Witness VM"
|
|
$witnessVM | Start-VM | Out-File -Append -LiteralPath $verboseLogFile
|
|
Close-VCSAConnection
|
|
}
|
|
|
|
if($createDRSRule) {
|
|
Write-Log "#### Creating DRS Rule ####"
|
|
$pVCSA = Get-VCSAConnection -vcsaName $podConfig.target.server -vcsaUser $podConfig.target.user -vcsaPassword $podConfig.target.password
|
|
$pCluster = Get-Cluster $podConfig.target.cluster
|
|
$vCHA = Get-VM -Name $podConfig.active.name,$podConfig.cluster."passive-name",$podConfig.cluster."witness-name"
|
|
New-DRSRule -Name "vCenter HA" -Cluster $pCluster -VM $vCHA -KeepTogether $false | Out-File -Append -LiteralPath $verboseLogFile
|
|
Write-Log "Enabling DRS on $($podConfig.target.cluster)"
|
|
$pCluster | Set-Cluster -DrsEnabled:$true -DrsAutomationLevel:FullyAutomated -Confirm:$false | Out-File -Append -LiteralPath $verboseLogFile
|
|
Close-VCSAConnection
|
|
}
|
|
|
|
|
|
$EndTime = Get-Date
|
|
$duration = [math]::Round((New-TimeSpan -Start $StartTime -End $EndTime).TotalMinutes,2)
|
|
|
|
Write-Log "Pod Deployment Completed in $($duration) minutes" |