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.

Tuesday 10 October 2017

Making a Sitecore PaaS package (WDP) for your module

For one of my first posts on Sitecore PaaS I'll run through one of the more basic things you may need to know for your deployments: adding a module to your Sitecore PaaS installation.  I'll use the Sitecore Powershell Extensions module (which as far as I know doesn't have a package already) as an example.  This is an easy one to start with, as it will only really be running on your CM server so it cuts down on the need for transforms and multiple packages.

Firstly, we need to create what Sitecore calls a "web deploy package" (WDP).  This is just a zip file containing our module files, and ending in .scwdp.zip.  As you can find from a dig around in the Sitecore Azure Module Documentation in the Generating an inital WDP section, you can create this WDP from a normal Sitecore module installation .zip or .update package.

The command is:
ConvertTo-SCModuleWebDeployPackage [-Path] <string> [[-Destination] <string>]

Which in our case will be:
ConvertTo-SCModuleWebDeployPackage -Path "Sitecore PowerShell Extensions-4.5 for Sitecore 8.zip" -Destination "SPE.scwdp.zip"

This generates our .scwdp.zip with the same files that were in the installer zip, and also creates a .dacpac file to amend our DB with any items from the installer zip.  The deploy package will take an "application path" parameter no matter what, and if you have any .dacpacs (new items) it will also automatically add parameters for whichever database you are updating (in our case, core and master).

Next up, we need to create an ARM template which will install our WDP.  A good starting point is taking a look at the WFFM templates, which are relatively straightforward.  I've created a gist for the SPE deployment template, and you can see the end of the post for how we pass in our parameters.

You'll note we're only adding one new resource in the ARM template, which is a MSDeploy extension.  This is how we use ARM to deploy things to Azure.


"resources": [
    {
      "name": "[concat(variables('cmWebAppNameTidy'), '/', 'MSDeploy')]",
      "type": "Microsoft.Web/sites/extensions",
      "location": "[parameters('location')]",
      "apiVersion": "[variables('webApiVersion')]",
      "properties": {
        "addOnPackages": [
          {
            "dbType": "SQL",
            "connectionString": "[concat('Data Source=tcp:', variables('sqlServerFqdnTidy'), ',1433;Initial Catalog=master;User Id=', parameters('sqlServerLogin'), '@', variables('sqlServerNameTidy'), ';Password=', parameters('sqlServerPassword'), ';')]",
            "packageUri": "[parameters('cmMsDeployPackageUrl')]",
            "setParameters": {
              "Application Path": "[variables('cmWebAppNameTidy')]",
              "Core Admin Connection String": "[concat('Encrypt=True;TrustServerCertificate=False;Data Source=', variables('sqlServerFqdnTidy'), ',1433;Initial Catalog=',variables('coreSqlDatabaseNameTidy'),';User Id=', parameters('sqlServerLogin'), ';Password=', parameters('sqlServerPassword'), ';')]",
              "Master Admin Connection String": "[concat('Encrypt=True;TrustServerCertificate=False;Data Source=', variables('sqlServerFqdnTidy'), ',1433;Initial Catalog=',variables('masterSqlDatabaseNameTidy'),';User Id=', parameters('sqlServerLogin'), ';Password=', parameters('sqlServerPassword'), ';')]"
            }
          }
        ]
      }
    }
  ]

You'll notice we're passing in the application path, and core/master connection strings parameters to the deployment, as mentioned above.

Finally, we need to add the extension to our Sitecore deployment.  We do this in our parameters file for our main deployment, in a "modules" parameter:

"modules": {
  "value": {
    "items": [
      {
        "name": "spe",
        "templateLink": "https://yoursite/sitecore/templates/azuredeploy.spe.json",
        "parameters": {
          "cmMsDeployPackageUrl": "https://yoursite/sitecore/modules/SPE.scwdp.zip"
        }
      }
    ]
  }
}

You'll see there is only one parameter we actually need to pass in: the WDP we created above.  This is because Sitecore automatically populates the other parameters with the values we pass in to the main ARM deployment.  This includes the CM app name (where the package is installed), SQL username and password (for installing the .dacpac which has the new items), etc.

And that's all there is to it!  Enjoy adding your additional modules and install packages to Sitecore PaaS in a nice modular way.