Guides Bulletproof Deployments #1 – Hyper-V

Teil 1 von 6

Teil 1: Vorbereitung der Nodes

Initiale Konfiguration, Features, Secured Core und Domain Join für alle Cluster-Nodes.

Bevor der Cluster entsteht, müssen alle Nodes auf demselben Stand und identisch konfiguriert sein. Features installieren, Secured Core aktivieren, Domain Join. Alles per PowerShell, auf allen Nodes gleichzeitig. Wer das nicht sicherstellt, hat spätestens beim ersten Update einen Problemkandidaten im Cluster.


Vorbereitung des Management Host

Als Management Host wird ein Windows Server 2025 verwendet. Dieser kann als VM oder kleine physische Maschine laufen. Von hier laufen alle Remote-Befehle gegen die Nodes.

Installation notwendiger Windows Features
Install-WindowsFeature -Name RSAT-Clustering,RSAT-Clustering-Mgmt,RSAT-Clustering-PowerShell,RSAT-Hyper-V-Tools,NetworkATC

Allgemeine Konfiguration

Voraussetzungen

Folgende Punkte müssen auf jedem Node vor dem Start erfüllt sein:

  • Windows Server 2022 oder höher als Server Core in der Datacenter-Edition installiert
  • Systemsprache: Englisch
  • Administratorkennwort gesetzt
  • IP-Adresse auf einem Management-Netzwerkadapter konfiguriert:

New-NetIPAddress -InterfaceAlias "SLOT 3 Port 1" -IPAddress XXX.XXX.XXX.XXX -PrefixLength 24 -DefaultGateway XXX.XXX.XXX.XXX

Setzen der Parameter für das Deployment
# ===================== ANPASSEN =====================
$TrustedHosts = "192.0.2.*" # IP-Bereich der Nodes (z.B. "192.168.1.*")
$LocalUser = "Administrator" # Lokaler Benutzername auf den Nodes
$LocalPassword = "Start123!" # Lokales Passwort auf den Nodes
$Servers = "192.0.2.31","192.0.2.32" # IP-Adressen aller Nodes
$DNSServer = "192.0.2.51","192.0.2.52" # DNS-Server
$Interface = "SLOT 6 Port 1" # Name des Management-Netzwerkadapters
$RDPEnable = $true # $true = RDP aktivieren | $false = RDP deaktivieren
$S2D = $true # $true = Storage Spaces Direct | $false = SAN Only
# =================== NICHT ANPASSEN =================
Set-Item WSMan:\localhost\Client\TrustedHosts -Value $TrustedHosts -Force
$SecureStringPassword = ConvertTo-SecureString $LocalPassword -AsPlainText -Force
$localCredentials = New-Object System.Management.Automation.PSCredential ($LocalUser, $SecureStringPassword)
$features = @(
"Hyper-V", "Failover-Clustering", "RSAT-Clustering-PowerShell", "Hyper-V-PowerShell",
"NetworkATC", "Data-Center-Bridging", "RSAT-DataCenterBridging-LLDP-Tools", "FS-SMBBW",
"System-Insights", "RSAT-System-Insights", "BitLocker", "RSAT-Feature-Tools-BitLocker"
)
Konfiguration der Hyper-V Nodes
Invoke-Command -ComputerName $servers -Credential $localCredentials -ScriptBlock {
# DNS Server
Set-DnsClientServerAddress -InterfaceAlias $using:Interface -ServerAddresses ($using:DNSServer)
# Features installieren
Install-WindowsFeature -Name $using:features
# Active Memory Dump
Set-ItemProperty -Path HKLM:\System\CurrentControlSet\Control\CrashControl -Name CrashDumpEnabled -Value 1
Set-ItemProperty -Path HKLM:\System\CurrentControlSet\Control\CrashControl -Name FilterPages -Value 1
# Energiesparmodus: Höchstleistung
powercfg /SetActive 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c
# WinRM Envelope Size
Set-Item -Path WSMan:\localhost\MaxEnvelopeSizekb -Value 8192
# Ping erlauben
netsh advfirewall firewall add rule name="Allow ICMPv4" protocol=icmpv4:8,any dir=in action=allow
netsh advfirewall firewall add rule name="Allow ICMPv6" protocol=icmpv6:8,any dir=in action=allow
# RDP konfigurieren
if ($using:RDPEnable) {
Set-ItemProperty -Path "HKLM:\System\CurrentControlSet\Control\Terminal Server" -Name "fDenyTSConnections" -Value 0
Enable-NetFirewallRule -DisplayGroup "Remote Desktop"
} else {
Set-ItemProperty -Path "HKLM:\System\CurrentControlSet\Control\Terminal Server" -Name "fDenyTSConnections" -Value 1
Disable-NetFirewallRule -DisplayGroup "Remote Desktop"
}
# Getrennte Netzwerkadapter deaktivieren
Get-NetAdapter | Where-Object { $_.Status -eq "Disconnected" } | Disable-NetAdapter -Confirm:$false
}

Neustart der Nodes

An dieser Stelle ist ein Neustart der Nodes erforderlich.

