Stretch Cluster with Storage Replica nested over a Failover Cluster using Nano Servers and Storage Space Direct hosted on Azure E2s v3 VM
This article is intended to guide you to build a lab environment where you can know almost all Windows Server 2016 new roles and features, and others not so new, but very useful:
- Nested Virtualization
- Nano Server
- Stretch Cluster
- Storage Replica
- Storage Spaces Direct
- PowerShell Direct
- Deduplication
- ReFS
- Thin and Tiers
- Infrastructure as a Code (IaaC)
At the picture below, we can understand that our lab will start at Level 2 of Nested Virtualization.
https://azurecomcdn.azureedge.net/mediahandler/acomblog/media/Default/blog/88616c64-c513-44ac-8259-c201e1798106.png
The intention is to use an Azure E2s v3 Virtual Machine as a base (AzureVM, Level 2), that supports Nested Virtualization. Install Hyper-V role in it and create three VMs. All O.S. will be Windows Server 2016. One VM will be the Domain Controller (DC, Level 3) and two VMs will be Nano Servers (NanoHost01/NanoHost02, Level 3) with Storage, Hyper-V and Failover Cluster roles.
The Nano Servers will form a Storage Spaces Direct and failover cluster (NanoCluster, Level 3) sharing a CSV to Hyper-V create two new nested VMs (Guest01/Guest02, Level 4) with Storage Replica, Failover Cluster, Deduplication and File Server, creating a Stretch Cluster (GuestCluster, Level 4) with Storage Replica and deduplication.
Let's summarize all necessary to start:
Create Azure VM
Create an Azure Virtual Machine E2s v3 named AzureVM.
Add Data Disk
Add a 200GB (SSD) data disk to AzureVM with Read/Write Cache and format as NTFS and assign the H letter.
Connect To VM With RDP
At this point, we need to connect at AzureVM through RDP, open PowerShell ISE as Administrator and follow one of the methods. · Install Hyper-V and Deduplication features and restart:
Install-WindowsFeature -Name Hyper-V, FS-Data-Deduplication -IncludeAllSubFeature -IncludeManagementTools -Restart
Turn 200GB disk online, initialize as GPT, create an H volume and format as NTFS:
Get-Disk | Where partitionstyle -eq 'raw' | Initialize-Disk -PartitionStyle GPT -PassThru | New-Partition -AssignDriveLetter H -UseMaximumSize | Format-Volume -FileSystem NTFS -NewFileSystemLabel "Hyper-V" -Confirm:$false
Configure Data Deduplication
The next step is to configure data deduplication at H: as HyperV:
- Download all files from https://github.com/CaioBauab/W2K16-Hands-On-Lab and save at H:\
- Download the Windows Server 2016 ISO file at Visual Studio Downloads or Evaluation Center and save at H:\
IaaC Method
At AzureVM, just run the H:\IaaC.ps1
PowerShell Method
A good start is creating a transcript file, logging all PowerShell output for further analysis or deployment documentation.
Start-Transcript -Path C:\Transcript.log
We will need to authenticate created VMs using the password defined in Unattended files. For this lab, we set all VMs passwords as P@ssw0rd. As a security best practice, we didn't hardcode the password, asking for it at runtime.
$SecurePassword = Read-Host -Prompt "Enter password" -AsSecureString
Mount the ISO that we'll use to generate the VHDX
$ISO = 'H:\en_windows_server_2016_x64_dvd_9718492.iso'
Mount-DiskImage -ImagePath $ISO | Out-Null
Set-Variable -Name "Drive" -Scope Global -Value ((Get-DiskImage -ImagePath $ISO | Get-Volume).DriveLetter)
Prepare some AzureVM Hyper-V and network options to provide route and internet (NAT) between levels of nested virtualization.
Set-VMhost -EnableEnhancedSessionMode $true
New-VMSwitch -Name 'BR-SP-PRD' -SwitchType Internal
New-VMSwitch -Name 'Cluster PRD' -SwitchType Internal
New-VMSwitch -Name 'BR-SP-DR' -SwitchType Internal
New-VMSwitch -Name 'Cluster DR' -SwitchType Internal
New-NetIPAddress -IPAddress 192.168.4.1 -PrefixLength 24 -InterfaceIndex (Get-NetAdapter -Name '*BR-SP-PRD*').InterfaceIndex
New-NetIPAddress -IPAddress 192.168.5.1 -PrefixLength 24 -InterfaceIndex (Get-NetAdapter -Name '*Cluster PRD*').InterfaceIndex
New-NetIPAddress -IPAddress 192.168.6.1 -PrefixLength 24 -InterfaceIndex (Get-NetAdapter -Name '*BR-SP-DR*').InterfaceIndex
New-NetIPAddress -IPAddress 192.168.7.1 -PrefixLength 24 -InterfaceIndex (Get-NetAdapter -Name '*Cluster DR*').InterfaceIndex
New-NetRoute -DestinationPrefix 192.168.8.0/24 -InterfaceAlias "vEthernet (BR-SP-PRD)" -AddressFamily IPv4 -NextHop 192.168.4.11
New-NetRoute -DestinationPrefix 192.168.9.0/24 -InterfaceAlias "vEthernet (Cluster PRD)" -AddressFamily IPv4 -NextHop 192.168.5.11
New-NetRoute -DestinationPrefix 192.168.10.0/24 -InterfaceAlias "vEthernet (BR-SP-DR)" -AddressFamily IPv4-NextHop 192.168.6.11
New-NetRoute -DestinationPrefix 192.168.11.0/24 -InterfaceAlias "vEthernet (Cluster DR)" -AddressFamily IPv4 -NextHop 192.168.7.11
New-NetNat -Name MyNATnetwork -InternalIPInterfaceAddressPrefix 192.168.4.0/22
Get-NetAdapter | Set-NetIPInterface -Forwarding Enabled
The first computer to support our lab is the Domain Controller (DC)
Function CreateDC ($Computer,$Volume){
# Convert ISO to VHDX
. ${Drive}:\NanoServer\NanoServerImageGenerator\Convert-WindowsImage.ps1
Convert-WindowsImage -SourcePath "${Drive}:\sources\install.wim" -Edition Datacenter -VHDPath "${Volume}:\Hyper-V\$Computer\Virtual Hard Disks\$Computer-C.vhdx" -VHDFormat VHDX -DiskLayout UEFI -RemoteDesktopEnable -BCDinVHD VirtualMachine -UnattendPath H:\DC-Unattend.xml
# Cria, configura e inicia a VM
New-VM -Name $Computer -Path ${Volume}:\Hyper-V -MemoryStartupBytes 2048MB -Generation 2 -VHDPath "${Volume}:\Hyper-V\$Computer\Virtual Hard Disks\$Computer-C.vhdx" -SwitchName 'BR-SP-PRD'
Set-VMProcessor –VMName $Computer -Count 2
Rename-VMNetworkAdapter -VMName $Computer -Name 'Network Adapter' -NewName 'BR-SP-PRD'
Get-VM -Name DC | Add-VMDvdDrive
Set-VMDvdDrive -VMName DC -Path $ISO
Start-VM -Name $Computer
}
CreateDC -Computer "DC" -Volume "H"
While DC start we can create both NanoHost01 and NanoHost02 with Windows Server 2016 Datacenter Nano that will form the Storage Spaces Direct and Hyper-V Failover Cluster (NanoCluster) with CSV to support the next Level (3) of Nested VM Guests.
Function CreateNano ($Computer,$IPPRD,$IPGW,$Volume,$Enviroment){
# Convert ISO to VHDX
Import-Module ${Drive}:\NanoServer\NanoServerImageGenerator
New-NanoServerImage -Edition Datacenter -DeploymentType Guest -MediaPath "${Drive}:\" -BasePath ${Volume}:\Hyper-V\Base -TargetPath "${Volume}:\Hyper-V\$Computer\Virtual Hard Disks\$Computer-C.vhdx" -ComputerName $Computer -Compute -Clustering -Storage -AdministratorPassword $SecurePassword -InterfaceNameOrIndex 'Ethernet' -Ipv4Address $IPPRD -Ipv4SubnetMask 255.255.255.0 -Ipv4Gateway $IPGW -Ipv4Dns 192.168.4.20 -EnableRemoteManagementPort -UnattendPath 'H:\Nano-Unattend.xml'
# Create, start and configure the VM
New-VM -Name $Computer -Path ${Volume}:\Hyper-V -MemoryStartupBytes 4352MB -Generation 2 -VHDPath "${Volume}:\Hyper-V\$Computer\Virtual Hard Disks\$Computer-C.vhdx" -SwitchName "BR-SP-$Enviroment"
# Enable Nested Virtualization
Set-VMProcessor -VMName $Computer -ExposeVirtualizationExtensions $true -Count 2
Set-VMMemory -VMName $Computer -DynamicMemoryEnabled $false
New-VHD -Path "${Volume}:\Hyper-V\$Computer\Virtual Hard Disks\$Computer-SSD.vhdx" -SizeBytes 10GB -Dynamic
New-VHD -Path "${Volume}:\Hyper-V\$Computer\Virtual Hard Disks\$Computer-HDD.vhdx" -SizeBytes 60GB -Dynamic
Add-VMHardDiskDrive -VMName $Computer -ControllerType SCSI -Path "${Volume}:\Hyper-V\$Computer\Virtual Hard Disks\$Computer-SSD.vhdx" -ControllerNumber 0 -ControllerLocation 1
Add-VMHardDiskDrive -VMName $Computer -ControllerType SCSI -Path "${Volume}:\Hyper-V\$Computer\Virtual Hard Disks\$Computer-HDD.vhdx" -ControllerNumber 0 -ControllerLocation 2
Enable-VMIntegrationService -Name "Guest Service Interface" -VMName $Computer
Set-VMFirmware –Vmname $Computer -EnableSecureBoot Off
Start-VM -Name $Computer
Start-Sleep -Seconds 20
${NICPRD} = (Get-VMNetworkAdapter -VMName $Computer | Where-Object IPAddresses -Match 192.168.).Name
Rename-VMNetworkAdapter -VMName $Computer -Name ${NICPRD} -NewName "BR-SP-$Enviroment"
Set-VMNetworkAdapter -VMName $Computer -Name "BR-SP-$Enviroment" -MacAddressSpoofing On
Add-VMNetworkAdapter -VMName $Computer -Name "Cluster $Enviroment" -SwitchName "Cluster $Enviroment"
Set-VMNetworkAdapter -VMName $Computer -Name "Cluster $Enviroment" -MacAddressSpoofing On
}
CreateNano -Computer NanoHost01 -IPPRD 192.168.4.11 -IPGW 192.168.4.1 -Volume "H" -Enviroment 'PRD'
CreateNano -Computer NanoHost02 -IPPRD 192.168.6.11 -IPGW 192.168.6.1 -Volume "H" -Enviroment 'DR'
While NanoHosts do its first start, let’s configure and promote the DC using PowerShell Direct from AzureVM (host) to DC (guest), without the need of network or IP configured at guest NIC at this time.
Function ConfigureDC{
$cred= New-Object System.Management.Automation.PSCredential ("DC\Administrator",$SecurePassword)
# PowerShell Direct
Invoke-Command -VMName DC -Credential $cred {param($SecurePassword)
# Configure Network
Get-NetAdapter | Rename-NetAdapter -NewName "BR-SP-PRD"
New-NetIPAddress -InterfaceAlias "BR-SP-PRD" -IPAddress '192.168.4.20' -AddressFamily IPv4 -PrefixLength 24 -DefaultGateway '192.168.4.1'
# As PDC of root forest, it need to sync with external source
Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\W32Time\Parameters" -Name "Type" -Value "NTP" -Force
# Install Roles and Features
Add-WindowsFeature AD-Domain-Services,RSAT-ADDS,RSAT-ADDS-Tools,RSAT-AD-PowerShell,DNS,RSAT-DNS-Server,RSAT-Hyper-V-Tools,Hyper-V-Powershell,RSAT-Clustering,RSAT-Clustering-Powershell,FS-Data-Deduplication
# Configure DNS Server and Client
$DNS = Get-DnsServerSetting -All ; $DNS.ListeningIPAddress = @("192.168.4.20") ; Set-DNSServerSetting -InputObject $DNS
Set-DnsClientServerAddress -InterfaceAlias "BR-SP-PRD" -ServerAddresses 192.168.4.20
# Promote as Domain Controller
Install-ADDSForest -CreateDnsDelegation:$false -DatabasePath "C:\Windows\NTDS" -DomainMode "WinThreshold" -DomainName "bauab.local" -DomainNetbiosName "BAUAB" -ForestMode "WinThreshold" -InstallDns:$true -LogPath "C:\Windows\NTDS" -SysvolPath "C:\Windows\SYSVOL" -Force:$true -SafeModeAdministratorPassword $SecurePassword
} -ArgumentList ($SecurePassword)
Start-Sleep -Seconds 600
}
ConfigureDC
Once promoted, we need to keep the domain credential for future logons
$DomainCred = New-Object System.Management.Automation.PSCredential ("Bauab\Administrator",$SecurePassword)
Now we can configure DC Roles, adjusting Sites, Site Links, Subnets, DNS Zones and Scavenging.
Function ConfigureDCRoles{
Invoke-Command -VMName DC -Credential $DomainCred {
Get-ADReplicationSite -Filter {Name -eq "Default-First-Site-Name"} | Rename-ADObject -NewName "BR-SP-PRD"
Get-ADReplicationSite -Filter {Name -eq 'BR-SP-PRD'} | Set-ADReplicationSite -Description 'Production Site'
New-ADReplicationSite -Name BR-SP-DR -Description 'Disaster Recovery Site'
Get-ADReplicationSiteLink -Filter {Name -eq "DEFAULTIPSITELINK"} | Rename-ADObject -NewName "BR-SP-PRD<>BR-SP-DR"
Get-ADReplicationSiteLink -Filter {Name -eq "BR-SP-PRD<>BR-SP-DR"} | Set-ADReplicationSiteLink -SitesIncluded @{Add="BR-SP-DR"}
New-ADReplicationSubnet -Name 192.168.4.0/24 -Description "Production" -Location "São Paulo,Brasil" -Site "BR-SP-PRD"
New-ADReplicationSubnet -Name 192.168.5.0/24 -Description "Cluster Production" -Location "São Paulo,Brasil" -Site "BR-SP-PRD"
New-ADReplicationSubnet -Name 192.168.6.0/24 -Description "Disaster Recovery" -Location "São Paulo,Brasil" -Site "BR-SP-DR"
New-ADReplicationSubnet -Name 192.168.7.0/24 -Description "Cluster Disaster Recovery" -Location "São Paulo,Brasil" -Site "BR-SP-DR"
New-ADReplicationSubnet -Name 192.168.8.0/24 -Description "Production" -Location "São Paulo,Brasil" -Site "BR-SP-PRD"
New-ADReplicationSubnet -Name 192.168.9.0/24 -Description "Cluster Production" -Location "São Paulo,Brasil" -Site "BR-SP-PRD"
New-ADReplicationSubnet -Name 192.168.10.0/24 -Description "Disaster Recovery" -Location "São Paulo,Brasil" -Site "BR-SP-DR"
New-ADReplicationSubnet -Name 192.168.11.0/24 -Description "Cluster Disaster Recovery" -Location "São Paulo,Brasil" -Site "BR-SP-DR"
Add-DnsServerPrimaryZone -DynamicUpdate Secure -Name "192.in-addr.arpa" -ReplicationScope Domain
Set-DnsServerScavenging -ScavengingState $true -ApplyOnAllZones -ScavengingInterval 7.00:00:00
}
}
ConfigureDCRoles
[
Done, we are ready to join the NanoHosts to Bauab.local domain, configure VMSwitches, adapters and routes to support Guests VMs.
Function ConfigureNano ($Computer,$IPProd,$IPCluster,$IPClusterClient,$IPClusterOnly,$DestinationPrefix,$NextHop,$Enviroment){
$cred = New-Object System.Management.Automation.PSCredential ("$Computer\Administrator",$SecurePassword)
Invoke-Command -VMName DC -Credential $DomainCred -ScriptBlock {param($Computer,$IPProd,$cred)
# Set NanoHosts as trusted hosts to stablish a WSMan session to copy the djoin file
Set-Item WSMan:\localhost\Client\TrustedHosts $Computer -Concatenate -Force
Add-DnsServerResourceRecordA -IPv4Address $IPProd -Name $Computer -ZoneName bauab.local -AllowUpdateAny -CreatePtr -AgeRecord
$SessionNano = New-PSSession -ComputerName $Computer -Credential $cred
Djoin.exe /provision /domain bauab.local /machine $Computer /savefile C:\$Computer.djoin
Copy-Item C:\$Computer.djoin C:\ -ToSession $SessionNano
} -ArgumentList ($Computer,$IPProd,$cred)
Invoke-Command -VMName $Computer -Credential $cred -ScriptBlock {param($Computer,$IPCluster,$IPClusterClient,$IPClusterOnly,$DestinationPrefix,$NextHop,$Enviroment)
Set-VMhost -EnableEnhancedSessionMode $true
New-VMSwitch -Name "Cluster and Client" -SwitchType Internal
New-VMSwitch -Name "Cluster Only" -SwitchType Internal
Get-NetAdapter -Name "vEthernet (Cluster and Client)" | New-NetIPAddress -IPAddress $IPClusterClient -AddressFamily IPv4 -PrefixLength 24
Get-NetAdapter -Name "vEthernet (Cluster Only)" | New-NetIPAddress -IPAddress $IPClusterOnly -AddressFamily IPv4 -PrefixLength 24
Get-NetAdapter | Set-NetIPInterface -Forwarding Enabled
Get-NetAdapter -Name ((Get-NetIPAddress | Where-Object {$_.IPAddress -Match '169.' -and $_.AddressState -eq 'Preferred' -and $_.AddressFamily -eq 'IPv4'}).InterfaceAlias) | Rename-NetAdapter -NewName "Cluster $Enviroment" | Set-DnsClient -RegisterThisConnectionsAddress $False
New-NetIPAddress -InterfaceAlias "Cluster $Enviroment" -IPAddress $IPCluster -AddressFamily IPv4 -PrefixLength 24
Get-NetAdapter -Name ((Get-NetIPAddress | Where-Object {$_.IPAddress -eq $IPProd -and $_.AddressState -eq 'Preferred'}).InterfaceAlias) | Rename-NetAdapter -NewName "BR-SP-$Enviroment"
If ($Computer -eq 'NanoHost01'){
New-NetRoute -DestinationPrefix 192.168.9.0/24 -InterfaceAlias "vEthernet (Cluster Only)" -AddressFamily IPv4 -NextHop 192.168.9.111
New-NetRoute -DestinationPrefix 192.168.11.0/24 -InterfaceAlias "Cluster PRD" -AddressFamily IPv4 -NextHop 192.168.5.1
New-NetRoute -DestinationPrefix 192.168.5.0/24 -InterfaceAlias "Cluster PRD" -AddressFamily IPv4 -NextHop 192.168.5.1
}
Else {
New-NetRoute -DestinationPrefix 192.168.11.0/24 -InterfaceAlias "vEthernet (Cluster Only)" -AddressFamily IPv4 -NextHop 192.168.11.111
New-NetRoute -DestinationPrefix 192.168.7.0/24 -InterfaceAlias "Cluster DR" -AddressFamily IPv4 -NextHop 192.168.7.1
New-NetRoute -DestinationPrefix 192.168.9.0/24 -InterfaceAlias "Cluster DR" -AddressFamily IPv4 -NextHop 192.168.7.1
}
# You can adjust to reflect yours TZ
Set-TimeZone "E. South America Standard Time"
Djoin.exe /RequestODJ /loadfile C:\$Computer.djoin /windowspath c:\windows /localos
} -ArgumentList ($Computer,$IPCluster,$IPClusterClient,$IPClusterOnly,$DestinationPrefix,$NextHop,$Enviroment)
Stop-VM $Computer ; Start-VM $Computer
}
ConfigureNano -Computer "NanoHost01" -IPProd 192.168.4.11 -IPCluster 192.168.5.11 -IPClusterClient 192.168.8.1 -IPClusterOnly 192.168.9.1 -DestinationPrefix 192.168.9.0/24 -NextHop 192.168.9.111 -Enviroment 'PRD'
ConfigureNano -Computer "NanoHost02" -IPProd 192.168.6.11 -IPCluster 192.168.7.11 -IPClusterClient 192.168.10.1 -IPClusterOnly 192.168.11.1 -DestinationPrefix 192.168.10.0/24 -NextHop 192.168.10.111 -Enviroment 'DR'
Start-Sleep -Seconds 30
Now that we have both NanoHosts joined in domain, we can form the Hyper-V Failover Cluster (NanoCluster) using Storage Pool Direct, to provide the CSV for guest nested computers (Guest01\Guest02). We will set some VHDX as SSD and others as HDD just to try Tiers.
# Form Hyper-V Failover Cluster with Storage Spaces Direct with CSV and Tiers
Invoke-Command -VMName DC -Credential $DomainCred {
New-Cluster -Name NanoCluster -Node NanoHost01,NanoHost02 -StaticAddress 192.168.4.10,192.168.6.10 -NoStorage
New-Item C:\Quorum -ItemType Directory
$acl = Get-Acl -Path C:\Quorum
$perm = "bauab\NanoCluster$", 'Read,Modify', 'ContainerInherit, ObjectInherit', 'None', 'Allow'
$rule = New-Object -TypeName System.Security.AccessControl.FileSystemAccessRule -ArgumentList $perm
$acl.SetAccessRule($rule)
$acl | Set-Acl -Path C:\Quorum
New-SmbShare -Name "Quorum" -Path "C:\Quorum" -FullAccess "bauab\NanoCluster$"
Start-Sleep 60
Set-ClusterQuorum -FileShareWitness \\dc\Quorum -Cluster NanoCluster.bauab.local
(Get-ClusterNetwork -Cluster NanoCluster.bauab.local | Where-Object {$_.Address -eq "192.168.4.0"}).Name = "BR-SP-PRD"
(Get-ClusterNetwork -Cluster NanoCluster.bauab.local | Where-Object {$_.Address -eq "192.168.5.0"}).Name = "Cluster PRD"
(Get-ClusterNetwork -Cluster NanoCluster.bauab.local | Where-Object {$_.Address -eq "192.168.6.0"}).Name = "BR-SP-DR"
(Get-ClusterNetwork -Cluster NanoCluster.bauab.local | Where-Object {$_.Address -eq "192.168.7.0"}).Name = "Cluster DR"
(Get-ClusterNetwork -Cluster NanoCluster.bauab.local | Where-Object {$_.Address -eq "192.168.8.0"}).Name = "Cluster and Client"
(Get-ClusterNetwork -Cluster NanoCluster.bauab.local | Where-Object {$_.Address -eq "192.168.9.0"}).Name = "Cluster Only"
(Get-ClusterNetwork -Cluster NanoCluster.bauab.local | Where-Object {$_.Address -eq "192.168.10.0"}).Name = "Cluster and Client DR"
(Get-ClusterNetwork -Cluster NanoCluster.bauab.local | Where-Object {$_.Address -eq "192.168.11.0"}).Name = "Cluster Only DR"
# Enable Storage Pool Direct at cluster
$ClusterSession = New-CimSession -ComputerName NanoCluster.bauab.local
Enable-ClusterStorageSpacesDirect –CimSession $ClusterSession -CacheState Disabled -SkipEligibilityChecks -confirm:$false
# Set SSD and HDD
Get-PhysicalDisk -CimSession $ClusterSession | Where Size -EQ 10GB | Set-PhysicalDisk -CimSession $ClusterSession -MediaType SSD
Get-PhysicalDisk -CimSession $ClusterSession | Where Size -EQ 60GB | Set-PhysicalDisk -CimSession $ClusterSession -MediaType HDD
Get-PhysicalDisk -CimSession $ClusterSession | select Size,mediatype
# Create Storage Tiers
Get-StoragePool -CimSession $ClusterSession -FriendlyName "S2D*" | New-StorageTier –FriendlyName Performance –MediaType SSD
Get-StorageTier -CimSession $ClusterSession | FT FriendlyName,Size
Get-StoragePool -CimSession $ClusterSession -FriendlyName "S2D*" | FL Size, AllocatedSize
Get-StorageTierSupportedSize -CimSession $ClusterSession Performance | FT -AutoSize
Get-StorageTierSupportedSize -CimSession $ClusterSession Capacity | FT -AutoSize
# Create disk and CSV volume with tiers SSD and HDD
$Volume = New-Volume -CimSession $ClusterSession -StoragePoolFriendlyName "S2D*" -FriendlyName VMs -FileSystem CSVFS_NTFS -StorageTierFriendlyNames Performance,Capacity -StorageTierSizes 7GB,56GB -ResiliencySettingName Mirror -WriteCacheSize 1GB -ProvisioningType Thin
}
With NanoCluster ready, we proceed to deploy and start two Windows 2016 Datacenter VMs (Guest01 and Guest02) with Failover Cluster, Storage Replica and Deduplication as nested virtual machines. (Level 4)
Function CreateGuest ($Computer,$Enviroment){
# Generate Windows Server 2016 Datacenter VHDX
$NanoHost = "NanoHost0$($Computer.Substring($Computer.Length -1))"
. ${Drive}:\NanoServer\NanoServerImageGenerator\Convert-WindowsImage.ps1
Convert-WindowsImage -SourcePath "${Drive}:\sources\install.wim" -Edition Datacenter -VHDPath "H:\Hyper-V\$Computer-C.vhdx" -VHDFormat VHDX -DiskLayout UEFI -RemoteDesktopEnable -BCDinVHD VirtualMachine -UnattendPath "H:\Guest0$($Computer.Substring($Computer.Length -1))-Unattend.xml"
Copy-VMFile -FileSource Host -SourcePath "H:\Hyper-V\$Computer-C.vhdx" -Name NanoHost01 -DestinationPath "C:\ClusterStorage\Volume1\Hyper-V\$Computer\Virtual Hard Disks\$Computer-C.vhdx" -CreateFullPath
Invoke-Command -VMName DC -Credential $DomainCred {Param($Computer,$NanoHost,$Enviroment)
# Create and configure the VM at NanoHost
New-VM -ComputerName $NanoHost -Name $Computer -Path C:\ClusterStorage\Volume1\Hyper-V -MemoryStartupBytes 1792MB -Generation 2 -VHDPath "C:\ClusterStorage\Volume1\Hyper-V\$Computer\Virtual Hard Disks\$Computer-C.vhdx" -SwitchName "Cluster and Client"
Set-VMProcessor -ComputerName $NanoHost -VMName $Computer -Count 2
Set-VMMemory -ComputerName $NanoHost -VMName $Computer -DynamicMemoryEnabled $false
Rename-VMNetworkAdapter -ComputerName $NanoHost -VMName $Computer -Name "Network Adapter" -NewName "BR-SP-$Enviroment"
# Add two Thin disks for storage replica, one for
New-VHD -ComputerName $NanoHost -Path "C:\ClusterStorage\Volume1\Hyper-V\$Computer\Virtual Hard Disks\$Computer-Log.vhdx" -SizeBytes 9GB -Dynamic
New-VHD -ComputerName $NanoHost -Path "C:\ClusterStorage\Volume1\Hyper-V\$Computer\Virtual Hard Disks\$Computer-Data.vhdx" -SizeBytes 10GB -Dynamic
Add-VMHardDiskDrive -ComputerName $NanoHost -VMName $Computer -ControllerType SCSI -Path "C:\ClusterStorage\Volume1\Hyper-V\$Computer\Virtual Hard Disks\$Computer-Log.vhdx" -ControllerNumber 0 -ControllerLocation 1
Add-VMHardDiskDrive -ComputerName $NanoHost -VMName $Computer -ControllerType SCSI -Path "C:\ClusterStorage\Volume1\Hyper-V\$Computer\Virtual Hard Disks\$Computer-Data.vhdx" -ControllerNumber 0 -ControllerLocation 2
# Add VM to Cluster and start
Add-ClusterVirtualMachineRole -Cluster NanoCluster -VirtualMachine $Computer
Set-ClusterOwnerNode -Cluster NanoCluster -Group $Computer -Owners $NanoHost
Start-VM -ComputerName NanoCluster -Name $Computer
} -ArgumentList($Computer,$NanoHost,$Enviroment)
}
CreateGuest -Computer "Guest01" -Enviroment 'PRD'
CreateGuest -Computer "Guest02" -Enviroment 'DR'
Start-Sleep -Seconds 600
After first start of Guests, lets configure its network, roles, features, disks and join to domain.
Function ConfigureGuest($NanoHost,$Guest,$IPPRD,$IPClus,$IPGW,$Enviroment){
$credGuest= New-Object System.Management.Automation.PSCredential ("$Guest\Administrator",$SecurePassword)
Invoke-Command -VMName $NanoHost -Credential $DomainCred {Param($Guest,$IPPRD,$IPClus,$IPGW,$Enviroment,$credGuest)
While ((Get-VMNetworkAdapter -VMName $Guest | Where-Object IPAddresses -Match 169.) -eq $null){Start-Sleep 5}
Invoke-Command -VMName $Guest -Credential $credGuest {Param($IPPRD,$IPGW,$Enviroment)
# Configure Client and Cluster Network
Get-NetAdapter | Rename-NetAdapter -NewName "BR-SP-$Enviroment"
New-NetIPAddress -InterfaceAlias "BR-SP-$Enviroment" -IPAddress $IPPRD -AddressFamily IPv4 -PrefixLength 24 -DefaultGateway $IPGW
Set-DnsClientServerAddress -InterfaceAlias "BR-SP-$Enviroment" -ServerAddresses 192.168.4.20
# Install Roles and Features
Add-WindowsFeature Failover-Clustering,FS-Data-Deduplication,Storage-Replica,FS-FileServer -IncludeManagementTools -Restart
} -ArgumentList($IPPRD,$IPGW,$Enviroment)
While ((Get-VM -VMName $Guest).Uptime.Minutes -gt 1) {Start-Sleep 5}
Add-VMNetworkAdapter -VMName $Guest -Name "Cluster $Enviroment" -SwitchName "Cluster Only"
While ((Get-VMNetworkAdapter -VMName $Guest | Where-Object IPAddresses -Match 169.) -eq $null){Start-Sleep 5}
Invoke-Command -VMName $Guest -Credential $credGuest {Param($Guest,$IPClus,$Enviroment)
# Configure cluster network
Get-NetAdapter -Name ((Get-NetIPAddress | Where-Object {$_.IPAddress -Match '169.' -and $_.AddressState -eq 'Preferred' -and $_.AddressFamily -eq 'IPv4'}).InterfaceAlias) | Rename-NetAdapter -NewName "Cluster $Enviroment"
Get-NetAdapter -Name "Cluster $Enviroment" | Set-DnsClient -RegisterThisConnectionsAddress $False
New-NetIPAddress -InterfaceAlias "Cluster $Enviroment" -IPAddress $IPClus -AddressFamily IPv4 -PrefixLength 24
If ($Guest -eq 'Guest01'){
New-NetRoute -DestinationPrefix 192.168.9.0/24 -InterfaceAlias "Cluster PRD" -AddressFamily IPv4 -NextHop 192.168.9.1
New-NetRoute -DestinationPrefix 192.168.11.0/24 -InterfaceAlias "Cluster PRD" -AddressFamily IPv4 -NextHop 192.168.9.1
}
Else{
New-NetRoute -DestinationPrefix 192.168.11.0/24 -InterfaceAlias "Cluster DR" -AddressFamily IPv4 -NextHop 192.168.11.1
New-NetRoute -DestinationPrefix 192.168.9.0/24 -InterfaceAlias "Cluster DR" -AddressFamily IPv4 -NextHop 192.168.11.1
}
# Initialize disks, create partitions, NTFS volumes and enable Deduplication.
Get-Disk | Where size -eq 10GB | Initialize-Disk -PartitionStyle GPT -PassThru | New-Partition -DriveLetter D -UseMaximumSize | Format-Volume -FileSystem NTFS -NewFileSystemLabel "Dados" -Confirm:$false
Get-Disk | Where size -eq 9GB | Initialize-Disk -PartitionStyle GPT -PassThru | New-Partition -DriveLetter L -UseMaximumSize | Format-Volume -FileSystem NTFS -NewFileSystemLabel "Log" -Confirm:$false
Get-Volume | Where size -LE 10GB | Enable-DedupVolume -UsageType Default
} -ArgumentList($Guest,$IPClus,$Enviroment)
} -ArgumentList($Guest,$IPPRD,$IPClus,$IPGW,$Enviroment,$credGuest)
Invoke-Command -VMName DC -Credential $DomainCred {param($Guest,$IPPRD,$credGuest)
# Configure Guest as trusted for WSMan
Set-Item WSMan:\localhost\Client\TrustedHosts $Guest -Concatenate -Force
Add-DnsServerResourceRecordA -IPv4Address $IPPRD -Name $Guest -ZoneName bauab.local -AllowUpdateAny -CreatePtr -AgeRecord
If (Test-Connection -ComputerName $Guest -Quiet)
{
$SessionGuest = New-PSSession -ComputerName $Guest -Credential $credGuest
Djoin.exe /provision /domain bauab.local /machine $Guest /savefile C:\$Guest.djoin
Copy-Item C:\$Guest.djoin C:\ -ToSession $SessionGuest
}
} -ArgumentList ($Guest,$IPPRD,$credGuest)
Invoke-Command -VMName $NanoHost -Credential $DomainCred {Param($Guest,$credGuest)
While ((Get-VMNetworkAdapter -VMName $Guest | Where-Object IPAddresses -Match 192.) -eq $null){Start-Sleep 5}
Invoke-Command -VMName $Guest -Credential $credGuest {Param($Guest)
# Join to bauab.local domain
Djoin.exe /RequestODJ /loadfile C:\$Guest.djoin /windowspath c:\windows /localos
} -ArgumentList($Guest)
Stop-VM $Guest ; Start-VM $Guest
} -ArgumentList($Guest,$credGuest)
}
ConfigureGuest -NanoHost NanoHost01 -Guest Guest01 -IPPRD 192.168.8.111 -IPClus 192.168.9.111 -IPGW 192.168.8.1 -Enviroment PRD
ConfigureGuest -NanoHost NanoHost02 -Guest Guest02 -IPPRD 192.168.10.111 -IPClus 192.168.11.111 -IPGW 192.168.10.1 -Enviroment DR
Finally the last step! At this point we will create the GuestCluster, a Stretch Cluster with File Share Role, appropriate for Disaster Recovery sites, using Storage Replica.
Function ConfigureGuestCluster{
Invoke-Command -VMName NanoHost01 -Credential $DomainCred -ScriptBlock {Param($DomainCred)
While ((Get-VMNetworkAdapter -VMName Guest01 | Where-Object IPAddresses -Match 192.) -eq $null){Start-Sleep 5}
While ((Get-VMNetworkAdapter -VMName Guest02 | Where-Object IPAddresses -Match 192.) -eq $null){Start-Sleep 5}
Invoke-Command -VMName Guest01 -Credential $DomainCred -ScriptBlock {
# Create Stretch Cluster
New-Cluster GuestCluster –Node Guest01,Guest02 –StaticAddress 192.168.8.100,192.168.10.100 -NoStorage
}
} -ArgumentList($DomainCred)
If (Test-Connection -ComputerName GuestCluster -Quiet)
{
Invoke-Command -VMName DC -Credential $DomainCred -ScriptBlock {
# Create share for GuestCluster
New-Item C:\GuestQuorum -ItemType Directory
$acl = Get-Acl -Path C:\GuestQuorum
$perm = "bauab\GuestCluster$", 'Read,Modify', 'ContainerInherit, ObjectInherit', 'None', 'Allow'
$rule = New-Object -TypeName System.Security.AccessControl.FileSystemAccessRule -ArgumentList $perm
$acl.SetAccessRule($rule)
$acl | Set-Acl -Path C:\GuestQuorum
New-SmbShare -Name "GuestQuorum" -Path "C:\GuestQuorum" -FullAccess "bauab\GuestCluster$"
}
Invoke-Command -VMName NanoHost01 -Credential $DomainCred -ScriptBlock {Param($DomainCred)
Invoke-Command -VMName Guest01 -Credential $DomainCred -ScriptBlock {
Set-ClusterQuorum -FileShareWitness \\dc\GuestQuorum -Cluster GuestCluster.bauab.local
(Get-ClusterNetwork -Cluster GuestCluster | Where-Object {$_.Address -eq "192.168.8.0"}).Name = "BR-SP-PRD"
(Get-ClusterNetwork -Cluster GuestCluster | Where-Object {$_.Address -eq "192.168.9.0"}).Name = "Cluster PRD"
(Get-ClusterNetwork -Cluster GuestCluster | Where-Object {$_.Address -eq "192.168.10.0"}).Name = "BR-SP-DR"
(Get-ClusterNetwork -Cluster GuestCluster | Where-Object {$_.Address -eq "192.168.11.0"}).Name = "Cluster DR"
Get-ClusterResource -Cluster GuestCluster -Name "Cluster Name" | Set-ClusterParameter HostRecordTTL 300
# Configure Stretch Cluster site awareness
New-ClusterFaultDomain -Name BR-SP-PRD -Type Site -Description “Production" -Location “BR-SP-PRD"
New-ClusterFaultDomain -Name BR-SP-DR -Type Site -Description “DR" -Location “BR-SP-DR"
Set-ClusterFaultDomain -Name Guest01 -Parent BR-SP-PRD
Set-ClusterFaultDomain -Name Guest02 -Parent BR-SP-DR
(Get-Cluster).PreferredSite=“BR-SP-PRD“
Get-ClusterAvailableDisk -All -Cluster GuestCluster | Add-ClusterDisk
# Create File Share Role
Add-ClusterFileServerRole -Cluster GuestCluster -Name FS -Storage "Cluster Disk 3" -StaticAddress 192.168.8.101,192.168.10.101
MD D:\Shares\Public
$acl = Get-Acl -Path D:\Shares\Public
$perm = "bauab\Administrator", 'Read,Modify', 'ContainerInherit, ObjectInherit', 'None', 'Allow'
$rule = New-Object -TypeName System.Security.AccessControl.FileSystemAccessRule -ArgumentList $perm
$acl.SetAccessRule($rule)
$acl | Set-Acl -Path D:\Shares\Public
New-SmbShare -Name Public -Path D:\Shares\Public -ContinuouslyAvailable $false -FullAccess "bauab\Administrator"
Get-ClusterResource -Cluster GuestCluster -Name FS | Set-ClusterParameter HostRecordTTL 300
# Enable Storage Replica
New-SRPartnership -SourceComputerName Guest01.bauab.local -SourceRGName rg01 -SourceVolumeName D: -SourceLogVolumeName L: -DestinationComputerName Guest02.bauab.local -DestinationRGName rg02 -DestinationVolumeName D: -DestinationLogVolumeName L:
}
} -ArgumentList($DomainCred)
}
}
ConfigureGuestCluster
We can stop the transcript and retain it as deploy documentation (C:\Transcript.log)
Stop-Transcript