The Azure Synapse Analytics Managed Virtual Network

If you’ve ever created an Azure Synapse Analytics workspace, you may recall seeing an entry about managed virtual networks.

I’m pretty sure virtual networks are what we see in sci-fi movies from the ’90s.

The managed virtual network option means that any server resources within Azure Synapse Analytics will be hidden behind a virtual network, one which Microsoft controls.

As a quick upshot, having a managed VNet set up means that any Spark pools you create will have subnet segregation, meaning that the Spark machines will be in their own subnet, away from everything else. This provides a bit of cross-pool protection for you automatically. It also performs similar network isolation for your Synapse workspace, keeping it separated from other workspaces. The other big thing it does is create managed private endpoints to the serverless and dedicated SQL pools, which means that any network traffic between these pools and resources in the Synapse workspace will be guaranteed to transit over Azure networks and not the public internet, at least until it gets to you hitting the web.azuresynapse.net URL (and there are additional methods to lock down that part of it that we won’t cover today).

By default, the portal will not create a managed virtual network, so you’ll need to enable it at creation time. You cannot enable or disable the managed virtual network setting after a workspace has been created, so if you make a mistake, you’d need to rebuild the workspace, though you can at least use the same storage account.

One last thing that managed virtual networks offer you is the ability to enable data exfiltration protection.

Data Exfiltration Protection

Data exfiltration protection is another creation-time setting you can enable. In the image above, it’s the setting which reads “Allow outbound data traffic only to approved targets.” I’ll lay out my thoughts on data exfiltration protection up-front: if you need to use it, fine, but beware: it can be really annoying and will definitely limit your ability to use Azure Synapse Analytics. I’ve run into several ways in which data exfiltration protection makes life harder and I’ll cover a couple of them in future posts. If you need it for security reasons, I can understand that, but I recommend not enabling it unless you need it. Unfortunately, it, like managed virtual networks, is a creation-only property at this time, so if you think you’ll need it in the future, it may make sense to bite the bullet today.

The major benefit to data exfiltration protection is that it severely limits the external resources that your Azure Synapse Analytics workspace can reach. It doesn’t prevent users from running queries or performing most normal activities, but it will prevent the pools from accessing resources outside of an approved Azure AD tenant (and the approval list automatically includes the tenant of the creating user). The goal here is to prevent malicious users from using SQL or Spark pools to write data out to remote servers, Azure-based or not.

A Sample ARM Template

I had some trouble finding a good ARM template for Azure Synapse Analytics with these settings enabled, and so decided to create my own. This template is part of an upcoming training that I’ll announce once it’s officially out, but for now, let’s take a look at the template JSON.

{
    "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
      "uniqueSuffix": {
        "type": "String",
        "metadata": {
          "description": "Suffix added to all resource name to make them unique."
        }
      },
      "sqlAdministratorLoginPassword": {
        "type": "SecureString",
        "metadata": {
          "description": "Password for SQL Admin"
        }
      }
    },
    "variables": {
      "location": "[resourceGroup().location]",
      "sqlAdministratorLogin": "sqladminuser",
      "workspaceName": "[concat('briworkspace', variables('uniqueSuffix'))]",
      "adlsStorageAccountName": "[concat('bridadls', variables('uniqueSuffix'))]",
      "defaultDataLakeStorageFilesystemName": "tempdata",
      "sqlComputeName": "briwh",
      "sparkComputeName": "brispark",
      "computeSubnetId": "",
      "sqlServerSKU": "DW500c",
      "storageBlobDataOwnerRoleID": "b7e6dc6d-f1e8-4753-8033-0f276bb0955b",
      "defaultDataLakeStorageAccountUrl": "[concat('https://', variables('adlsStorageAccountName'), '.dfs.core.windows.net')]",
      "sparkAutoScaleEnabled": "true",
      "sparkMinNodeCount": "3",
      "sparkMaxNodeCount": "4",
      "sparkNodeCount": "0",
      "sparkNodeSizeFamily": "MemoryOptimized",
      "sparkNodeSize": "Small",
      "sparkAutoPauseEnabled": "true",
      "sparkAutoPauseDelayInMinutes": "15",
      "sparkVersion": "3.1",
      "keyVaultName": "[concat('brikeyvault', variables('uniqueSuffix'))]",
      "applicationInsightsName": "[concat('briappinsights', variables('uniqueSuffix'))]",
      "uniqueSuffix": "[toLower(parameters('uniqueSuffix'))]"
    },
    "resources": [
      {
        "type": "Microsoft.KeyVault/vaults",
        "apiVersion": "2018-02-14",
        "name": "[variables('keyVaultName')]",
        "location": "[variables('location')]",
        "properties": {
          "tenantId": "[subscription().tenantId]",
          "sku": {
            "name": "standard",
            "family": "A"
          },
          "accessPolicies": []
        }
      },
      {
        "type": "Microsoft.KeyVault/vaults/secrets",
        "name": "[concat(variables('keyVaultName'), '/SqlPassword')]",
        "apiVersion": "2018-02-14",
        "location": "[variables('location')]",
        "dependsOn": [
          "[resourceId('Microsoft.KeyVault/vaults', variables('keyVaultName'))]"
        ],
        "properties": {
          "value": "[parameters('sqlAdministratorLoginPassword')]"
        }
      },
      {
        "type": "Microsoft.Storage/storageAccounts",
        "apiVersion": "2019-06-01",
        "name": "[variables('adlsStorageAccountName')]",
        "location": "[variables('location')]",
        "dependsOn": [],
        "tags": {},
        "sku": {
          "name": "Standard_LRS"
        },
        "kind": "StorageV2",
        "properties": {
          "accessTier": "Hot",
          "supportsHttpsTrafficOnly": "true",
          "isHnsEnabled": "true",
          "largeFileSharesState": "Disabled"
        },
        "resources": [
          {
            "type": "blobServices/containers",
            "apiVersion": "2018-07-01",
            "name": "[concat('default/', variables('defaultDataLakeStorageFilesystemName'))]",
            "dependsOn": [
              "[concat('Microsoft.Storage/storageAccounts/', variables('adlsStorageAccountName'))]"
            ]
          }
        ]
      },
      {
        "type": "Microsoft.Storage/storageAccounts/blobServices",
        "apiVersion": "2019-06-01",
        "name": "[concat(variables('adlsStorageAccountName'), '/default')]",
        "dependsOn": [
          "[resourceId('Microsoft.Storage/storageAccounts', variables('adlsStorageAccountName'))]"
        ],
        "sku": {
          "name": "Standard_LRS",
          "tier": "Standard"
        },
        "properties": {
          "cors": {
            "corsRules": []
          },
          "deleteRetentionPolicy": {
            "enabled": false
          }
        }
      },
      {
        "type": "Microsoft.Storage/storageAccounts/fileServices",
        "apiVersion": "2019-06-01",
        "name": "[concat(variables('adlsStorageAccountName'), '/default')]",
        "dependsOn": [
          "[resourceId('Microsoft.Storage/storageAccounts', variables('adlsStorageAccountName'))]"
        ],
        "sku": {
          "name": "Standard_LRS",
          "tier": "Standard"
        },
        "properties": {
          "cors": {
            "corsRules": []
          }
        }
      },
      {
        "type": "Microsoft.Synapse/workspaces",
        "apiVersion": "2019-06-01-preview",
        "name": "[variables('workspaceName')]",
        "location": "[variables('location')]",
        "dependsOn": [
          "[concat('Microsoft.Storage/storageAccounts/', variables('adlsStorageAccountName'), '/blobServices/default/containers/', variables('defaultDataLakeStorageFilesystemName'))]"
        ],
        "identity": {
          "type": "SystemAssigned"
        },
        "properties": {
          "defaultDataLakeStorage": {
            "accountUrl": "[variables('defaultDataLakeStorageAccountUrl')]",
            "filesystem": "[variables('defaultDataLakeStorageFilesystemName')]"
          },
		  "managedResourceGroupName": "BRIManagedVNet",
          "managedVirtualNetwork": "default",
		  "managedVirtualNetworkSettings": {
			  "allowedAadTenantIdsForLinking": [],
			  "linkedAccessCheckOnTargetResource": true,
			  "preventDataExfiltration": true
		  },
          "sqlAdministratorLogin": "[variables('sqlAdministratorLogin')]",
          "sqlAdministratorLoginPassword": "[parameters('sqlAdministratorLoginPassword')]"
        },
        "resources": [
          {
            "type": "firewallrules",
            "apiVersion": "2019-06-01-preview",
            "name": "allowAll",
            "location": "[variables('location')]",
            "dependsOn": [
              "[concat('Microsoft.Synapse/workspaces/', variables('workspaceName'))]"
            ],
            "properties": {
              "startIpAddress": "0.0.0.0",
              "endIpAddress": "255.255.255.255"
            }
          }
        ]
      },
      {
        "type": "Microsoft.Storage/storageAccounts/blobServices/containers/providers/roleAssignments",
        "apiVersion": "2018-09-01-preview",
        "name": "[concat(variables('adlsStorageAccountName'), '/default/', variables('defaultDataLakeStorageFilesystemName'), '/Microsoft.Authorization/',  guid(concat(resourceGroup().id, '/', variables('storageBlobDataOwnerRoleID'), '/', variables('workspaceName'))))]",
        "location": "[variables('location')]",
        "dependsOn": [
          "[concat('Microsoft.Synapse/workspaces/', variables('workspaceName'))]"
        ],
        "properties": {
          "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', variables('storageBlobDataOwnerRoleID'))]",
          "principalId": "[reference(concat('Microsoft.Synapse/workspaces/', variables('workspaceName')), '2019-06-01-preview', 'Full').identity.principalId]",
          "principalType": "ServicePrincipal"
        }
      },
      {
        "type": "Microsoft.Authorization/roleAssignments",
        "apiVersion": "2018-09-01-preview",
        "name": "[guid(concat(resourceGroup().id, '/', variables('storageBlobDataOwnerRoleID'), '/', variables('workspaceName'), '2'))]",
        "location": "[variables('location')]",
        "dependsOn": [
          "[concat('Microsoft.Synapse/workspaces/', variables('workspaceName'))]"
        ],
        "properties": {
          "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', variables('storageBlobDataOwnerRoleID'))]",
          "principalId": "[reference(concat('Microsoft.Synapse/workspaces/', variables('workspaceName')), '2019-06-01-preview', 'Full').identity.principalId]",
          "principalType": "ServicePrincipal"
        }
      },
      {
        "type": "Microsoft.Synapse/workspaces/bigDataPools",
        "apiVersion": "2019-06-01-preview",
        "name": "[concat(variables('workspaceName'), '/', variables('sparkComputeName'))]",
        "location": "[variables('location')]",
        "dependsOn": [
          "[concat('Microsoft.Synapse/workspaces/', variables('workspaceName'))]"
        ],
        "properties": {
          "nodeCount": "[variables('sparkNodeCount')]",
          "nodeSizeFamily": "[variables('sparkNodeSizeFamily')]",
          "nodeSize": "[variables('sparkNodeSize')]",
          "autoScale": {
            "enabled": "[variables('sparkAutoScaleEnabled')]",
            "minNodeCount": "[variables('sparkMinNodeCount')]",
            "maxNodeCount": "[variables('sparkMaxNodeCount')]"
          },
          "autoPause": {
            "enabled": "[variables('sparkAutoPauseEnabled')]",
            "delayInMinutes": "[variables('sparkAutoPauseDelayInMinutes')]"
          },
          "sparkVersion": "[variables('sparkVersion')]"
        }
      },
      {
        "type": "Microsoft.Synapse/workspaces/sqlPools",
        "apiVersion": "2019-06-01-preview",
        "name": "[concat(variables('workspaceName'), '/', variables('sqlComputeName'))]",
        "location": "[variables('location')]",
        "dependsOn": [
          "[concat('Microsoft.Synapse/workspaces/', variables('workspaceName'))]",
          "[concat('Microsoft.Synapse/workspaces/', variables('workspaceName'), '/bigDataPools/', variables('sparkComputeName'))]"
        ],
        "sku": {
          "name": "[variables('sqlServerSKU')]"
        },
        "properties": {
          "createMode": "Default",
          "collation": "SQL_Latin1_General_CP1_CI_AS"
        }
      },
      {
        "type": "Microsoft.Insights/components",
        "apiVersion": "2018-05-01-preview",
        "name": "[variables('applicationInsightsName')]",
        "location": "[if(or(equals(variables('location'),'eastus2'),equals(variables('location'),'westcentralus')),'southcentralus',variables('location'))]",
        "kind": "web",
        "properties": {
          "Application_Type": "web"
        }
      },
      {
        "type": "Microsoft.Storage/storageAccounts/blobServices/containers",
        "apiVersion": "2019-06-01",
        "name": "[concat(variables('adlsStorageAccountName'), '/default/synapse')]",
        "dependsOn": [
          "[resourceId('Microsoft.Storage/storageAccounts/blobServices', variables('adlsStorageAccountName'), 'default')]",
          "[resourceId('Microsoft.Storage/storageAccounts', variables('adlsStorageAccountName'))]"
        ],
        "properties": {
          "publicAccess": "None"
        }
      }
    ],
    "outputs": {}
  }
  

That’s about 300 lines of template, so let’s break it down chunk by chunk.

First up we have two parameters and a whole bunch of variables. For my case, I just need users to define a unique suffix (so that multiple people can use this script without getting unique name violations) and a password for the SQL administrative account.

"parameters": {
      "uniqueSuffix": {
        "type": "String",
        "metadata": {
          "description": "Suffix added to all resource name to make them unique."
        }
      },
      "sqlAdministratorLoginPassword": {
        "type": "SecureString",
        "metadata": {
          "description": "Password for SQL Admin"
        }
      }
    },
    "variables": {
      "location": "[resourceGroup().location]",
      "sqlAdministratorLogin": "sqladminuser",
      "workspaceName": "[concat('briworkspace', variables('uniqueSuffix'))]",
      "adlsStorageAccountName": "[concat('bridadls', variables('uniqueSuffix'))]",
      "defaultDataLakeStorageFilesystemName": "tempdata",
      "sqlComputeName": "briwh",
      "sparkComputeName": "brispark",
      "computeSubnetId": "",
      "sqlServerSKU": "DW500c",
      "storageBlobDataOwnerRoleID": "b7e6dc6d-f1e8-4753-8033-0f276bb0955b",
      "defaultDataLakeStorageAccountUrl": "[concat('https://', variables('adlsStorageAccountName'), '.dfs.core.windows.net')]",
      "sparkAutoScaleEnabled": "true",
      "sparkMinNodeCount": "3",
      "sparkMaxNodeCount": "4",
      "sparkNodeCount": "0",
      "sparkNodeSizeFamily": "MemoryOptimized",
      "sparkNodeSize": "Small",
      "sparkAutoPauseEnabled": "true",
      "sparkAutoPauseDelayInMinutes": "15",
      "sparkVersion": "3.1",
      "keyVaultName": "[concat('brikeyvault', variables('uniqueSuffix'))]",
      "applicationInsightsName": "[concat('briappinsights', variables('uniqueSuffix'))]",
      "uniqueSuffix": "[toLower(parameters('uniqueSuffix'))]"
    },

Among the variables, I make a bunch of assumptions about sizing, including a DW500 SKU, 3-4 Spark nodes, and Spark version 3.1. You could, of course, change these before deploying the template.

Next up, I create a Key Vault and insert our administrative password. This isn’t strictly necessary, but my philosophy is that you’re going to need a Key Vault at some point with Synapse, so might as well get it created early.

{
        "type": "Microsoft.KeyVault/vaults",
        "apiVersion": "2018-02-14",
        "name": "[variables('keyVaultName')]",
        "location": "[variables('location')]",
        "properties": {
          "tenantId": "[subscription().tenantId]",
          "sku": {
            "name": "standard",
            "family": "A"
          },
          "accessPolicies": []
        }
      },
      {
        "type": "Microsoft.KeyVault/vaults/secrets",
        "name": "[concat(variables('keyVaultName'), '/SqlPassword')]",
        "apiVersion": "2018-02-14",
        "location": "[variables('location')]",
        "dependsOn": [
          "[resourceId('Microsoft.KeyVault/vaults', variables('keyVaultName'))]"
        ],
        "properties": {
          "value": "[parameters('sqlAdministratorLoginPassword')]"
        }
      },

After that, I create my Azure Data Lake Storage Gen2 account (I can tell because isHnsEnabled, or “is hierarchical namespace enabled” is set to true). For Synapse, we need to create blob and file storage services, which is why we get both. There’s nothing special about this. I limit traffic to HTTPS only, as I don’t see any value to a non-HTTPS method of accessing my data.

{
        "type": "Microsoft.Storage/storageAccounts",
        "apiVersion": "2019-06-01",
        "name": "[variables('adlsStorageAccountName')]",
        "location": "[variables('location')]",
        "dependsOn": [],
        "tags": {},
        "sku": {
          "name": "Standard_LRS"
        },
        "kind": "StorageV2",
        "properties": {
          "accessTier": "Hot",
          "supportsHttpsTrafficOnly": "true",
          "isHnsEnabled": "true",
          "largeFileSharesState": "Disabled"
        },
        "resources": [
          {
            "type": "blobServices/containers",
            "apiVersion": "2018-07-01",
            "name": "[concat('default/', variables('defaultDataLakeStorageFilesystemName'))]",
            "dependsOn": [
              "[concat('Microsoft.Storage/storageAccounts/', variables('adlsStorageAccountName'))]"
            ]
          }
        ]
      },
      {
        "type": "Microsoft.Storage/storageAccounts/blobServices",
        "apiVersion": "2019-06-01",
        "name": "[concat(variables('adlsStorageAccountName'), '/default')]",
        "dependsOn": [
          "[resourceId('Microsoft.Storage/storageAccounts', variables('adlsStorageAccountName'))]"
        ],
        "sku": {
          "name": "Standard_LRS",
          "tier": "Standard"
        },
        "properties": {
          "cors": {
            "corsRules": []
          },
          "deleteRetentionPolicy": {
            "enabled": false
          }
        }
      },
      {
        "type": "Microsoft.Storage/storageAccounts/fileServices",
        "apiVersion": "2019-06-01",
        "name": "[concat(variables('adlsStorageAccountName'), '/default')]",
        "dependsOn": [
          "[resourceId('Microsoft.Storage/storageAccounts', variables('adlsStorageAccountName'))]"
        ],
        "sku": {
          "name": "Standard_LRS",
          "tier": "Standard"
        },
        "properties": {
          "cors": {
            "corsRules": []
          }
        }
      },

After creating our ADLSGen2 storage account, we can create a Synapse workspace.

{
        "type": "Microsoft.Synapse/workspaces",
        "apiVersion": "2019-06-01-preview",
        "name": "[variables('workspaceName')]",
        "location": "[variables('location')]",
        "dependsOn": [
          "[concat('Microsoft.Storage/storageAccounts/', variables('adlsStorageAccountName'), '/blobServices/default/containers/', variables('defaultDataLakeStorageFilesystemName'))]"
        ],
        "identity": {
          "type": "SystemAssigned"
        },
        "properties": {
          "defaultDataLakeStorage": {
            "accountUrl": "[variables('defaultDataLakeStorageAccountUrl')]",
            "filesystem": "[variables('defaultDataLakeStorageFilesystemName')]"
          },
		  "managedResourceGroupName": "BRIManagedVNet",
          "managedVirtualNetwork": "default",
		  "managedVirtualNetworkSettings": {
			  "allowedAadTenantIdsForLinking": [],
			  "linkedAccessCheckOnTargetResource": true,
			  "preventDataExfiltration": true
		  },
          "sqlAdministratorLogin": "[variables('sqlAdministratorLogin')]",
          "sqlAdministratorLoginPassword": "[parameters('sqlAdministratorLoginPassword')]"
        },
        "resources": [
          {
            "type": "firewallrules",
            "apiVersion": "2019-06-01-preview",
            "name": "allowAll",
            "location": "[variables('location')]",
            "dependsOn": [
              "[concat('Microsoft.Synapse/workspaces/', variables('workspaceName'))]"
            ],
            "properties": {
              "startIpAddress": "0.0.0.0",
              "endIpAddress": "255.255.255.255"
            }
          }
        ]
      },

We can see that with accountUrl and filesystem that we tie together the Synapse workspace to the storage account endpoints that we just finished creating. Right after that, we get to the chunk which forms the basis of the post: managed virtual networks. Let’s narrow in on that:

		  "managedResourceGroupName": "BRIManagedVNet",
          "managedVirtualNetwork": "default",
		  "managedVirtualNetworkSettings": {
			  "allowedAadTenantIdsForLinking": [],
			  "linkedAccessCheckOnTargetResource": true,
			  "preventDataExfiltration": true
		  },

I need to create a resource group for managed resources. Even though I won’t be able to see resources in the RG, it’s still important. Furthermore, I won’t be able to delete the managed resource group unless I delete the Synapse workspace first. The default option for managed virtual networks in the ARM template is to create one, and we have a few additional settings. I turned data exfiltration protection on and indicated that the only Azure Active Directory tenant I want to allow is my own. The linkedAccessCheckOnTargetResource property has a really useful description, in that it “Gets or sets linked Access Check On Target Resource.” So yeah, we’ve got that going for us…

Anyhow, I also set a firewall rule which opens up the workspace to the wider internet, but that’s a rule you’ll want to delete later. And probably replace with a private link hub.

We have the workspace, but now I want to create some additional resources. First up is a role assignment, giving storage blob data owner to the workspace service account. That way, we will be able to read from and write to the data lake via our workspace.

{
        "type": "Microsoft.Storage/storageAccounts/blobServices/containers/providers/roleAssignments",
        "apiVersion": "2018-09-01-preview",
        "name": "[concat(variables('adlsStorageAccountName'), '/default/', variables('defaultDataLakeStorageFilesystemName'), '/Microsoft.Authorization/',  guid(concat(resourceGroup().id, '/', variables('storageBlobDataOwnerRoleID'), '/', variables('workspaceName'))))]",
        "location": "[variables('location')]",
        "dependsOn": [
          "[concat('Microsoft.Synapse/workspaces/', variables('workspaceName'))]"
        ],
        "properties": {
          "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', variables('storageBlobDataOwnerRoleID'))]",
          "principalId": "[reference(concat('Microsoft.Synapse/workspaces/', variables('workspaceName')), '2019-06-01-preview', 'Full').identity.principalId]",
          "principalType": "ServicePrincipal"
        }
      },
      {
        "type": "Microsoft.Authorization/roleAssignments",
        "apiVersion": "2018-09-01-preview",
        "name": "[guid(concat(resourceGroup().id, '/', variables('storageBlobDataOwnerRoleID'), '/', variables('workspaceName'), '2'))]",
        "location": "[variables('location')]",
        "dependsOn": [
          "[concat('Microsoft.Synapse/workspaces/', variables('workspaceName'))]"
        ],
        "properties": {
          "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', variables('storageBlobDataOwnerRoleID'))]",
          "principalId": "[reference(concat('Microsoft.Synapse/workspaces/', variables('workspaceName')), '2019-06-01-preview', 'Full').identity.principalId]",
          "principalType": "ServicePrincipal"
        }
      },

After that, I create a Spark pool, making sure to enable that auto-shutdown after 15 minutes to save my wallet from pain.

      {
        "type": "Microsoft.Synapse/workspaces/bigDataPools",
        "apiVersion": "2019-06-01-preview",
        "name": "[concat(variables('workspaceName'), '/', variables('sparkComputeName'))]",
        "location": "[variables('location')]",
        "dependsOn": [
          "[concat('Microsoft.Synapse/workspaces/', variables('workspaceName'))]"
        ],
        "properties": {
          "nodeCount": "[variables('sparkNodeCount')]",
          "nodeSizeFamily": "[variables('sparkNodeSizeFamily')]",
          "nodeSize": "[variables('sparkNodeSize')]",
          "autoScale": {
            "enabled": "[variables('sparkAutoScaleEnabled')]",
            "minNodeCount": "[variables('sparkMinNodeCount')]",
            "maxNodeCount": "[variables('sparkMaxNodeCount')]"
          },
          "autoPause": {
            "enabled": "[variables('sparkAutoPauseEnabled')]",
            "delayInMinutes": "[variables('sparkAutoPauseDelayInMinutes')]"
          },
          "sparkVersion": "[variables('sparkVersion')]"
        }
      },

Then we create a dedicated SQL pool. I set the default to DWU500, which is pretty expensive but is also the minimum to get us one full node. 500 or 1000 is the minimum that I’d use in a development environment, but if you’re playing around with it on your own, you can drop that down to 100 and cut the price down to 1/5.

      {
        "type": "Microsoft.Synapse/workspaces/sqlPools",
        "apiVersion": "2019-06-01-preview",
        "name": "[concat(variables('workspaceName'), '/', variables('sqlComputeName'))]",
        "location": "[variables('location')]",
        "dependsOn": [
          "[concat('Microsoft.Synapse/workspaces/', variables('workspaceName'))]",
          "[concat('Microsoft.Synapse/workspaces/', variables('workspaceName'), '/bigDataPools/', variables('sparkComputeName'))]"
        ],
        "sku": {
          "name": "[variables('sqlServerSKU')]"
        },
        "properties": {
          "createMode": "Default",
          "collation": "SQL_Latin1_General_CP1_CI_AS"
        }
      },

Finally, I set up an App Insights service and create a synapse container in blob storage.

      {
        "type": "Microsoft.Insights/components",
        "apiVersion": "2018-05-01-preview",
        "name": "[variables('applicationInsightsName')]",
        "location": "[if(or(equals(variables('location'),'eastus2'),equals(variables('location'),'westcentralus')),'southcentralus',variables('location'))]",
        "kind": "web",
        "properties": {
          "Application_Type": "web"
        }
      },
      {
        "type": "Microsoft.Storage/storageAccounts/blobServices/containers",
        "apiVersion": "2019-06-01",
        "name": "[concat(variables('adlsStorageAccountName'), '/default/synapse')]",
        "dependsOn": [
          "[resourceId('Microsoft.Storage/storageAccounts/blobServices', variables('adlsStorageAccountName'), 'default')]",
          "[resourceId('Microsoft.Storage/storageAccounts', variables('adlsStorageAccountName'))]"
        ],
        "properties": {
          "publicAccess": "None"
        }
      }

Conclusion

Over the course of today’s post, we got some understanding of what a managed virtual network does, what data exfiltration protection does, and how to create a new Azure Synapse Analytics workspace from an ARM template and have both of these enabled. Given the state of Azure Synapse Analytics today, I’m not sure there’s a strong reason not to use the managed virtual network option, but I’d be much more hesitant about enabling data exfiltration protection unless you’re willing to diagnose strange problems in the future.

One thought on “Azure Synapse Analytics with Managed VNet + DEP

Leave a comment