Showing posts with label arm. Show all posts
Showing posts with label arm. Show all posts

Tuesday, 5 December 2017

Enhancing Sitecore ARM templates to be production-ready

The Sitecore ARM templates are great for most environments, and better yet they are extensible, however in a production environment you're probably going to need a few more things in your environment; things like staging slots, your own hostname(s), and maybe some storage / networking / traffic manager.

As I mentioned, Sitecore has made the their templates in a fairly modular fashion: you have your base deployment which takes all parameters, and runs a sub-deployment for '-infrastructure' (your Azure resources), '-application' (Sitecore package installation), and a sub-deployment for each Sitecore module.
This gives us the option of just running a separate template after running the base Sitecore templates, or we can integrate our additional deployment as another sub-deployment in the base template in the same way.  Obviously we want to keep our enhancements as separate as possible so that when there is an update to Sitecore or their templates it doesn't mean a massive and painful re-write of our enhancements.

I've uploaded an ARM template as a gist, and included the extra Powershell code below. They do the following:
  • Change your CM + CD hosting plan to Standard
  • Add staging slots to CM + CD
  • Copy files from your CM + CD to the staging slot
  • Add custom hostnames
  • Add an SSL certificate and enable HTTPS for all your hostnames
  • Add connection strings to your Rep / Prc web apps so you can swap them
  • Add custom firewall rules
See the gist of the ARM template which you can run on its own, or as a sub-deployment, in which case you should place this in the "nested" folder, along with the infrastructure.json file. You can then include this into your primary ARM template by duplicating the "-infrastructure" (deployment) resource and changing the name to "-infrastructure-prod" and the reference to this new json file.

You can upload your SSL cert through the ARM template, by providing the binary like in the provided gist; alternatively you can upload the certificate to a key vault (which also creates a secret in the vault). If you choose to use the vault, you must give the ARM deployment service access to your key vault by running the following Powershell command (the service principal ID is an Azure Guid, so the same for everyone):
Set-AzureRmKeyVaultAccessPolicy -VaultName your-keyvault-name -ServicePrincipalName abfa0a7c-a6b6-4736-8310-5855508787cd -PermissionsToSecrets get

You can then substitute the certificate section in the gist with the following, which uses the key vault ID and secret name:
{
  "apiVersion": "[variables('certificateApiVersion')]",
  "location": "[parameters('location')]",
  "name": "[variables('sslCertificateNameTidy')]",
  "type": "Microsoft.Web/certificates",
  "properties": {
    "keyVaultId": "[parameters('keyVaultId')]",
    "keyVaultSecretName": "[parameters('sslKeyVaultCertificateName')]"
  } 
}

In your Powershell script, after the part which does the deployment, add the following to copy the app settings and files to your staging slots:

function copyAppSettings($rg, $webApp, $slot) {
 $props = (Invoke-AzureRmResourceAction -ResourceGroupName $rg `
    -ResourceType Microsoft.Web/sites/Config -Name $webApp/appsettings `
    -Action list -ApiVersion 2015-08-01 -Force).Properties

 $hash = @{}
 $props | Get-Member -MemberType NoteProperty | % { $hash[$_.Name] = $props.($_.Name) }

 Set-AzureRMWebAppSlot -ResourceGroupName $rg -Name $webApp -Slot $slot -AppSettings $hash
}

# Copy app settings to staging slot
Write-Host "Copying app settings to staging slots";
copyAppSettings $ResourceGroupName "$($DeploymentId)-cd" "cd-staging"
copyAppSettings $ResourceGroupName "$($DeploymentId)-cm" "cm-staging"
Write-Host "Done copying app settings";
  
# Copy files to staging slot
Write-Host "Copying files to staging CD";
..\sync_slots -SubscriptionId $SubscriptionId -ResourceGroupName $ResourceGroupName -WebAppName "$($DeploymentId)-cd" -SlotName "cd-staging"
Write-Host "Copying files to staging CM";
..\sync_slots -SubscriptionId $SubscriptionId -ResourceGroupName $ResourceGroupName -WebAppName "$($DeploymentId)-cm" -SlotName "cm-staging"
Write-Host "Done copying files to staging";

Thursday, 26 October 2017

Sitecore PaaS conditional deployments

For our current project our client has a production Sitecore environment and DR environment which are almost identical.  Production is always up and running, however DR is spun-up on demand, in a secondary region, when the production region goes down for any reason.  The only difference between the two environments is the data: the SQL servers are using Azure's active geo-replication and failover groups so that the secondary region's data is always up and ready to go, and to fails over automatically.  This is more costly, but enables us to meet the client's RPO and RTO (as opposed to the backup and restore method), and specify the grace period with data loss if the RTO isn't going to be met.
As a side-note, the secondary SQL servers, DBs, and failover groups are located in the primary resource group, not the DR resource group. This is so that the DR resource group can simply be deleted when the primary region comes back on-line.  Don't forget point 1 of resource groups: the resources inside should share the same lifecycle.

But moving on to the topic of the title: the DR environment is identical to prod minus the SQL servers and databases.  There's no point scripting up two ARM templates when things are this similar, and fortunately Azure has us covered with ARM template conditions.  This is the best use for them that I've found so far, but you could also use it for any other environments/deployments which are quite similar.

First, in the Powershell script (we're using a modified version of the Sitecore Azure toolkit's Sitecore.Cloud.Cmdlets.psm1 Start-SitecoreAzureDeployment function), we dynamically set the template property based on a flag:
if($IsDR) {
  $paramJson | Add-Member -NotePropertyName "Environment" -NotePropertyValue @{ "value" = "DR" } -Force
}

Then in the template we add our Environment property:
"Environment": {
    "type": "string",
    "allowedValues": [
        "Prod",
        "DR"
    ],
    "defaultValue": "Prod",
    "metadata": {
        "description": "Select whether this environment is prod (requires SQL) or DR (no SQL required)."
    }
}

Finally, in the resource itself we add the condition to only create SQL servers if it's not DR:
{
  "condition": "[not(equals(parameters('Environment'), 'DR'))]",
  "type": "Microsoft.Sql/servers",
  "name": "[variables('dbServerNameTidy')]",
  ... etc.
}

You can also use the conditions in your properties, which is required in the DR deployment to get the old FQDN of the original prod SQL servers.
"sqlServerFqdn": "[reference(if(equals(parameters('Environment'), 'DR'), resourceId(parameters('prodResourceGroup'), 'Microsoft.Sql/servers', variables('oldDbServerNameTidy')), resourceId('Microsoft.Sql/servers', variables('dbServerNameTidy'))), variables('dbApiVersion')).fullyQualifiedDomainName]",

Easy as that! Now you have one ARM template which can be used for both environments, just by passing the -IsDr parameter to your powershell deployment script.
You could easily modify the Environment parameter to contain more values for different environments if you have more which are similar.  However if they're more than a little different it's probably worth having a different nested template, or entire set of templates, for each environment.