ps/Modules/Alkami.PowerShell.ServiceFabric/Public/New-AlkamiServiceFabricCluster.ps1
2023-05-30 22:51:22 -07:00

215 lines
9.4 KiB
PowerShell

function New-AlkamiServiceFabricCluster {
<#
.SYNOPSIS
Creates a service fabric cluster with the set of servers and a security group prefix.
Security group prefix is 'Stage' for Staging, 'DEV' for QA
.PARAMETER servers
The servers to join together into a service fabric cluster.
.PARAMETER securityGroupPrefix
The prefix of the "X - GMSA" security group that the servers are in.
.PARAMETER serverCertificateCommonName
The common name of the admin certificate used to administer the cluster, and secure the service fabric dashboard.
.PARAMETER clientCertificateCommonName
The common name of the client certificate used to gain read-only access to the cluster, and service fabric dashboard.
#>
[CmdletBinding()]
Param(
[Parameter(Mandatory = $true)]
[string[]]$Servers,
[Parameter(Mandatory = $true)]
[string]$SecurityGroupPrefix,
[Parameter(Mandatory = $true)]
[string]$ServerCertificateCommonName,
[Parameter(Mandatory = $true)]
[string]$ClientCertificateCommonName
)
$loglead = (Get-LogLeadName);
Write-Host "$loglead : Initializing Service Fabric cluster.";
# Determine if this cluster is being created for a non-production environment.
$devEnvironmentTypes = @(
"Development",
"QA",
"TeamQA",
"Sandbox"
)
$environmentType = (Get-AppSetting -appSettingKey "Environment.Type" -ComputerName $servers[0]);
$isDevEnvironment = $null -ne ($devEnvironmentTypes | Where-Object { $_ -eq $environmentType; });
$nodes = @();
foreach($server in $servers) {
# If the server name passed in is a FQDN, strip off everything after the "." for convenience of reading later.
$nodeName = $server;
$dotIndex = $nodeName.IndexOf(".");
if($dotIndex -ge 0) {
$nodeName = $nodeName.Substring(0, $dotIndex);
}
$hashcode = [Math]::Abs($server.GetHashCode());
$availabilityZone = Get-AvailabilityZone -ComputerName $server;
if($null -eq $availabilityZone) {
$availabilityZone = $hashcode;
}
$faultDomain = "fd:/{0}/r0" -f $availabilityZone;
$nodes += @{
NodeName = $nodeName;
IPAddress = $server;
FaultDomain = $faultDomain
UpgradeDomain = $hashcode;
}
}
# If it's a development environment and there are less than 3 AZ's, tack unique numbers onto the end of the AZ's.
# This is to trick Service Fabric into believing that it has 3 AZ's so that the cluster bootstraps successfully.
if($isDevEnvironment)
{
$uniqueFaultDomains = $nodes | Foreach-Object { $_["FaultDomain"] } | Select-Object -Unique;
if($uniqueFaultDomains.Count -lt 3)
{
$counter = 1;
foreach($node in $nodes)
{
$node["FaultDomain"] = $node["FaultDomain"] + "-$counter";
$counter++;
}
}
}
$podName = (Get-AppSetting -appSettingKey "Environment.Name" -ComputerName $servers[0])
if($null -eq $podName) {
throw "$loglead : Environment.Name machine config app setting value must be specified!";
}
$podName = $podName.Replace(" ","-");
$clusterName = "$podName-Fabric";
Write-Host "$loglead : Creating Cluster $clusterName`n";
Write-Host "$loglead : Downloading Service Fabric installer/runtime files via Chocolatey."
choco upgrade Alkami.DevOps.ServiceFabric -y --no-progress;
$chocoInstallPath = Get-ChocolateyInstallPath
$fabricBasePath = Join-Path $chocoInstallPath "lib\Alkami.DevOps.ServiceFabric\files"
if(!(Test-Path $fabricBasePath)) {
throw "$loglead : Service Fabric was not downloaded correctly. Check the Chocolatey logs.";
}
# Make sure that the offline installation .cab was downloaded.
$runtime = Get-ChildItem -Path $fabricBasePath -Filter "*.cab" | Select-Object -First 1
if(($null -eq $runtime) -or (!(Test-Path $runtime.FullName))) {
throw "$loglead : The Service Fabric offline installation .cab was not downloaded correctly. Check the chocolatey logs."
}
$runtimePath = $runtime.FullName;
$templateFileName = "ClusterConfig.json";
$fabricConfigTemplatePath = (Join-Path $PSScriptRoot "ServiceFabricConfigTemplates/$templateFileName");
$clusterConfigTemplatePath = (Join-Path $fabricBasePath $templateFileName);
Copy-Item -Path $fabricConfigTemplatePath -Destination $clusterConfigTemplatePath -Force
if(!(Test-Path $clusterConfigTemplatePath)) {
throw "$loglead : Cannot locate cluster configuration template file.";
}
Write-Host "`n$loglead : Building service fabric cluster definition.";
$config = ((Get-Content -Path $clusterConfigTemplatePath) | ConvertFrom-Json);
# Define cluster name.
$config.name = $clusterName;
# Define nodes in the cluster.
$tempNode = $config.nodes[0].PsObject.Copy();
$config.nodes = @();
foreach($serverNode in $nodes) {
$xmlNode = $tempNode.PsObject.Copy();
$xmlNode.nodeName = $serverNode.NodeName;
$xmlNode.ipAddress = $serverNode.IPAddress;
$xmlNode.faultDomain = $serverNode.FaultDomain;
$xmlNode.upgradeDomain = $serverNode.UpgradeDomain;
$config.nodes += $xmlNode;
}
# Configure gmsa cluster security.
Write-Host "$loglead : Now configuring GMSA account security for the cluster.";
$gmsaGroup = "$securityGroupPrefix - gMSA";
Write-Host "$loglead : Configuring security within the `"$gmsaGroup`" group.";
$config.properties.security.WindowsIdentities.ClusterIdentity = $gmsaGroup;
Write-Host "$loglead : Configuring certificates."
$config.properties.security.CertificateInformation.ServerCertificateCommonNames.CommonNames[0].CertificateCommonName = $serverCertificateCommonName;
# Find certificates for the cluster.
$adminCert = Find-CertificateByName -CommonName $serverCertificateCommonName -StoreLocation LocalMachine -StoreName "My";
if($null -eq $adminCert) {
throw "$loglead : Could not locate server certificate $serverCertificateCommonName. Please make sure that it exists.";
}
$clientCert = Find-CertificateByName -CommonName $clientCertificateCommonName -StoreLocation LocalMachine -StoreName "My";
if($null -eq $clientCert) {
throw "$loglead : Could not locate server certificate $serverCertificateCommonName. Please make sure that it exists.";
}
Write-Host "$loglead : Fetching issuing certificate thumbprints for admin/client certificates."
# Find the thumbprint of the issuing certificate for the admin/client certificates.
$chain = New-Object System.Security.Cryptography.X509Certificates.X509Chain;
# Find the issuing thumbprint of the admin cert.
$chain.Build($adminCert);
$adminIssuerThumbprint = $chain.ChainElements[1].Certificate.Thumbprint;
$chain.Reset();
# Find the issuing thumbprint of the client cert.
$chain.Build($clientCert);
$clientIssuerThumbprint = $chain.ChainElements[1].Certificate.Thumbprint;
$chain.Reset();
# Configure admin cert.
$config.properties.security.CertificateInformation.ClientCertificateCommonNames[0].CertificateCommonName = $serverCertificateCommonName;
$config.properties.security.CertificateInformation.ClientCertificateCommonNames[0].CertificateIssuerThumbprint = $adminIssuerThumbprint;
$config.properties.security.CertificateInformation.ClientCertificateCommonNames[0].IsAdmin = $true;
# Configure client cert.
$config.properties.security.CertificateInformation.ClientCertificateCommonNames[1].CertificateCommonName = $clientCertificateCommonName;
$config.properties.security.CertificateInformation.ClientCertificateCommonNames[1].CertificateIssuerThumbprint = $clientIssuerThumbprint;
$config.properties.security.CertificateInformation.ClientCertificateCommonNames[1].IsAdmin = $false;
$clusterConfigLocation = "$fabricBasePath\ClusterConfig.json";
Write-Host "$loglead : Saving Service Fabric cluster definition to $clusterConfigLocation";
Set-Content -Path $clusterConfigLocation -Value ($config | ConvertTo-Json -Depth 8);
Write-Host "$loglead : Starting Remote Registry service on all machines.";
$script = {
param($loglead)
$server = $env:COMPUTERNAME;
$serviceName = "RemoteRegistry";
$service = (Get-Service $serviceName);
if($null -eq $service) {
return;
}
$enabled = ($service.StartType -ne "Disabled");
if(!$enabled) {
Write-Host "$loglead : Setting $serviceName to manual on $server";
Set-Service $serviceName -StartupType Manual;
}
$running = $service.Status -eq "Running"
if(!$running) {
Write-Host "$loglead : Starting $serviceName on $server";
Start-Service $serviceName;
}
}
Invoke-Command -ComputerName $servers -ScriptBlock $script -ArgumentList $loglead;
$installerPath = (Join-Path $fabricBasePath "CreateServiceFabricCluster.ps1");
Write-Host "$loglead : Creating cluster.";
Write-Host "$loglead : InstallerScriptPath: $installerPath";
Write-Host "$loglead : ClusterConfigPath: $clusterConfigLocation";
Write-Host "$loglead : FabricRuntimePath: $runtimePath";
& $installerPath -ClusterConfigFilePath $clusterConfigLocation -FabricRuntimePackagePath $runtimePath -AcceptEULA -Verbose;
}