Automated secure Service Fabric deployment with Azure Key Vault using JSON template and Powershell

Let’s check today how to automate the deployment of secure Service Fabric to Azure RM. First of all, to deploy Service Fabric is extremely easy. You can find the template straight away from the Azure Market Place, provide the details and everything else is done for you.

If you want to deploy secure Service Fabric, there is also a template in github, that you can deploy secure Service Fabric. The only prerequisite is that you already have to have Key Vault deployed and Self-Signed certificates upload to your Key Vault. But what I am going to show you, how to automate the whole process.

My solution will consist of JSON template and the powershell script that I have created. The steps are like this:

  1. Authenticate to Azure subscription
  2. Create a Self-Signed certificate
  3. Create Key Vault
  4. Upload the certificates
  5. Deploy secure Service Fabric from the template by providing Key Vault and certificate details

Let’s get started.

So now I am about to create powershell script. First, I want to put the variables in place. Variables below represent certificate name, path where certificate is going to be exported and imported later, Resource Group of Key Vault, location (location of the Key Vault and location to Service Fabric has to be the same), Key Vault name, Resource Group of Service Fabric and JSON template local path.

#region VARIABLES
$certName = "Azure-SecureServiceFabric"
$Path = "D:\Work\SF"
$RG = "Marty-SF-KeyVault"
$Location = "Uk South"
$KeyVaultName = "SF-KeyVault-Name"
$RGSF = 'Marty-secureSF'
$json = 'D:\Work\SF\secure-SF.json'
#endregion

Now let’s put everything in a functions that it would be easy to execute later on.

CreateSelfSignedCert function is going to create local certificate which we are going to put in the cert:\CurrentUser\My location. We will call this function dependant if we need to create new self-signed certificates or if we are going just to create secure Service Fabric from the current Key Vault where certificate(s) are already stored.

function CreateSelfSignedCert 
{
    Write-Host "Creating Self Signed certificate..." -ForegroundColor Yellow
    $thumbprint = (New-SelfSignedCertificate -DnsName $certName -CertStoreLocation Cert:\CurrentUser\My -KeySpec KeyExchange).Thumbprint
    $cert = (Get-ChildItem -Path cert:\CurrentUser\My\$thumbprint)

    Export-PfxCertificate -Cert $cert -FilePath "$Path\$certName.pfx" -Password $CertPassword
    Write-Host "Certificate created and is stored: $Path\$certName.pfx" -ForegroundColor Green
}

Now we need to create Resource Group for the Key Vault. We will do the simple check if the Resource Group is already created, and proceed further if it is not yet created.

function CreateRGforKV
{
   if (Get-AzureRmResourceGroup | Where "ResourceGroupName" -eq $RG)
    {
        Write-Host "Resource Group $RG already exists" -ForegroundColor Yellow
    }
    else
    {
        Write-Host "Creating Resource Group for Key Vault: $RG in a $Location location" -ForegroundColor Yellow
        New-AzureRmResourceGroup -Name $RG -Location $Location
        Write-Host "Resource Group $RG created successfully" -ForegroundColor Green
    }
}

Finally we need to create Key Vault while doing some logic validation. I am checking if the Resource Group already exists with the Key Vault name. If it exists, output notification, if it doesn’t create the Key Vault. There is another validation I had to implement with try catch error method, as Key Vault creation will fail, if for example, that Key Vault name already exists somewhere else.