Credits: MSLab

Neustart der Hyper-V Nodes
Restart-Computer $servers -Protocol WSMan -Wait -For PowerShell -Force -Credential $localCredentials
Start-Sleep 20
foreach ($Server in $Servers) {
do {
$Test = Test-NetConnection -ComputerName $Server -CommonTCPPort WINRM
} while ($Test.TcpTestSucceeded -eq $false)
}

Clean Disks (nur S2D)

Hierbei werden alle nicht-bootfähigen Datenträger bereinigt, damit sie für S2D bereitstehen. Alle Daten auf den Datenträgern gehen dabei verloren.

Datenträger bereinigen
if ($S2D) {
Invoke-Command -ComputerName $Servers -Credential $localCredentials -ScriptBlock {
Get-StoragePool | Where-Object IsPrimordial -eq $false | Set-StoragePool -IsReadOnly:$false -ErrorAction SilentlyContinue
Get-StoragePool | Where-Object IsPrimordial -eq $false | Get-VirtualDisk | Remove-VirtualDisk -Confirm:$false -ErrorAction SilentlyContinue
Get-StoragePool | Where-Object IsPrimordial -eq $false | Remove-StoragePool -Confirm:$false -ErrorAction SilentlyContinue
Get-PhysicalDisk | Reset-PhysicalDisk -ErrorAction SilentlyContinue
Get-Disk | Where-Object Number -ne $null |
Where-Object IsBoot -ne $true |
Where-Object IsSystem -ne $true |
Where-Object PartitionStyle -ne RAW |
Where-Object BusType -ne USB | ForEach-Object {
$_ | Set-Disk -IsOffline:$false
$_ | Set-Disk -IsReadOnly:$false
$_ | Clear-Disk -RemoveData -RemoveOEM -Confirm:$false
$_ | Set-Disk -IsReadOnly:$true
$_ | Set-Disk -IsOffline:$true
}
}
}

Secured Core

Secured Core schützt die Cluster-Nodes auf Hardware-Ebene. Alle Nodes müssen die Anforderungen erfüllen und identisch konfiguriert sein.

Voraussetzungen prüfen
$scriptBlock = {
$tpm = Get-Tpm
$secureBoot = $false
try { $secureBoot = Confirm-SecureBootUEFI } catch {}
[PSCustomObject]@{
TPM_Enabled = if ($tpm -and $tpm.TpmEnabled) { "Yes" } else { "No" }
SecureBoot = if ($secureBoot) { "Yes" } else { "No" }
}
}
$Info = Invoke-Command -ComputerName $servers -Credential $localCredentials -ScriptBlock $scriptBlock
$Info | Select-Object PSComputerName, TPM_Enabled, SecureBoot | Format-Table -AutoSize
# In der Tabelle müssen alle Werte auf "Yes" stehen.

Hardware-Voraussetzungen

Die folgenden Registry-Werte setzen voraus, dass die Server-Hardware Secured Core Server vollständig unterstützt. Nicht alle Einstellungen greifen auf jeder Hardware. Bei fehlender Unterstützung können einzelne Werte wirkungslos sein oder im schlimmsten Fall den Bootvorgang beeinträchtigen.

Anforderungen für die nachfolgende Konfiguration:

  • TPM 2.0 (nicht TPM 1.2)
  • UEFI-Firmware mit aktiviertem Secure Boot
  • Intel TXT oder AMD SKINIT für System Guard Secure Launch (DRTM)
  • IOMMU (Intel VT-d / AMD-Vi) für Kernel DMA Protection
  • Memory Attribute Table (MAT) Unterstützung für HVCI MAT

Die genauen Anforderungen und unterstützte Modelle sind in der Herstellerdokumentation des Servers zu prüfen.

Secured Core konfigurieren
Invoke-Command -ComputerName $servers -Credential $localCredentials -ScriptBlock {
# Device Guard
REG ADD "HKLM\SYSTEM\CurrentControlSet\Control\DeviceGuard" /v "EnableVirtualizationBasedSecurity" /t REG_DWORD /d 1 /f
REG ADD "HKLM\SYSTEM\CurrentControlSet\Control\DeviceGuard" /v "RequirePlatformSecurityFeatures" /t REG_DWORD /d 3 /f
REG ADD "HKLM\SYSTEM\CurrentControlSet\Control\DeviceGuard" /v "RequireMicrosoftSignedBootChain" /t REG_DWORD /d 1 /f
REG ADD "HKLM\SYSTEM\CurrentControlSet\Control\DeviceGuard" /v "Locked" /t REG_DWORD /d 0 /f
# Credential Guard
REG ADD "HKLM\SYSTEM\CurrentControlSet\Control\Lsa" /v "LsaCfgFlags" /t REG_DWORD /d 1 /f
# System Guard Secure Launch
REG ADD "HKLM\SYSTEM\CurrentControlSet\Control\DeviceGuard\Scenarios\SystemGuard" /v "Enabled" /t REG_DWORD /d 1 /f
# HVCI
REG ADD "HKLM\SYSTEM\CurrentControlSet\Control\DeviceGuard\Scenarios\HypervisorEnforcedCodeIntegrity" /v "Enabled" /t REG_DWORD /d 1 /f
REG ADD "HKLM\SYSTEM\CurrentControlSet\Control\DeviceGuard\Scenarios\HypervisorEnforcedCodeIntegrity" /v "HVCIMATRequired" /t REG_DWORD /d 1 /f
REG ADD "HKLM\SYSTEM\CurrentControlSet\Control\DeviceGuard\Scenarios\HypervisorEnforcedCodeIntegrity" /v "Locked" /t REG_DWORD /d 0 /f
}

