Last active
January 29, 2016 13:33
-
-
Save pbolduc/f8ba49358a97e1e95332 to your computer and use it in GitHub Desktop.
Create Event Store Cluster Azure VMs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
param ( | |
[Int] | |
$clusterSize, | |
[string] | |
$VMName, | |
[Int] | |
$nodeNumber, | |
[Int] | |
$IntIp, | |
[Int] | |
$ExtIp, | |
[Int] | |
$IntTcpPort = 1112, | |
[Int] | |
$IntHttpPort = 2112, | |
[Int] | |
$ExtTcpPort = 1113, | |
[Int] | |
$ExtHttpPort = 2113 | |
) | |
function Extract-ZipFile($file, $destination) | |
{ | |
if (![System.IO.Directory]::Exists($destination)) { | |
[System.IO.Directory]::CreateDirectory($destination) | |
} | |
$shell = new-object -com shell.application | |
$zip = $shell.NameSpace($file) | |
foreach($item in $zip.items()) { | |
$shell.Namespace($destination).copyhere($item) | |
} | |
} | |
function Install-Chocolatey($InstallToPath) { | |
$env:ChocolateyInstall = $InstallToPath | |
iex ((new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1')) | |
} | |
function Download-EventStore($DownloadUrl, $SaveToPath) { | |
$client = new-object System.Net.WebClient | |
$client.DownloadFile($DownloadUrl, $SaveToPath) | |
} | |
function Format-DataDisks() { | |
$Interleave = 65536 # is this the best value for EventStore? | |
$uninitializedDisks = Get-PhysicalDisk -CanPool $true | |
$poolDisks = $uninitializedDisks | |
$numberOfDisksPerPool = $poolDisks.Length | |
$poolName = "Data Storage Pool" | |
$newPool = New-StoragePool -FriendlyName $poolName -StorageSubSystemFriendlyName "Storage Spaces*" -PhysicalDisks $poolDisks | |
$virtualDiskJob = New-VirtualDisk -StoragePoolFriendlyName $poolName -FriendlyName $poolName -ResiliencySettingName Simple -ProvisioningType Fixed -Interleave $Interleave ` | |
-NumberOfDataCopies 1 -NumberOfColumns $numberOfDisksPerPool -UseMaximumSize -AsJob | |
Receive-Job -Job $virtualDiskJobs -Wait | |
Wait-Job -Job $virtualDiskJobs | |
Remove-Job -Job $virtualDiskJobs | |
# Initialize and format the virtual disks on the pools | |
$formatted = Get-VirtualDisk | Initialize-Disk -PassThru | New-Partition -AssignDriveLetter -UseMaximumSize | Format-Volume -FileSystem NTFS -Confirm:$false | |
# Create the data directory | |
$formatted | ForEach-Object { | |
# Get current drive letter. | |
$downloadDriveLetter = $_.DriveLetter | |
# Create the data directory | |
$dataDirectory = "$($downloadDriveLetter):\Data" | |
New-Item $dataDirectory -Type directory -Force | Out-Null | |
} | |
# Dive time to the storage service to pick up the changes | |
Start-Sleep -Seconds 60 | |
} | |
function New-EventStoreConfigFile() { | |
$seeds = @() | |
for ($n = 1; $n -le $clusterSize; $n++) { | |
$nodeName = $VMName + "-" + $n | |
if ($nodeName -ne $env:COMPUTERNAME) { | |
do { | |
# the other nodes may not be running yet, wait for them to return an ip address | |
$ip = (Resolve-DnsName $nodeName -Type A -ErrorAction SilentlyContinue).IPAddress | |
} while ($ip -eq $null) | |
# | |
$seeds += "'" + $ip + ":" + $IntHttpPort + "'" | |
} | |
} | |
$gossipSeed = $seeds -join ',' | |
# this is a bit of a hack that depends on the BGInfo plugin. Is there a better way to determine this? | |
#$publicIp = PS C:\Users\eventstore> (Get-ItemProperty -Path "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows Azure\BGInfo").PublicIp | |
$configFile = "C:\EventStore\EventStore-Config.yaml" | |
$ipAddress = (Resolve-DnsName $env:COMPUTERNAME -Type A).IPAddress | |
$IntIp = $ipAddress | |
$ExtIp = $ipAddress | |
"# EventStore configuration file. Created at: $(Get-Date -Format 'u')" | Out-File -FilePath $configFile | |
"Db: F:\Data\eventstore" | Out-File -FilePath $configFile -Append | |
"Log: D:\Logs\eventstore" | Out-File -FilePath $configFile -Append | |
"IntIp: $IntIp" | Out-File -FilePath $configFile -Append | |
"ExtIp: $ExtIp" | Out-File -FilePath $configFile -Append | |
"IntTcpPort: $IntTcpPort" | Out-File -FilePath $configFile -Append | |
"IntHttpPort: $IntHttpPort" | Out-File -FilePath $configFile -Append | |
"ExtTcpPort: $ExtTcpPort" | Out-File -FilePath $configFile -Append | |
"ExtHttpPort: $ExtHttpPort" | Out-File -FilePath $configFile -Append | |
"DiscoverViaDns: false" | Out-File -FilePath $configFile -Append | |
"GossipSeed: [$gossipSeed]" | Out-File -FilePath $configFile -Append | |
"ClusterSize: $clusterSize" | Out-File -FilePath $configFile -Append | |
} | |
# | |
# Azure VM's have Temporary Storage on D:\ - Store only log data / temp files there | |
# | |
Format-DataDisks | |
Install-Chocolatey -InstallToPath "C:\Chocolatey" | |
Download-EventStore -DownloadUrl "http://download.geteventstore.com/binaries/EventStore-OSS-Win-v3.0.2.zip" -SaveToPath "D:\EventStore-OSS-Win-v3.0.2.zip" | |
Extract-ZipFile -file "D:\EventStore-OSS-Win-v3.0.2.zip" -destination "C:\EventStore\v3.0.2\" | |
New-EventStoreConfigFile | |
choco install nssm --version 2.24.0 | |
#choco install logstash | |
#choco install timberwinr | |
# | |
# | |
# | |
$ipAddress = (Resolve-DnsName $env:COMPUTERNAME -Type A).IPAddress | |
#Database Node Internal HTTP Interface (open source and commercial) | |
netsh http add urlacl url=http://${ipAddress}:${IntHttpPort}/ user="NT AUTHORITY\LOCAL SERVICE" | |
# Database Node External HTTP Interface (open source and commercial) | |
netsh http add urlacl url=http://${ipAddress}:${ExtHttpPort}/ user="NT AUTHORITY\LOCAL SERVICE" | |
# Manager Node Internal HTTP Interface (commercial only) | |
#netsh http add urlacl url=http://$ipAddress:30777/ user="NT AUTHORITY\LOCAL SERVICE" | |
# Manager Node External HTTP Interface (commercial only) | |
#netsh http add urlacl url=http://$ipAddress:30778/ user="NT AUTHORITY\LOCAL SERVICE" | |
# Added all the ports, but think I only require the 2112,2113 ports | |
New-NetFirewallRule -Name Allow_EventStore_Int_In -DisplayName "Allow inbound Internal Event Store traffic" -Protocol TCP -Direction Inbound -Action Allow -LocalPort ${IntTcpPort},${IntHttpPort} | |
New-NetFirewallRule -Name Allow_EventStore_Ext_In -DisplayName "Allow inbound External Event Store traffic" -Protocol TCP -Direction Inbound -Action Allow -LocalPort ${ExtTcpPort},${ExtHttpPort} | |
C:\Chocolatey\lib\NSSM.2.24.0\Tools\nssm-2.24\win64\nssm.exe install EventStore C:\EventStore\v3.0.2\EventStore.ClusterNode.exe --config C:\EventStore\EventStore-Config.yaml | |
C:\Chocolatey\lib\NSSM.2.24.0\Tools\nssm-2.24\win64\nssm.exe set EventStore Description "The EventStore service." | |
#net start EventStore |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<# | |
.SYNOPSIS | |
.DESCRIPTION | |
.EXAMPLE | |
#> | |
# http://azure.microsoft.com/en-us/documentation/articles/install-configure-powershell/ | |
# Add-AzureAccount | |
param | |
( | |
[Int]$ClusterSize, | |
[Int]$DataDiskSize = 0, | |
[String]$Location, | |
[String]$InstanceSize, | |
[String]$username, | |
[String]$password, | |
[String]$ServiceName, | |
[String]$VMName, | |
[String]$ImageName, | |
[String]$AffinityGroup, | |
[String]$TargetStorageAccountName, | |
[Int]$MaxDisksPerStorageAccount = 40, | |
[Int]$MaxDataDisksPerVirtualMachine = 0, | |
[String]$AvailabilitySetName, | |
[String]$VNetName, | |
[String]$VNetSubnetName, | |
[String]$CustomScriptExtensionStorageAccountName, | |
[String]$CustomScriptExtensionStorageAccountKey, | |
[String]$CustomScriptExtensionContainerName, | |
[String]$CustomScriptExtensionProvisionFile | |
) | |
function Get-RandomStorageAccountName { | |
param ( | |
[Parameter(Mandatory = $true)] | |
[String] | |
$prefix, | |
[ValidateRange(3,24)] | |
[Int] | |
$maxLength = 24 | |
) | |
$name = $prefix.ToLower() | |
while ($name.Length -lt 24) { | |
$name = $name + [char](Get-Random -Minimum 97 -Maximum 122) | |
} | |
return $name | |
} | |
function Ensure-AzureAffinityGroup { | |
param | |
( | |
[Parameter(Mandatory = $true)] | |
[String] | |
$name, | |
[Parameter(Mandatory = $true)] | |
[string]$location | |
) | |
$affinityGroup = Get-AzureAffinityGroup -Name $name -ErrorAction SilentlyContinue | |
if ($affinityGroup -eq $null) { | |
New-AzureAffinityGroup -Location $location -Name $name | Out-Null | |
} elseif ($affinityGroup.Location -ne $location) { | |
$actualLocation = $affinityGroup.Location | |
Write-Error "Affinity Group '$name' exists, but is not in location '$location'. It is in location '$actualLocation'." | |
} | |
} | |
function Ensure-StorageAccount() { | |
if ((Get-AzureStorageAccount -StorageAccountName $TargetStorageAccountName -ErrorAction SilentlyContinue) -eq $null) { | |
$storageAccountName = Get-RandomStorageAccountName($TargetStorageAccountName) | |
New-AzureStorageAccount -AffinityGroup $AffinityGroup -StorageAccountName $storageAccountName -Type Standard_LRS | Out-Null | |
# Wait for the storage account to be available so we can use it | |
Write-Verbose "$(Get-Date -Format 'T') Waiting for storage account $storageAccountName to be available..." | |
while ((Get-AzureStorageAccount -StorageAccountName $storageAccountName).StatusOfPrimary -ne "Available") { | |
Start-Sleep -Seconds 1 | |
} | |
return $storageAccountName | |
} else { | |
return $TargetStorageAccountName | |
} | |
} | |
function Create-EventStoreNodes { | |
param ( | |
[Parameter(Mandatory = $true)] | |
[String] | |
$StorageAccountName | |
) | |
<# Subscription Limits - http://azure.microsoft.com/en-us/documentation/articles/azure-subscription-service-limits/ | |
Default Max limit | |
Cores 20 10,000 | |
Storage accounts per subscription - 100 100 | |
Max 8 KB IOPS per persistent disk (Basic Tier) 300 | |
Max 8 KB IOPS per persistent disk (Standard Tier) 500 | |
Total Request Rate (assuming 1KB object size) per storage account 20,000 | |
Target Throughput for Single Blob Up to 60 MB per second, or up to 500 requests per second | |
#> | |
# create the cloud service first so that it will be ready when the VM are created below | |
New-AzureService -ServiceName $ServiceName -AffinityGroup $AffinityGroup | Out-Null | |
$vms = @() | |
$numberOfDataDisks = (Get-AzureRoleSize -InstanceSize $InstanceSize).MaxDataDiskCount | |
# if we are limiting the total data disks | |
if ($MaxDataDisksPerVirtualMachine -ge 1) { | |
$numberOfDataDisks = [System.Math]::Min($numberOfDataDisks,$MaxDataDisksPerVirtualMachine) | |
} | |
$totalDiskCount = $ClusterSize * ($numberOfDataDisks+1) # OS Disk + N data disks $MaxDiskPerStorageAccount | |
if ($totalDiskCount -ge 40) { | |
Write-Warning "You may have too many disks per storage account. Your performance may degrade. See http://blogs.msdn.com/b/mast/archive/2014/10/14/configuring-azure-virtual-machines-for-optimal-storage-performance.aspx" | |
} | |
for ($node=1;$node -le $ClusterSize;$node++) { | |
$name = $VMName + "-" + $node | |
$vm = New-AzureVMConfig -ImageName $ImageName -InstanceSize $InstanceSize -Name $name -AvailabilitySetName $AvailabilitySetName | |
$vm = $vm | Set-AzureSubnet -SubnetNames $VNetSubnetName | |
$isWindows = $true | |
if ($isWindows) { | |
$vm = $vm | Add-AzureProvisioningConfig -AdminUsername $username -Password $password -Windows | |
} else { | |
$vm = $vm | Add-AzureProvisioningConfig -LinuxUser $username -Password $password -Linux | |
} | |
if ($true) { | |
$StandardTcpPort = 1113 | |
$StandardHttpPort = 2113 | |
$ExtTcpPort = $StandardTcpPort + ($node - 1) * 100 | |
$ExtHttpPort = $StandardHttpPort + ($node - 1) * 100 | |
# http://michaelwasham.com/windows-azure-powershell-reference-guide/configuring-disks-endpoints-vms-powershell/ | |
$vm = $vm | Add-AzureEndpoint -Name 'EventStoreTcp' -LocalPort $ExtTcpPort -PublicPort $ExtTcpPort -Protocol Tcp | |
$vm = $vm | Add-AzureEndpoint -Name 'EventStoreHttp' -LocalPort $ExtHttpPort -PublicPort $ExtHttpPort -Protocol Tcp | |
} | |
$vm = $vm | Set-AzureVMCustomScriptExtension ` | |
-StorageAccountName $CustomScriptExtensionStorageAccountName ` | |
-StorageAccountKey $CustomScriptExtensionStorageAccountKey ` | |
-ContainerName $CustomScriptExtensionContainerName ` | |
-FileName $CustomScriptExtensionProvisionFile ` | |
-Run $CustomScriptExtensionProvisionFile ` | |
-Argument "-clusterSize $ClusterSize -VMName $VMName -nodeNumber $node -ExtTcpPort $ExtTcpPort -ExtHttpPort $ExtHttpPort" | |
# Attach the maximum data disks allowed for the virtual machine size | |
if ($DataDiskSize -gt 0) { | |
$minSizeInGB = 4 # min size for each disk to be stripped: All disks must be at least 4 GB. | |
$sizeInGB = [int][System.Math]::Max([System.Math]::Ceiling($DataDiskSize / $numberOfDataDisks), $minSizeInGB) | |
for ($index = 0; $index -lt $numberOfDataDisks; $index++) { | |
$label = "Data disk " + $index | |
# The maximum number of data disks that may simultaneously use read caching is 4. | |
$vm = $vm | Add-AzureDataDisk -CreateNew -DiskSizeInGB $sizeInGB -DiskLabel $label -LUN $index -HostCaching None | |
} | |
} | |
$vms += $vm | |
} | |
# create all of the VMs | |
New-AzureVM -ServiceName $ServiceName -VNetName $VNetName -VMs $vms | Out-Null | |
} | |
Write-Verbose "$(Get-Date -Format 'T') Ensuring Affinity Group '$AffinityGroup' exists and is in '$Location' location." | |
Ensure-AzureAffinityGroup $AffinityGroup $Location | |
$storageAccountName = Ensure-StorageAccount | |
$SubscriptionName = (Get-AzureSubscription).SubscriptionName | |
Set-AzureSubscription -SubscriptionName $SubscriptionName -CurrentStorageAccount $storageAccountName | |
Write-Verbose "$(Get-Date -Format 'T') Creating Virtual Machines" | |
Create-EventStoreNodes -StorageAccountName $storageAccountName |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment