diff --git a/extensions/azurehybridtoolkit/notebooks/hybridbook/_data/toc.yml b/extensions/azurehybridtoolkit/notebooks/hybridbook/_data/toc.yml
index a0e2137144..c492bcbf97 100644
--- a/extensions/azurehybridtoolkit/notebooks/hybridbook/_data/toc.yml
+++ b/extensions/azurehybridtoolkit/notebooks/hybridbook/_data/toc.yml
@@ -64,6 +64,8 @@
sections:
- title: Backup Database to Blob Storage
url: hadr/backup-to-blob
+ - title: Create Failover Cluster
+ url: hadr/configure-failover
- title: Offline Migration
url: /offline-migration/readme
not_numbered: true
diff --git a/extensions/azurehybridtoolkit/notebooks/hybridbook/content/hadr/configure-failover.ipynb b/extensions/azurehybridtoolkit/notebooks/hybridbook/content/hadr/configure-failover.ipynb
new file mode 100644
index 0000000000..a72b732325
--- /dev/null
+++ b/extensions/azurehybridtoolkit/notebooks/hybridbook/content/hadr/configure-failover.ipynb
@@ -0,0 +1,405 @@
+{
+ "metadata": {
+ "kernelspec": {
+ "name": "powershell",
+ "display_name": "PowerShell",
+ "language": "powershell"
+ },
+ "language_info": {
+ "name": "powershell",
+ "codemirror_mode": "shell",
+ "mimetype": "text/x-sh",
+ "file_extension": ".ps1"
+ }
+ },
+ "nbformat_minor": 2,
+ "nbformat": 4,
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "source": [
+ "# Create and configure Failover Cluster\n",
+ "\n",
+ "## Description\n",
+ "\n",
+ "Notebook to walk through creating a failover cluster on on-premises SQL Server Database."
+ ],
+ "metadata": {
+ "azdata_cell_guid": "6af59d69-ade7-480a-b33e-52a86fe5bfd3"
+ }
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "Note: before running the steps below, make sure to have the following:\n",
+ "\n",
+ "- A resource group with a domain controller.\n",
+ "- One or more domain joined VMs in Azure running SQL Server 2016 (or later) in the _same_ availability set or _different_ availability zones that have been registered with the SQL IaaS Agent extension\n",
+ "- Domain joined VMs must have either a \"Enterprise\" or \"Developer\" image SKU to create a cluster in this notebook\n",
+ "\n",
+ "Tutorial on how to set up a domain with VMs can be found [here.](https://docs.microsoft.com/en-us/azure/azure-sql/virtual-machines/windows/availability-group-manually-configure-prerequisites-tutorial)\n",
+ "\n",
+ "Here is a listing of variables that will be used to create the failover cluster:\n",
+ "\n",
+ "| Line # | Name | Example | Description |\n",
+ "| --- | --- | --- | --- |\n",
+ "| 1 | **ResourceGroup** | \"TestRG1\" | Name of new or existing resource group (RG). An Azure Resource Group is a collection of Azure resources that share the same permissions, policies, etc. |\n",
+ "| 2 | **Location** | \"East US\" | Value representing the region or location of the RG. See [Azure Geographies](https://azure.microsoft.com/en-us/global-infrastructure/geographies/ \"https://azure.microsoft.com/en-us/global-infrastructure/geographies/\") for more information. |\n",
+ "| 3 | **PublisherName** | \"MicrosoftSQLServer\" | Name of publisher that offers SQL Server configurations that can be installed on virtual machines. Default is MicrosoftSQLServer |\n",
+ "| 4 | **StorageAccountName** | \"TestStorageAccount\" | Name of the storage account that will be used as a file share witness. See [Cloud Witness](https://docs.microsoft.com/en-us/windows-server/failover-clustering/deploy-cloud-witness) for more information. |\n",
+ "| 5 | **StorageSku** | \"Standard_LRS\" | Name of SKU for a storage account. There are SKUs for different types of storage such as Standard_LRS for Standard Locally Redundant Storage. See [Storage Redundancy](https://docs.microsoft.com/en-us/azure/storage/common/storage-redundancy) for more information. |\n",
+ "| 6 | **ClusterName** | \"TestCluster\" | Name of the failover cluster that will be created. A failover cluster is a group of independent computers that work together to increase the availability and scalability of clustered roles. See [Failover Cluster](https://docs.microsoft.com/en-us/windows-server/failover-clustering/failover-clustering-overview) for more information. |\n",
+ "| 7 | **FQDN** | \"test.domain.com\" | Fully Qualified Domain Name, this localized URL address is created as shown in the domain setup. |\n",
+ "| 8 | **ServerSku** | \"SQL2016SP1-WS2016\" | SQL Server SKU offer name. The SKU determines the edition and operating system of a SQL Server VM. Use the same type of SKU for all SQL VMs in the domain. Make sure to use Developer or Enterprise edition when creating the SQL VMs. |\n",
+ "| 9 | **SqlVm1Name** | \"Server1\" | Name of the first SQL server added on the domain as shown in the domain setup. |\n",
+ "| 10 | **SqlVm2Name** | \"Server2\" | Name of the second SQL server added on the domain (more SQL servers can be added to the cluster as explained below) |\n",
+ "| 11 | **ServiceAccount** | \"serviceaccount@domain.com\" | Username for the SQL server service account for the domain (password is required to run commands). Any active directory account with sufficient permissions such as the installation account may be used as the service account. |\n",
+ "| 12 | **OperatorAccount** | \"operatoraccount@domain.com\" | Username for the cluster operator account, may or may not be the same as the service account. |\n",
+ "| 13 | **BootstrapAccount** | \"bootstrapaccount@domain.com\" | Username for the bootstrap account, may or may not be the same as the service account |",
+ ],
+ "metadata": {
+ "azdata_cell_guid": "b911ea4f-a3d8-4ac1-bff6-6c5eb1b514f9"
+ }
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "#Set general parameters\r\n",
+ "$ResourceGroupName = \"\" # Resource group name (it should be case insensitive)\r\n",
+ "$Location = \"\" # Specify the valid Location such as 'West US 2','EASTUS' etc...\r\n",
+ "$PublisherName = \"\" # Name of Publisher, Default would be 'MicrosoftSQLServer'\r\n",
+ "\r\n",
+ "#Create Storage Account\r\n",
+ "$StorageAccountName = \"\" # Name of Storage Account used for cloud witness.\r\n",
+ "$StorageSku = \"\" # Specify a valid storage SKU...Such as 'Standard_LRS'\r\n",
+ "\r\n",
+ "#Define Cluster Media\r\n",
+ "$ClusterName = \"\" # The name of the SQL VM Cluster.\r\n",
+ "$FQDN = \"\" # Name of the domain set by Domain Controller.\r\n",
+ "$ServerSku = \"\" # SKU of Servers, must be either \"Enterprise\" or \"Developer\".\r\n",
+ "\r\n",
+ "#Add VMs to the cluster\r\n",
+ "$SqlVm1Name = \"\" # The name of the first SQL VM.\r\n",
+ "$SqlVm2Name = \"\" # The name of the second SQL VM.\r\n",
+ "$ServiceAccount = \"\" # SQL Server Service account\r\n",
+ "$ServiceAccountPassword = \"\" # Password for SQL Server Service Account\r\n",
+ "$OperatorAccount = \"\" # Cluster Operator Account (may be same or different as Service Account)\r\n",
+ "$OperatorAccountPassword = \"\" # Password for Cluster Operator Account\r\n",
+ "$BootstrapAccount = \"\" # Bootstrap Account (may be same or different as Service Account)\r\n",
+ "$BootstrapAccountPassword = \"\" # Password for Bootstrap Account\r\n",
+ "\r\n",
+ "$SqlPath = \"sqlserver:\\sql\\$($env:COMPUTERNAME)\" #This script will generate Sql Path"
+ ],
+ "metadata": {
+ "azdata_cell_guid": "7ad525ec-4993-4e14-9677-4f77433b2123",
+ "tags": []
+ },
+ "outputs": [],
+ "execution_count": null
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "Steps of this procedure include:\n",
+ "\n",
+ "1. Connect to Azure subscription\n",
+ "2. Provision resource group for SQL VM migration\n",
+ "3. Create a storage account\n",
+ "4. Define cluster metadata\n",
+ "5. Add VMs to the cluster\n",
+ "6. Validate cluster"
+ ],
+ "metadata": {
+ "azdata_cell_guid": "34dad8cf-9457-474c-b6a3-34b003e9c71a"
+ }
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "### Sign in to Azure \r\n",
+ "Sign in to your Azure Subscription with the _Connect-AzAccount_ command and follow the on-screen directions."
+ ],
+ "metadata": {
+ "azdata_cell_guid": "5dd9519d-3957-46ef-8988-440a043535b2"
+ }
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "Connect-AzAccount"
+ ],
+ "metadata": {
+ "azdata_cell_guid": "18c920f2-a19a-49d6-9766-2d7539f6fe43"
+ },
+ "outputs": [],
+ "execution_count": null
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "### Get Subscription\r\n",
+ "Below command will open a _**Dialouge Box**_ with list of subscriptions. Selecting one of those will set that Subscription for rest of the commands."
+ ],
+ "metadata": {
+ "azdata_cell_guid": "cc6cb8c6-76b7-41a0-ab26-7713e72c2f7d"
+ }
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "$Subscription = Get-AzSubscription | Out-GridView -PassThru\r\n",
+ "Set-AzContext -SubscriptionName $Subscription"
+ ],
+ "metadata": {
+ "azdata_cell_guid": "741c35fa-7923-4200-8c3a-497d62b4ae66"
+ },
+ "outputs": [],
+ "execution_count": null
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "\r\n",
+ "### Create a resource group\r\n",
+ "Create an Azure resource group with _New-AzResourceGroup_. A resource group is a logical container into which Azure resources are deployed and managed."
+ ],
+ "metadata": {
+ "azdata_cell_guid": "455a6002-e5d5-4cb0-9146-8d417917c751"
+ }
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "# Create Azure resource group, if necessary\r\n",
+ "$ResourceGroup = Get-AzResourceGroup -Name $ResourceGroupName\r\n",
+ "\r\n",
+ "if (!$ResourceGroup)\r\n",
+ "{\r\n",
+ " # Need to create a new resource group\r\n",
+ " Write-Output \"Resource Group $ResourceGroupName does not exist. Creating...\"\r\n",
+ " $ResourceGroup = New-AzResourceGroup -Name $ResourceGroupName -Location $Location\r\n",
+ "}"
+ ],
+ "metadata": {
+ "azdata_cell_guid": "d48948e1-3136-4e42-91e7-77a3301ae97d"
+ },
+ "outputs": [],
+ "execution_count": null
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "### Create a storage account\r\n",
+ "Create a standard, general-purpose storage account with LRS replication by using _New-AzStorageAccount_. Next, get the storage account context that defines the storage account you want to use. When acting on a storage account, reference the context instead of repeatedly passing in the credentials. Use the following example to create a storage account called storageaccountazure with locally redundant storage (LRS) and blob encryption (enabled by default)."
+ ],
+ "metadata": {
+ "azdata_cell_guid": "246d062f-e7d1-4f42-ba9f-153a12a286e4"
+ }
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "$StorageAccount = New-AzStorageAccount -ResourceGroupName $ResourceGroupName -Name $StorageAccountName `\r\n",
+ " -SkuName $StorageSku -Location $Location -Kind StorageV2 `\r\n",
+ " -AccessTier Hot -EnableHttpsTrafficOnly $true\r\n",
+ "\r\n",
+ "$Ctx = $StorageAccount.Context"
+ ],
+ "metadata": {
+ "azdata_cell_guid": "17e3421b-a80a-455c-bd6a-25daaff46514"
+ },
+ "outputs": [],
+ "execution_count": null
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "### Create a container\r\n",
+ "Blobs are always uploaded into a container. Blobs can be organized in groups like files in folders.\r\n",
+ "Set the container name, then create the container by using _New-AzStorageContainer_. Set the permissions to blob to allow public access of the files. The container name in this example is quickstartblobs."
+ ],
+ "metadata": {
+ "azdata_cell_guid": "82503754-84ea-47b7-bcaf-31e5012da008"
+ }
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "$ContainerName = \"quickstartblobs\"\r\n",
+ "New-AzStorageContainer -Name $ContainerName -Context $Ctx -Permission blob"
+ ],
+ "metadata": {
+ "azdata_cell_guid": "c4557040-5ff7-4f31-9243-01b850270b90",
+ "tags": []
+ },
+ "outputs": [],
+ "execution_count": null
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "### Get Azure Storage Acount Key\r\n",
+ "This script will get the key for Storage Account which is been created."
+ ],
+ "metadata": {
+ "azdata_cell_guid": "f48b23bf-01bc-48df-afed-ad3eea69c41d"
+ }
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "$StorageAccountKey = `\r\n",
+ " (Get-AzStorageAccountKey `\r\n",
+ " -ResourceGroupName $ResourceGroupName `\r\n",
+ " -Name $StorageAccountName).Value[0]"
+ ],
+ "metadata": {
+ "azdata_cell_guid": "e450bc4a-3373-4c7e-b0d0-f334712298b2",
+ "tags": []
+ },
+ "outputs": [],
+ "execution_count": null
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "### Get Azure Storage Container Uri\r\n",
+ "The following script can be used to get the Uri of Storage container."
+ ],
+ "metadata": {
+ "azdata_cell_guid": "5f7e616b-1e9f-4640-b61f-78d4327c5562"
+ }
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ " $StorageUri = (Get-AzStorageAccount -ResourceGroupName $ResourceGroupName -Name $StorageAccountName | Get-AzStorageContainer | Where-Object { $_.Name -eq $AzureContainerName }).CloudBlobContainer.Uri.AbsoluteUri "
+ ],
+ "metadata": {
+ "azdata_cell_guid": "615d8e6e-2cbf-4001-8da0-1826185a06bf"
+ },
+ "outputs": [],
+ "execution_count": null
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "### **Azure SQL VM Offers**\n",
+ "\n",
+ "Run the following command to get updated list of offers for Microsoft SQL Server in your location. Pick the offer that matches the operating system version of the VM servers.\n",
+ "\n",
+ "Note that the SQL Version is first then appended with an operating system version. E.g.: \"WS2019\" means Windows Server 2019. Along with various versions of Windows Servers, there are also enterprise Linux versions such as RedHat Enterprise, Suse Enterprise, and Ubuntu. Some versions are BYOL (Bring Your Own License) aka [Hybrid Benefit](https://azure.microsoft.com/en-us/pricing/hybrid-benefit/)."
+ ],
+ "metadata": {
+ "azdata_cell_guid": "c17d224e-e514-4dfd-b181-06d2cde378e4"
+ }
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "$Offer = Get-AzVMImageOffer -Location $Location -Publisher $PublisherName | Out-GridView -PassThru"
+ ],
+ "metadata": {
+ "azdata_cell_guid": "6b2fa291-43cb-4619-a010-3a373dababcc"
+ },
+ "outputs": [],
+ "execution_count": null
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "### **Define cluster metadata**\n",
+ "\n",
+ "The following script is used to define the metadata for Windows Server Failover Cluster so that when the first SQL Server VM is added, the cluster is created as defined."
+ ],
+ "metadata": {
+ "azdata_cell_guid": "000a1320-fecf-4e0b-8d1f-00ab39fc44ce"
+ }
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "$StorageAccountUrl = \"https://$StorageAccountName.blob.core.windows.net/\"\r\n",
+ "$SecureSAKey = ConvertTo-SecureString $StorageAccountKey -AsPlainText -Force\r\n",
+ "\r\n",
+ "$group = New-AzSqlVMGroup -Name $ClusterName -Location $Location `\r\n",
+ " -ResourceGroupName $ResourceGroupName -Offer $Offer.Offer `\r\n",
+ " -Sku $ServerSku -DomainFqdn $FQDN -ClusterOperatorAccount $OperatorAccount `\r\n",
+ " -ClusterBootstrapAccount $BootstrapAccount -SqlServiceAccount $ServiceAccount `\r\n",
+ " -StorageAccountUrl $StorageAccountUrl `\r\n",
+ " -StorageAccountPrimaryKey $SecureSAKey"
+ ],
+ "metadata": {
+ "azdata_cell_guid": "f59b2416-a41f-43c0-85ea-1e113e6f4221",
+ "tags": []
+ },
+ "outputs": [],
+ "execution_count": null
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "### **Add VMs to the cluster**\n",
+ "\n",
+ "Adding the first SQL Server VM to the cluster creates the cluster. The Update-AzSqlVM command creates the cluster with the name previously given, installs the cluster role on the SQL Server VMs, and adds them to the cluster. Subsequent uses of the command adds more SQL Server VMs to the newly created cluster. (This will take some time, please wait for the process to finish.)"
+ ],
+ "metadata": {
+ "azdata_cell_guid": "cca91ca6-bf10-4e32-9b4f-80133049c1b5"
+ }
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "$sqlvm1 = Get-AzSqlVM -Name $SqlVm1Name -ResourceGroupName $ResourceGroupName\r\n",
+ "$sqlvm2 = Get-AzSqlVM -Name $SqlVm2Name -ResourceGroupName $ResourceGroupName\r\n",
+ "$SecureOAPassword = ConvertTo-SecureString $OperatorAccountPassword -AsPlainText -Force\r\n",
+ "$SecureSAPassword = ConvertTo-SecureString $ServiceAccountPassword -AsPlainText -Force\r\n",
+ "$SecureBAPassword = ConvertTo-SecureString $BootstrapAccountPassword -AsPlainText -Force\r\n",
+ "\r\n",
+ "$sqlvmconfig1 = Set-AzSqlVMConfigGroup -SqlVM $sqlvm1 `\r\n",
+ " -SqlVMGroup $group -ClusterOperatorAccountPassword $SecureOAPassword `\r\n",
+ " -SqlServiceAccountPassword $SecureSAPassword `\r\n",
+ " -ClusterBootstrapAccountPassword $SecureBAPassword\r\n",
+ "\r\n",
+ "Update-AzSqlVM -ResourceId $sqlvm1.ResourceId -SqlVM $sqlvmconfig1\r\n",
+ "\r\n",
+ "$sqlvmconfig2 = Set-AzSqlVMConfigGroup -SqlVM $sqlvm2 `\r\n",
+ " -SqlVMGroup $group -ClusterOperatorAccountPassword $SecureOAPassword `\r\n",
+ " -SqlServiceAccountPassword $SecureSAPassword `\r\n",
+ " -ClusterBootstrapAccountPassword $SecureBAPassword\r\n",
+ "\r\n",
+ "Update-AzSqlVM -ResourceId $sqlvm2.ResourceId -SqlVM $sqlvmconfig2"
+ ],
+ "metadata": {
+ "azdata_cell_guid": "9b21ab69-5be2-4d09-ac4b-3d43521a1188",
+ "tags": []
+ },
+ "outputs": [],
+ "execution_count": null
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "### **Validate cluster (done locally on server)**\n",
+ "\n",
+ "Connect to a SQL VM in the cluster (ex. RDP) and run the below command via Powershell.\n",
+ "\n",
+ "For a failover cluster to be supported by Microsoft, it must pass cluster validation. Failure to do so leaves your cluster in an unsupported state."
+ ],
+ "metadata": {
+ "azdata_cell_guid": "63b41df3-6fe6-421e-9587-e5d65a0a5592"
+ }
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "Test-Cluster"
+ ],
+ "metadata": {
+ "azdata_cell_guid": "e195e874-6178-4f87-b5e8-94524cbc6ea1"
+ },
+ "outputs": [],
+ "execution_count": null
+ }
+ ]
+}
diff --git a/extensions/azurehybridtoolkit/notebooks/hybridbook/content/hadr/readme.md b/extensions/azurehybridtoolkit/notebooks/hybridbook/content/hadr/readme.md
index f76ca5e970..b2898ae6ce 100644
--- a/extensions/azurehybridtoolkit/notebooks/hybridbook/content/hadr/readme.md
+++ b/extensions/azurehybridtoolkit/notebooks/hybridbook/content/hadr/readme.md
@@ -5,4 +5,9 @@
Notebooks to help with HADR tasks in a Hybrid Cloud environment.
## Notebooks in this Chapter
+
+### Blob Storage Backup:
- [Backup Database to Blob Storage](backup-to-blob.ipynb)
+
+### Availability Group Configuration:
+- [Create Failover Cluster](configure-failover.ipynb)