Update der Nodes (optional)

Vor der Cluster-Erstellung können alle Nodes auf einen einheitlichen Patchstand gebracht werden. Warum optional? Wir können das anschließend auch über Cluster Aware Update (CAU) erledigen. Falls ein älteres ISO verwendet wurde, empfehle ich diesen Schritt jedoch hier.

Credits: MSLab

Windows Updates per PowerShell ausführen
# Virtuelles Konto erstellen (umgeht CredSSP-Erfordernis)
Invoke-Command -ComputerName $servers -Credential $localCredentials -ScriptBlock {
New-PSSessionConfigurationFile -RunAsVirtualAccount -Path $env:TEMP\VirtualAccount.pssc
Register-PSSessionConfiguration -Name 'VirtualAccount' -Path $env:TEMP\VirtualAccount.pssc -Force
} -ErrorAction Ignore
Start-Sleep 2
# Windows Update ausführen
Invoke-Command -ComputerName $servers -Credential $localCredentials -ConfigurationName 'VirtualAccount' {
$Searcher = New-Object -ComObject Microsoft.Update.Searcher
$SearchCriteria = "IsInstalled=0 and DeploymentAction='Installation' or " +
"IsPresent=1 and DeploymentAction='Uninstallation' or " +
"IsInstalled=1 and DeploymentAction='Installation' and RebootRequired=1 or " +
"IsInstalled=0 and DeploymentAction='Uninstallation' and RebootRequired=1"
$SearchResult = $Searcher.Search($SearchCriteria).Updates
if ($SearchResult.Count -gt 0) {
$Session = New-Object -ComObject Microsoft.Update.Session
$Downloader = $Session.CreateUpdateDownloader()
$Downloader.Updates = $SearchResult
$Downloader.Download()
$Installer = New-Object -ComObject Microsoft.Update.Installer
$Installer.Updates = $SearchResult
$Result = $Installer.Install()
$Result
}
}
# Temporäre PSSession-Konfiguration entfernen
Invoke-Command -ComputerName $servers -Credential $localCredentials -ScriptBlock {
Unregister-PSSessionConfiguration -Name 'VirtualAccount'
Remove-Item -Path $env:TEMP\VirtualAccount.pssc
}

Domain Join & Umbenennung

Jeder Node bekommt seinen finalen Namen und kommt in die Domäne. Danach ist er auch per Hostname statt IP erreichbar.

Domain Join & Umbenennung der Nodes
# ===================== ANPASSEN =====================
$DomainName = "corp.example.com"
$OUPath = "OU=S2D-Cluster-01,OU=Virtualization,OU=Germany,OU=Server,DC=corp,DC=example,DC=com"
$NodeName = "HV-S2D-N0" # Namensprefix – Nodes werden durchnummeriert (z.B. AHL-S2D-N01, N02 ...)
$StartNode = 1 # Startnummer der Durchnummerierung
# =================== NICHT ANPASSEN =================
$DomainUser = Get-Credential
foreach ($server in $servers) {
Invoke-Command -ComputerName $server -Credential $localCredentials -ArgumentList $StartNode -ScriptBlock {
param($StartNode)
$newName = $using:NodeName + $StartNode.ToString()
Add-Computer -DomainName $using:DomainName -NewName $newName -OUPath $using:OUPath -DomainCredential $using:DomainUser -Restart
}
$StartNode++
}
Start-Sleep 20
foreach ($Server in $Servers) {
do {
$Test = Test-NetConnection -ComputerName $Server -CommonTCPPort WINRM
} while ($Test.TcpTestSucceeded -eq $false)
}

Berechtigungsgruppe setzen

Damit nicht mit Domänen Admin Rechten gearbeitet werden muss und der Zugriff granularer gesteuert werden kann, wird eine AD-Gruppe als lokale Administratorgruppe auf allen Nodes hinzugefügt. Alle Mitglieder dieser Gruppe haben dann Admin-Rechte auf allen Nodes und somit dem auch Cluster.

AD-Gruppe als lokalen Administrator hinzufügen
# ===================== ANPASSEN =====================
$AdminADGroup = "CORP\Group_Hyper-V_Admin" # AD-Gruppe der Hyper-V Administratoren (muss bereits existieren)
# =================== NICHT ANPASSEN =================
foreach ($server in $servers) {
Invoke-Command -ComputerName $server -Credential $localCredentials -ScriptBlock {
Add-LocalGroupMember -Group Administrators -Member $using:AdminADGroup
}
}