function CreateKeyVault
{
    if(Get-AzureRmKeyVault -ResourceGroupName $RG | Where "VaultName" -eq $KeyVaultName)
    {
        #Write-Host "Creating vault $VaultName in $location (resource group $ResourceGroupName)"   
            $keyVault = Get-AzureRmKeyVault -VaultName $KeyVaultName -ResourceGroupName $RG
            Write-Host "Key Vault $KeyVaultName already exists in $RG Resource Group" -ForegroundColor Yellow
      }
     else
        {
            Write-Host "Creating Key Vault $KeyVaultName in a Resource Group $RG" -ForegroundColor Yellow
             try {
             if ($keyVault = New-AzureRmKeyVault -VaultName $KeyVaultName -ResourceGroupName $RG  -Location $Location `
                                            -EnabledForDeployment -Verbose  -Sku premium -ErrorAction Stop)

            {
                Write-Host "Key Vault $KeyVaultName has been successfully created" -ForegroundColor Green
            }
            }
            catch { 
                Write-Host $_
                Write-Host "Key Vault was not created" -ForegroundColor Red -BackgroundColor White
            }
    }        
}

Last function I am going to create related to certificates is for the certificate import to the Key Vault.

function ImportCerttoKeyVault
{
    $ImportCert = Import-AzureKeyVaultCertificate -VaultName $KeyVaultName -Name $certName -FilePath "$Path\$certName.pfx" -Password $CertPassword
}

So far, I have created functions to operate with Self-Signed certificates and Azure Key Vault. Now let’s check with authentication to Azure and execution of the script.

I found this bit of the Powershell code in Microsoft page. It is really helpful if you are working with multiple subscriptions and need to deploy resources everywhere.

function Add-IndexNumberToArray (
    [Parameter(Mandatory=$True)]
    [array]$array
    )
{
    for($i=0; $i -lt $array.Count; $i++) 
    { 
        Add-Member -InputObject $array[$i] -Name "#" -Value ($i+1) -MemberType NoteProperty 
    }
    $array
} 

With the Add-IndexNumberToArray function and the Powershell code below it will help you to select the subscription you want to work with.

Write-Host "Authenticating to Azure..." -ForegroundColor Cyan
try
{
    $AzureLogin = get-azurermcontext
}
catch
{
    Login-AzureRmAccount
}

$AzureLogin = get-azurermcontext
If($AzureLogin -and !($SubscriptionID))
{
    [array]$SubscriptionArray = Add-IndexNumberToArray (Get-AzureRmSubscription) 
    [int]$SelectedSub = 0

    # use the current subscription if there is only one subscription available
    if ($SubscriptionArray.Count -eq 1) 
    {
        $SelectedSub = 1
    }
    # Get SubscriptionID if one isn't provided
    while($SelectedSub -gt $SubscriptionArray.Count -or $SelectedSub -lt 1 -or $SelectedSub.GetType().Name -ne "int32")
    {
        Write-host "Please select a subscription from the list below"
        $SubscriptionArray | select "#", SubscriptionId, SubscriptionName | ft
        $SelectedSub = Read-Host "Please enter a selection from 1 to $($SubscriptionArray.count)"
    }
    
    write-verbose "You Selected Azure Subscription: $($SubscriptionArray[$SelectedSub - 1].SubscriptionName)"
    [guid]$SubscriptionID = [guid]$($SubscriptionArray[$SelectedSub - 1].SubscriptionID)
    
}
Write-Host "Selecting Azure Subscription: $($SubscriptionID.Guid) ..." -ForegroundColor Cyan
$Var = Select-AzureRmSubscription -SubscriptionId $SubscriptionID.Guid

Everything what you have to do just to enter the number of the subscription you want to work with.

And let’s finish with the last bit of the script. After I have been authenticated and chose in which subscription I am going to deploy my secure Service Fabric I need to go through some logic. First, I am asking user if Self-signed certificate is already in place, or if it is needed to create one. Running the very first time, I want to create new certificate. Then, I need to store certificates private key password, in which case is going to be used to store in Key Vault as well. If I choose that I have already create the certificate and want just to import it, I need to provide the password, when it was initially created.

#Dont loose the password!
$Q1 = Read-Host "Do you need to create a new Self Signed certificate? Y/N"

#If Certificate already exists, make sure that $Path and $certName variables are defining existing certificate
if ($Q1 -eq 'Y') 
{ 
    $CertPassword = Read-Host "Provide Certifivate's Private Key Password" -AsSecureString
    CreateSelfSignedCert

}
  else 
{
    $CertPassword = Read-Host "What is the Certificates that was created earlier, Private Key Password?" -AsSecureString
}

It is actually the same step as you would go to your certificate store, and export the certificate with the private key password.

Then, I am calling functions to create Key Vault, getting the values for my secure Fabric Template, creating the Resource Group for Service Fabric, getting the username and password which are going to be used to login to VM and creating the hash table with required values to provide to the JSON template.

CreateRGforKV
CreateKeyVault
ImportCerttoKeyVault

#Clear-Variable -Name KVresourceID, certURL, thumb -Force
$KVresourceID = (Get-AzureRmKeyVault -ResourceGroupName $RG -VaultName $KeyVaultName).ResourceId
$certURL = (Get-AzureKeyVaultCertificate -VaultName $KeyVaultName -Name $certName).secretid
$thumb = (Get-AzureKeyVaultCertificate -VaultName $KeyVaultName -Name $certName).thumbprint

New-AzureRmResourceGroup -Name $RGSF -Location $Location

[string]$username = Read-Host "Remote desktop user Id"
[string]$SFpass = Read-Host "Remote desktop user password. Must be a strong password" -AsSecureString

$SFDeployment = @{
    Name = 'SFDeployment';
    ResourceGroupName = $RGSF;
    Mode = 'Incremental';
    TemplateFile = $json;
    TemplateParameterObject = @{
        clusterLocation = $Location;
        adminUserName = $username;
        adminPassword = $SFpass;
        certificateThumbprint = $thumb;
        sourceVaultValue = $KVresourceID;
        certificateUrlValue = $certURL;
        nt0InstanceCount = 5;
    }
}
New-AzureRmResourceGroupDeployment @SFDeployment -Verbose

The template that I am using is from the github quickstart template. The reason why I am not executing directly from URI is because I am deploying to UK South location, and UK South data center does not support Standard_D2 VM sizes, so I had to change it to Standard_D2_v2 (line 189).

#region VARIABLES

$certName = "Azure-SecureServiceFabric"
$Path = "D:\Work\SF"
$RG = "Marty-SF-KeyVault"
$Location = "Uk South"
$KeyVaultName = "SF-KeyVault-Name"
$RGSF = 'Marty-secureSF'
$json = 'D:\Work\SF\secure-SF.json'
#endregion

function CreateSelfSignedCert 
{
    Write-Host "Creating Self Signed certificate..." -ForegroundColor Yellow
    $thumbprint = (New-SelfSignedCertificate -DnsName $certName -CertStoreLocation Cert:\CurrentUser\My -KeySpec KeyExchange).Thumbprint
    $cert = (Get-ChildItem -Path cert:\CurrentUser\My\$thumbprint)

    Export-PfxCertificate -Cert $cert -FilePath "$Path\$certName.pfx" -Password $CertPassword
    Write-Host "Certificate created and is stored: $Path\$certName.pfx" -ForegroundColor Green

}

function CreateRGforKV
{

   if (Get-AzureRmResourceGroup | Where "ResourceGroupName" -eq $RG)
    
    {
        Write-Host "Resource Group $RG already exists" -ForegroundColor Yellow
    }

    else
    {
        Write-Host "Creating Resource Group for Key Vault: $RG in a $Location location" -ForegroundColor Yellow
        New-AzureRmResourceGroup -Name $RG -Location $Location
        Write-Host "Resource Group $RG created successfully" -ForegroundColor Green
    }
}


function CreateKeyVault
{
    if(Get-AzureRmKeyVault -ResourceGroupName $RG | Where "VaultName" -eq $KeyVaultName)
    {
        #Write-Host "Creating vault $VaultName in $location (resource group $ResourceGroupName)"   
            $keyVault = Get-AzureRmKeyVault -VaultName $KeyVaultName -ResourceGroupName $RG
            Write-Host "Key Vault $KeyVaultName already exists in $RG Resource Group" -ForegroundColor Yellow
             
     }
     else
        {
            Write-Host "Creating Key Vault $KeyVaultName in a Resource Group $RG" -ForegroundColor Yellow
            
            try {
            
            if ($keyVault = New-AzureRmKeyVault -VaultName $KeyVaultName -ResourceGroupName $RG  -Location $Location `
                                            -EnabledForDeployment -Verbose  -Sku premium -ErrorAction Stop)

            {
                Write-Host "Key Vault $KeyVaultName has been successfully created" -ForegroundColor Green
            }
            }
            catch { 
                Write-Host $_
                Write-Host "Key Vault was not created" -ForegroundColor Red -BackgroundColor White
            }

            
    }        
                    
}


function ImportCerttoKeyVault
{
    $ImportCert = Import-AzureKeyVaultCertificate -VaultName $KeyVaultName -Name $certName -FilePath "$Path\$certName.pfx" -Password $CertPassword
    
}

function Add-IndexNumberToArray (
    [Parameter(Mandatory=$True)]
    [array]$array
    )
{
    for($i=0; $i -lt $array.Count; $i++) 
    { 
        Add-Member -InputObject $array[$i] -Name "#" -Value ($i+1) -MemberType NoteProperty 
    }
    $array
} 



Write-Host "Authenticating to Azure..." -ForegroundColor Cyan
try
{
    $AzureLogin = get-azurermcontext
}
catch
{
    Login-AzureRmAccount
}

$AzureLogin = get-azurermcontext
If($AzureLogin -and !($SubscriptionID))
{
    [array]$SubscriptionArray = Add-IndexNumberToArray (Get-AzureRmSubscription) 
    [int]$SelectedSub = 0

    # use the current subscription if there is only one subscription available
    if ($SubscriptionArray.Count -eq 1) 
    {
        $SelectedSub = 1
    }
    # Get SubscriptionID if one isn't provided
    while($SelectedSub -gt $SubscriptionArray.Count -or $SelectedSub -lt 1 -or $SelectedSub.GetType().Name -ne "int32")
    {
        Write-host "Please select a subscription from the list below"
        $SubscriptionArray | select "#", SubscriptionId, SubscriptionName | ft
        $SelectedSub = Read-Host "Please enter a selection from 1 to $($SubscriptionArray.count)"
    }
    
    write-verbose "You Selected Azure Subscription: $($SubscriptionArray[$SelectedSub - 1].SubscriptionName)"
    [guid]$SubscriptionID = [guid]$($SubscriptionArray[$SelectedSub - 1].SubscriptionID)
    
}
Write-Host "Selecting Azure Subscription: $($SubscriptionID.Guid) ..." -ForegroundColor Cyan
$Var = Select-AzureRmSubscription -SubscriptionId $SubscriptionID.Guid

#Dont loose the password!
$Q1 = Read-Host "Do you need to create a new Self Signed certificate? Y/N"

#If Certificate alrady exists, make sure that $Path and $certName variables are defining existing certificate
if ($Q1 -eq 'Y') 
{ 
    $CertPassword = Read-Host "Provide Certifivate's Private Key Password" -AsSecureString
    CreateSelfSignedCert

}
  else 
{
    $CertPassword = Read-Host "What is the Certificates that was created earlier, Private Key Password?" -AsSecureString
}

CreateRGforKV
CreateKeyVault
ImportCerttoKeyVault


#Clear-Variable -Name KVresourceID, certURL, thumb -Force
$KVresourceID = (Get-AzureRmKeyVault -ResourceGroupName $RG -VaultName $KeyVaultName).ResourceId
$certURL = (Get-AzureKeyVaultCertificate -VaultName $KeyVaultName -Name $certName).secretid
$thumb = (Get-AzureKeyVaultCertificate -VaultName $KeyVaultName -Name $certName).thumbprint

New-AzureRmResourceGroup -Name $RGSF -Location $Location

[string]$username = Read-Host "Remote desktop user Id"
[string]$SFpass = Read-Host "Remote desktop user password. Must be a strong password" -AsSecureString

$SFDeployment = @{
    Name = 'SFDeployment';
    ResourceGroupName = $RGSF;
    Mode = 'Incremental';
    TemplateFile = $json;
    TemplateParameterObject = @{
        clusterLocation = $Location;
        adminUserName = $username;
        adminPassword = $SFpass;
        certificateThumbprint = $thumb;
        sourceVaultValue = $KVresourceID;
        certificateUrlValue = $certURL;
        nt0InstanceCount = 5;
    }
}

New-AzureRmResourceGroupDeployment @SFDeployment -Verbose
{
  "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "clusterLocation": {
      "type": "string",
      "metadata": {
        "description": "Location of the Cluster"
      }
    },
    "clusterName": {
      "type": "string",
      "defaultValue": "sfcluster",
      "metadata": {
        "description": "Name of your cluster - Between 3 and 23 characters. Letters and numbers only"
      }
    },
    "adminUserName": {
      "type": "string",
      "metadata": {
        "description": "Remote desktop user Id"
      }
    },
    "adminPassword": {
      "type": "securestring",
      "metadata": {
        "description": "Remote desktop user password. Must be a strong password"
      }
    },
    "vmImagePublisher": {
      "type": "string",
      "defaultValue": "MicrosoftWindowsServer",
      "metadata": {
      "description": "VM image Publisher"}
    },
    "vmImageOffer": {
      "type": "string",
      "defaultValue": "WindowsServer",
      "metadata": {
      "description": "VM image offer"}
    },
    "vmImageSku": {
      "type": "string",
      "defaultValue": "2012-R2-Datacenter",
      "metadata": {
      "description": "VM image SKU"}
    },
    "vmImageVersion": {
      "type": "string",
      "defaultValue": "latest",
      "metadata": {
      "description": "VM image version"}
    },
    "loadBalancedAppPort1": {
      "type": "int",
      "defaultValue": 80,
      "metadata": {
        "description": "Input endpoint1 for the application to use. Replace it with what your application uses"
      }
    },
    "loadBalancedAppPort2": {
      "type": "int",
      "defaultValue": 8081,
      "metadata": {
        "description": "Input endpoint2 for the application to use. Replace it with what your application uses"
      }
    },
    "certificateStoreValue": {
      "type": "string",
      "allowedValues": [
        "My"
      ],
      "defaultValue": "My",
      "metadata": {
        "description": "The store name where the cert will be deployed in the virtual machine"
      }
    },
    "certificateThumbprint": {
      "type": "string",
      "metadata": {
        "description": "Certificate Thumbprint"
      }
    },
    "sourceVaultValue": {
      "type": "string",
      "metadata": {
        "description": "Resource Id of the key vault, is should be in the format of /subscriptions/<Sub ID>/resourceGroups/<Resource group name>/providers/Microsoft.KeyVault/vaults/<vault name>"
      }
    },
    "certificateUrlValue": {
      "type": "string",
      "metadata": {
        "description": "Refers to the location URL in your key vault where the certificate was uploaded, it is should be in the format of https://<name of the vault>.vault.azure.net:443/secrets/<exact location>"
      }
    },
    "clusterProtectionLevel": {
      "type": "string",
      "allowedValues": [
        "None",
        "Sign",
        "EncryptAndSign"
      ],
      "defaultValue": "EncryptAndSign",
      "metadata": {
        "description": "Protection level.Three values are allowed - EncryptAndSign, Sign, None. It is best to keep the default of EncryptAndSign, unless you have a need not to"
      }
    },
    "storageAccountType": {
      "type": "string",
      "allowedValues": [
        "Standard_LRS",
        "Standard_GRS"
      ],
      "defaultValue": "Standard_LRS",
      "metadata": {
        "description": "Replication option for the VM image storage account"
      }
    },
    "supportLogStorageAccountType": {
      "type": "string",
      "allowedValues": [
        "Standard_LRS",
        "Standard_GRS"
      ],
      "defaultValue": "Standard_LRS",
      "metadata": {
        "description": "Replication option for the support log storage account"
      }
    },
    "applicationDiagnosticsStorageAccountType": {
      "type": "string",
      "allowedValues": [
        "Standard_LRS",
        "Standard_GRS"
      ],
      "defaultValue": "Standard_LRS",
      "metadata": {
        "description": "Replication option for the application diagnostics storage account"
      }
    },
    "nt0InstanceCount": {
      "type": "int",
      "defaultValue": 5,
      "metadata": {
        "description": "Instance count for node type"
      }
    }
  },
  "variables": {
    "computeLocation": "[parameters('clusterLocation')]",
    "dnsName": "[parameters('clusterName')]",
    "vmStorageAccountName": "[toLower(concat(uniqueString(resourceGroup().id), '1' ))]",
    "vmName": "vm",
    "publicIPAddressName": "PublicIP-VM",
    "publicIPAddressType": "Dynamic",
    "vmStorageAccountContainerName": "vhds",
    "virtualNetworkName": "VNet",
    "addressPrefix": "10.0.0.0/16",
    "nicName": "NIC",
    "lbName": "LoadBalancer",
    "lbIPName": "PublicIP-LB-FE",
    "availSetName": "AvailabilitySet",
    "maxPercentUpgradeDomainDeltaUnhealthyNodes": "100",
    "vnetID": "[resourceId('Microsoft.Network/virtualNetworks',variables('virtualNetworkName'))]",
    "overProvision": "false",
    "vmssApiVersion": "2016-03-30",
    "lbApiVersion": "2015-06-15",
    "vNetApiVersion": "2015-06-15",
    "storageApiVersion": "2016-01-01",
    "publicIPApiVersion": "2015-06-15",
    "nt0applicationStartPort": "20000",
    "nt0applicationEndPort": "30000",
    "nt0ephemeralStartPort": "49152",
    "nt0ephemeralEndPort": "65534",
    "nt0fabricTcpGatewayPort": "19000",
    "nt0fabricHttpGatewayPort": "19080",
    "subnet0Name": "Subnet-0",
    "subnet0Prefix": "10.0.0.0/24",
    "subnet0Ref": "[concat(variables('vnetID'),'/subnets/',variables('subnet0Name'))]",
    "supportLogStorageAccountName": "[toLower( concat( uniqueString(resourceGroup().id),'2'))]",
    "applicationDiagnosticsStorageAccountName": "[toLower(concat(uniqueString(resourceGroup().id), '3' ))]",
    "lbID0": "[resourceId('Microsoft.Network/loadBalancers',concat('LB','-', parameters('clusterName'),'-',variables('vmNodeType0Name')))]",
    "lbIPConfig0": "[concat(variables('lbID0'),'/frontendIPConfigurations/LoadBalancerIPConfig')]",
    "lbPoolID0": "[concat(variables('lbID0'),'/backendAddressPools/LoadBalancerBEAddressPool')]",
    "lbProbeID0": "[concat(variables('lbID0'),'/probes/FabricGatewayProbe')]",
    "lbHttpProbeID0": "[concat(variables('lbID0'),'/probes/FabricHttpGatewayProbe')]",
    "lbNatPoolID0": "[concat(variables('lbID0'),'/inboundNatPools/LoadBalancerBEAddressNatPool')]",
    "vmNodeType0Name": "[toLower(concat('NT1', variables('vmName')))]",
    "vmNodeType0Size": "Standard_D2_v2",
    "vmStorageAccountName0": "[toLower(concat(uniqueString(resourceGroup().id), '1', '0' ))]",
    "uniqueStringArray0": [
      "[concat(variables('vmStorageAccountName0'), '0')]",
      "[concat(variables('vmStorageAccountName0'), '1')]",
      "[concat(variables('vmStorageAccountName0'), '2')]",
      "[concat(variables('vmStorageAccountName0'), '3')]",
      "[concat(variables('vmStorageAccountName0'), '4')]"
    ]
  },
  "resources": [
    {
      "apiVersion": "[variables('storageApiVersion')]",
      "type": "Microsoft.Storage/storageAccounts",
      "name": "[variables('supportLogStorageAccountName')]",
      "location": "[variables('computeLocation')]",
      "dependsOn": [],
      "properties": {},
      "kind": "Storage",
      "sku": {
        "name": "[parameters('supportLogStorageAccountType')]"
      },
      "tags": {
        "resourceType": "Service Fabric",
        "clusterName": "[parameters('clusterName')]"
      }
    },
    {
      "apiVersion": "[variables('storageApiVersion')]",
      "type": "Microsoft.Storage/storageAccounts",
      "name": "[variables('applicationDiagnosticsStorageAccountName')]",
      "location": "[variables('computeLocation')]",
      "dependsOn": [],
      "properties": {},
      "kind": "Storage",
      "sku": {
        "name": "[parameters('applicationDiagnosticsStorageAccountType')]"
      },
      "tags": {
        "resourceType": "Service Fabric",
        "clusterName": "[parameters('clusterName')]"
      }
    },
    {
      "apiVersion": "[variables('vNetApiVersion')]",
      "type": "Microsoft.Network/virtualNetworks",
      "name": "[variables('virtualNetworkName')]",
      "location": "[variables('computeLocation')]",
      "properties": {
        "addressSpace": {
          "addressPrefixes": [
            "[variables('addressPrefix')]"
          ]
        },
        "subnets": [
          {
            "name": "[variables('subnet0Name')]",
            "properties": {
              "addressPrefix": "[variables('subnet0Prefix')]"
            }
          }
        ]
      },
      "tags": {
        "resourceType": "Service Fabric",
        "clusterName": "[parameters('clusterName')]"
      }
    },
    {
      "apiVersion": "[variables('publicIPApiVersion')]",
      "type": "Microsoft.Network/publicIPAddresses",
      "name": "[concat(variables('lbIPName'),'-','0')]",
      "location": "[variables('computeLocation')]",
      "properties": {
        "dnsSettings": {
          "domainNameLabel": "[variables('dnsName')]"
        },
        "publicIPAllocationMethod": "Dynamic"
      },
      "tags": {
        "resourceType": "Service Fabric",
        "clusterName": "[parameters('clusterName')]"
      }
    },
    {
      "apiVersion": "[variables('lbApiVersion')]",
      "type": "Microsoft.Network/loadBalancers",
      "name": "[concat('LB','-', parameters('clusterName'),'-',variables('vmNodeType0Name'))]",
      "location": "[variables('computeLocation')]",
      "dependsOn": [
        "[concat('Microsoft.Network/publicIPAddresses/',concat(variables('lbIPName'),'-','0'))]"
      ],
      "properties": {
        "frontendIPConfigurations": [
          {
            "name": "LoadBalancerIPConfig",
            "properties": {
              "publicIPAddress": {
                "id": "[resourceId('Microsoft.Network/publicIPAddresses',concat(variables('lbIPName'),'-','0'))]"
              }
            }
          }
        ],
        "backendAddressPools": [
          {
            "name": "LoadBalancerBEAddressPool",
            "properties": {}
          }
        ],
        "loadBalancingRules": [
          {
            "name": "LBRule",
            "properties": {
              "backendAddressPool": {
                "id": "[variables('lbPoolID0')]"
              },
              "backendPort": "[variables('nt0fabricTcpGatewayPort')]",
              "enableFloatingIP": "false",
              "frontendIPConfiguration": {
                "id": "[variables('lbIPConfig0')]"
              },
              "frontendPort": "[variables('nt0fabricTcpGatewayPort')]",
              "idleTimeoutInMinutes": "5",
              "probe": {
                "id": "[variables('lbProbeID0')]"
              },
              "protocol": "tcp"
            }
          },
          {
            "name": "LBHttpRule",
            "properties": {
              "backendAddressPool": {
                "id": "[variables('lbPoolID0')]"
              },
              "backendPort": "[variables('nt0fabricHttpGatewayPort')]",
              "enableFloatingIP": "false",
              "frontendIPConfiguration": {
                "id": "[variables('lbIPConfig0')]"
              },
              "frontendPort": "[variables('nt0fabricHttpGatewayPort')]",
              "idleTimeoutInMinutes": "5",
              "probe": {
                "id": "[variables('lbHttpProbeID0')]"
              },
              "protocol": "tcp"
            }
          },
          {
            "name": "AppPortLBRule1",
            "properties": {
              "backendAddressPool": {
                "id": "[variables('lbPoolID0')]"
              },
              "backendPort": "[parameters('loadBalancedAppPort1')]",
              "enableFloatingIP": "false",
              "frontendIPConfiguration": {
                "id": "[variables('lbIPConfig0')]"
              },
              "frontendPort": "[parameters('loadBalancedAppPort1')]",
              "idleTimeoutInMinutes": "5",
              "probe": {
                "id": "[concat(variables('lbID0'),'/probes/AppPortProbe1')]"
              },
              "protocol": "tcp"
            }
          },
          {
            "name": "AppPortLBRule2",
            "properties": {
              "backendAddressPool": {
                "id": "[variables('lbPoolID0')]"
              },
              "backendPort": "[parameters('loadBalancedAppPort2')]",
              "enableFloatingIP": "false",
              "frontendIPConfiguration": {
                "id": "[variables('lbIPConfig0')]"
              },
              "frontendPort": "[parameters('loadBalancedAppPort2')]",
              "idleTimeoutInMinutes": "5",
              "probe": {
                "id": "[concat(variables('lbID0'),'/probes/AppPortProbe2')]"
              },
              "protocol": "tcp"
            }
          }
        ],
        "probes": [
          {
            "name": "FabricGatewayProbe",
            "properties": {
              "intervalInSeconds": 5,
              "numberOfProbes": 2,
              "port": "[variables('nt0fabricTcpGatewayPort')]",
              "protocol": "tcp"
            }
          },
          {
            "name": "FabricHttpGatewayProbe",
            "properties": {
              "intervalInSeconds": 5,
              "numberOfProbes": 2,
              "port": "[variables('nt0fabricHttpGatewayPort')]",
              "protocol": "tcp"
            }
          },
          {
            "name": "AppPortProbe1",
            "properties": {
              "intervalInSeconds": 5,
              "numberOfProbes": 2,
              "port": "[parameters('loadBalancedAppPort1')]",
              "protocol": "tcp"
            }
          },
          {
            "name": "AppPortProbe2",
            "properties": {
              "intervalInSeconds": 5,
              "numberOfProbes": 2,
              "port": "[parameters('loadBalancedAppPort2')]",
              "protocol": "tcp"
            }
          }
        ],
        "inboundNatPools": [
          {
            "name": "LoadBalancerBEAddressNatPool",
            "properties": {
              "backendPort": "3389",
              "frontendIPConfiguration": {
                "id": "[variables('lbIPConfig0')]"
              },
              "frontendPortRangeEnd": "4500",
              "frontendPortRangeStart": "3389",
              "protocol": "tcp"
            }
          }
        ]
      },
      "tags": {
        "resourceType": "Service Fabric",
        "clusterName": "[parameters('clusterName')]"
      }
    },
    {
      "apiVersion": "[variables('storageApiVersion')]",
      "type": "Microsoft.Storage/storageAccounts",
      "name": "[variables('uniqueStringArray0')[copyIndex()]]",
      "location": "[variables('computeLocation')]",
      "dependsOn": [],
      "properties": {},
      "copy": {
        "name": "storageLoop",
        "count": 5
      },
      "kind": "Storage",
      "sku": {
        "name": "[parameters('storageAccountType')]"
      },
      "tags": {
        "resourceType": "Service Fabric",
        "clusterName": "[parameters('clusterName')]"
      }
    },
    {
      "apiVersion": "[variables('vmssApiVersion')]",
      "type": "Microsoft.Compute/virtualMachineScaleSets",
      "name": "[variables('vmNodeType0Name')]",
      "location": "[variables('computeLocation')]",
      "dependsOn": [
        "[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]",
        "[concat('Microsoft.Storage/storageAccounts/', variables('uniqueStringArray0')[0])]",
        "[concat('Microsoft.Storage/storageAccounts/', variables('uniqueStringArray0')[1])]",
        "[concat('Microsoft.Storage/storageAccounts/', variables('uniqueStringArray0')[2])]",
        "[concat('Microsoft.Storage/storageAccounts/', variables('uniqueStringArray0')[3])]",
        "[concat('Microsoft.Storage/storageAccounts/', variables('uniqueStringArray0')[4])]",
        "[concat('Microsoft.Network/loadBalancers/', concat('LB','-', parameters('clusterName'),'-',variables('vmNodeType0Name')))]",
        "[concat('Microsoft.Storage/storageAccounts/', variables('supportLogStorageAccountName'))]",
        "[concat('Microsoft.Storage/storageAccounts/', variables('applicationDiagnosticsStorageAccountName'))]"
      ],
      "properties": {
        "overprovision": "[variables('overProvision')]",
        "upgradePolicy": {
          "mode": "Automatic"
        },
        "virtualMachineProfile": {
          "extensionProfile": {
            "extensions": [
              {
                "name": "[concat('ServiceFabricNodeVmExt','_vmNodeType0Name')]",
                "properties": {
                  "type": "ServiceFabricNode",
                  "autoUpgradeMinorVersion": false,
                  "protectedSettings": {
                    "StorageAccountKey1": "[listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('supportLogStorageAccountName')),'2015-05-01-preview').key1]",
                    "StorageAccountKey2": "[listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('supportLogStorageAccountName')),'2015-05-01-preview').key2]"
                  },
                  "publisher": "Microsoft.Azure.ServiceFabric",
                  "settings": {
                    "clusterEndpoint": "[reference(parameters('clusterName')).clusterEndpoint]",
                    "nodeTypeRef": "[variables('vmNodeType0Name')]",
                    "dataPath": "D:\\\\SvcFab",
                    "durabilityLevel": "Bronze",
                    "nicPrefixOverride": "[variables('subnet0Prefix')]",
                    "certificate": {
                      "thumbprint": "[parameters('certificateThumbprint')]",
                      "x509StoreName": "[parameters('certificateStoreValue')]"
                    }
                  },
                  "typeHandlerVersion": "1.0"
                }
              },
              {
                "name": "[concat('VMDiagnosticsVmExt','_vmNodeType0Name')]",
                "properties": {
                  "type": "IaaSDiagnostics",
                  "autoUpgradeMinorVersion": true,
                  "protectedSettings": {
                    "storageAccountName": "[variables('applicationDiagnosticsStorageAccountName')]",
                    "storageAccountKey": "[listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('applicationDiagnosticsStorageAccountName')),'2015-05-01-preview').key1]",
                    "storageAccountEndPoint": "https://core.windows.net/"
                  },
                  "publisher": "Microsoft.Azure.Diagnostics",
                  "settings": {
                    "WadCfg": {
                      "DiagnosticMonitorConfiguration": {
                        "overallQuotaInMB": "50000",
                        "EtwProviders": {
                          "EtwEventSourceProviderConfiguration": [
                            {
                              "provider": "Microsoft-ServiceFabric-Actors",
                              "scheduledTransferKeywordFilter": "1",
                              "scheduledTransferPeriod": "PT5M",
                              "DefaultEvents": {
                                "eventDestination": "ServiceFabricReliableActorEventTable"
                              }
                            },
                            {
                              "provider": "Microsoft-ServiceFabric-Services",
                              "scheduledTransferPeriod": "PT5M",
                              "DefaultEvents": {
                                "eventDestination": "ServiceFabricReliableServiceEventTable"
                              }
                            }
                          ],
                          "EtwManifestProviderConfiguration": [
                            {
                              "provider": "cbd93bc2-71e5-4566-b3a7-595d8eeca6e8",
                              "scheduledTransferLogLevelFilter": "Information",
                              "scheduledTransferKeywordFilter": "4611686018427387904",
                              "scheduledTransferPeriod": "PT5M",
                              "DefaultEvents": {
                                "eventDestination": "ServiceFabricSystemEventTable"
                              }
                            }
                          ]
                        }
                      }
                    },
                    "StorageAccount": "[variables('applicationDiagnosticsStorageAccountName')]"
                  },
                  "typeHandlerVersion": "1.5"
                }
              }
            ]
          },
          "networkProfile": {
            "networkInterfaceConfigurations": [
              {
                "name": "[concat(variables('nicName'), '-0')]",
                "properties": {
                  "ipConfigurations": [
                    {
                      "name": "[concat(variables('nicName'),'-',0)]",
                      "properties": {
                        "loadBalancerBackendAddressPools": [
                          {
                            "id": "[variables('lbPoolID0')]"
                          }
                        ],
                        "loadBalancerInboundNatPools": [
                          {
                            "id": "[variables('lbNatPoolID0')]"
                          }
                        ],
                        "subnet": {
                          "id": "[variables('subnet0Ref')]"
                        }
                      }
                    }
                  ],
                  "primary": true
                }
              }
            ]
          },
          "osProfile": {
            "adminPassword": "[parameters('adminPassword')]",
            "adminUsername": "[parameters('adminUsername')]",
            "computernamePrefix": "[variables('vmNodeType0Name')]",
            "secrets": [
              {
                "sourceVault": {
                  "id": "[parameters('sourceVaultValue')]"
                },
                "vaultCertificates": [
                  {
                    "certificateStore": "[parameters('certificateStoreValue')]",
                    "certificateUrl": "[parameters('certificateUrlValue')]"
                  }
                ]
              }
            ]
          },
          "storageProfile": {
            "imageReference": {
              "publisher": "[parameters('vmImagePublisher')]",
              "offer": "[parameters('vmImageOffer')]",
              "sku": "[parameters('vmImageSku')]",
              "version": "[parameters('vmImageVersion')]"
            },
            "osDisk": {
              "vhdContainers": [
                "[concat(reference(concat('Microsoft.Storage/storageAccounts/', variables('uniqueStringArray0')[0]), variables('storageApiVersion')).primaryEndpoints.blob, variables('vmStorageAccountContainerName'))]",
                "[concat(reference(concat('Microsoft.Storage/storageAccounts/', variables('uniqueStringArray0')[1]), variables('storageApiVersion')).primaryEndpoints.blob, variables('vmStorageAccountContainerName'))]",
                "[concat(reference(concat('Microsoft.Storage/storageAccounts/', variables('uniqueStringArray0')[2]), variables('storageApiVersion')).primaryEndpoints.blob, variables('vmStorageAccountContainerName'))]",
                "[concat(reference(concat('Microsoft.Storage/storageAccounts/', variables('uniqueStringArray0')[3]), variables('storageApiVersion')).primaryEndpoints.blob, variables('vmStorageAccountContainerName'))]",
                "[concat(reference(concat('Microsoft.Storage/storageAccounts/', variables('uniqueStringArray0')[4]), variables('storageApiVersion')).primaryEndpoints.blob, variables('vmStorageAccountContainerName'))]"
              ],
              "name": "vmssosdisk",
              "caching": "ReadOnly",
              "createOption": "FromImage"
            }
          }
        }
      },
      "sku": {
        "name": "[variables('vmNodeType0Size')]",
        "capacity": "[parameters('nt0InstanceCount')]",
        "tier": "Standard"
      },
      "tags": {
        "resourceType": "Service Fabric",
        "clusterName": "[parameters('clusterName')]"
      }
    },
    {
      "apiVersion": "2016-09-01",
      "type": "Microsoft.ServiceFabric/clusters",
      "name": "[parameters('clusterName')]",
      "location": "[parameters('clusterLocation')]",
      "dependsOn": [
        "[concat('Microsoft.Storage/storageAccounts/', variables('supportLogStorageAccountName'))]"
      ],
      "properties": {
        "certificate": {
          "thumbprint": "[parameters('certificateThumbprint')]",
          "x509StoreName": "[parameters('certificateStoreValue')]"
        },
        "clientCertificateCommonNames": [],
        "clientCertificateThumbprints": [],
        "clusterState": "Default",
        "diagnosticsStorageAccountConfig": {
          "blobEndpoint": "[reference(concat('Microsoft.Storage/storageAccounts/', variables('supportLogStorageAccountName')), variables('storageApiVersion')).primaryEndpoints.blob]",
          "protectedAccountKeyName": "StorageAccountKey1",
          "queueEndpoint": "[reference(concat('Microsoft.Storage/storageAccounts/', variables('supportLogStorageAccountName')), variables('storageApiVersion')).primaryEndpoints.queue]",
          "storageAccountName": "[variables('supportLogStorageAccountName')]",
          "tableEndpoint": "[reference(concat('Microsoft.Storage/storageAccounts/', variables('supportLogStorageAccountName')), variables('storageApiVersion')).primaryEndpoints.table]"
        },
        "fabricSettings": [
          {
            "parameters": [
              {
                "name": "ClusterProtectionLevel",
                "value": "[parameters('clusterProtectionLevel')]"
              }
            ],
            "name": "Security"
          }
        ],
        "managementEndpoint": "[concat('https://',reference(concat(variables('lbIPName'),'-','0')).dnsSettings.fqdn,':',variables('nt0fabricHttpGatewayPort'))]",
        "nodeTypes": [
          {
            "name": "[variables('vmNodeType0Name')]",
            "applicationPorts": {
              "endPort": "[variables('nt0applicationEndPort')]",
              "startPort": "[variables('nt0applicationStartPort')]"
            },
            "clientConnectionEndpointPort": "[variables('nt0fabricTcpGatewayPort')]",
            "durabilityLevel": "Bronze",
            "ephemeralPorts": {
              "endPort": "[variables('nt0ephemeralEndPort')]",
              "startPort": "[variables('nt0ephemeralStartPort')]"
            },
            "httpGatewayEndpointPort": "[variables('nt0fabricHttpGatewayPort')]",
            "isPrimary": true,
            "vmInstanceCount": "[parameters('nt0InstanceCount')]"
          }
        ],
        "provisioningState": "Default",
        "reliabilityLevel": "Silver",
        "upgradeMode": "Automatic",
        "vmImage": "Windows"
      },
      "tags": {
        "resourceType": "Service Fabric",
        "clusterName": "[parameters('clusterName')]"
      }
    }
  ],
  "outputs": {
    "clusterProperties": {
      "value": "[reference(parameters('clusterName'))]",
      "type": "object"
    }
  }
}

Good luck!

Leave a Reply

Your email address will not be published.