├── .gitignore ├── LICENSE ├── README.md ├── aks.tf ├── application_insights.tf ├── bastion.tf ├── container_registry.tf ├── jumphost.tf ├── key_vault.tf ├── main.tf ├── media └── architecture_aml_pl.png ├── resource_group.tf ├── storage_account.tf ├── terraform.tfvars.example ├── variables.tf ├── virtual_network.tf └── workspace.tf /.gitignore: -------------------------------------------------------------------------------- 1 | # Local .terraform directories 2 | **/.terraform/* 3 | 4 | # .tfstate files 5 | *.tfstate 6 | *.tfstate.* 7 | 8 | # Crash log files 9 | crash.log 10 | 11 | # Ignore any .tfvars files that are generated automatically for each Terraform run. Most 12 | # .tfvars files are managed as part of configuration and so should be included in 13 | # version control. 14 | # 15 | terraform.tfvars 16 | 17 | # Ignore override files as they are usually used to override resources locally and so 18 | # are not checked in 19 | override.tf 20 | override.tf.json 21 | *_override.tf 22 | *_override.tf.json 23 | 24 | # Include override files you do wish to add to version control using negated pattern 25 | # 26 | # !example_override.tf 27 | 28 | # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan 29 | # example: *tfplan* 30 | .terraform.lock.hcl 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Microsoft 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Azure Machine Learning Enterprise Terraform Example 2 | 3 | This repo shows an example for rolling out a complete Azure Machine Learning enterprise enviroment via Terraform. 4 | 5 | ![Deployed resources](media/architecture_aml_pl.png "Deployed resources") 6 | 7 | This includes rollout of the following resources: 8 | 9 | * Azure Machine Learning Workspace with Private Link 10 | * Azure Storage Account with VNET binding (using Service Endpoints) and Private Link for Blob and File 11 | * Azure Key Vault with VNET binding (using Service Endpoints) and Private Link 12 | * Azure Container Registry 13 | * Azure Application Insights 14 | * Virtual Network 15 | * Jumphost (Windows) with Bastion for easy access to the VNET 16 | * Compute Cluster (in VNET) 17 | * Compute Instance (in VNET) 18 | * (Azure Kubernetes Service - disabled by default and still under development) 19 | 20 | ## Instructions 21 | 22 | Make sure you have the [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli) and the Azure Machine Learning CLI extension installed (`az extension add -n azure-cli-ml`). 23 | 24 | 1. Copy `terraform.tfvars.example` to `terraform.tfvars` 25 | 1. Update `terraform.tfvars` with your desired values 26 | 2. Run Terraform 27 | ```console 28 | $ terraform init 29 | $ terraform plan 30 | $ terraform apply 31 | ``` 32 | 33 | If you want to deploy AKS, you need to have [Azure Machine Learning CLI installed](https://docs.microsoft.com/en-us/azure/machine-learning/reference-azure-machine-learning-cli). 34 | 35 | # Known Limitations 36 | 37 | * Still need to update `Default Datastore` to use Managed Identity of the Studio UI data access 38 | 39 | # Important Notes 40 | 41 | * The user fileshare for the Compute Instances will be automatically provisioned upon first instance access -------------------------------------------------------------------------------- /aks.tf: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Microsoft 2 | # 3 | # This software is released under the MIT License. 4 | # https://opensource.org/licenses/MIT 5 | 6 | # Azure Kubernetes Service (not deployed per default) 7 | 8 | resource "azurerm_kubernetes_cluster" "aml_aks" { 9 | count = var.deploy_aks ? 1 : 0 10 | name = "${var.prefix}-aks-${random_string.postfix.result}" 11 | location = azurerm_resource_group.aml_rg.location 12 | resource_group_name = azurerm_resource_group.aml_rg.name 13 | dns_prefix = "aks" 14 | 15 | default_node_pool { 16 | name = "default" 17 | node_count = 3 18 | vm_size = "Standard_DS2_v2" 19 | vnet_subnet_id = azurerm_subnet.aks_subnet[count.index].id 20 | } 21 | 22 | identity { 23 | type = "SystemAssigned" 24 | } 25 | 26 | network_profile { 27 | network_plugin = "azure" 28 | dns_service_ip = "10.0.3.10" 29 | service_cidr = "10.0.3.0/24" 30 | docker_bridge_cidr = "172.17.0.1/16" 31 | } 32 | 33 | provisioner "local-exec" { 34 | command = "az ml computetarget attach aks -n ${azurerm_kubernetes_cluster.aml_aks[count.index].name} -i ${azurerm_kubernetes_cluster.aml_aks[count.index].id} -g ${var.resource_group} -w ${azurerm_machine_learning_workspace.aml_ws.name}" 35 | } 36 | 37 | depends_on = [azurerm_machine_learning_workspace.aml_ws] 38 | } 39 | -------------------------------------------------------------------------------- /application_insights.tf: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Microsoft 2 | # 3 | # This software is released under the MIT License. 4 | # https://opensource.org/licenses/MIT 5 | 6 | # Application Insights for Azure Machine Learning (no Private Link/VNET integration) 7 | 8 | resource "azurerm_application_insights" "aml_ai" { 9 | name = "${var.prefix}-ai-${random_string.postfix.result}" 10 | location = azurerm_resource_group.aml_rg.location 11 | resource_group_name = azurerm_resource_group.aml_rg.name 12 | application_type = "web" 13 | } -------------------------------------------------------------------------------- /bastion.tf: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Microsoft 2 | # 3 | # This software is released under the MIT License. 4 | # https://opensource.org/licenses/MIT 5 | 6 | resource "azurerm_public_ip" "bastion_ip" { 7 | name = "${var.prefix}-public-ip-bastion" 8 | location = azurerm_resource_group.aml_rg.location 9 | resource_group_name = azurerm_resource_group.aml_rg.name 10 | allocation_method = "Static" 11 | sku = "Standard" 12 | } 13 | 14 | resource "azurerm_bastion_host" "jumphost_bastion" { 15 | name = "${var.prefix}-bastion-host" 16 | location = azurerm_resource_group.aml_rg.location 17 | resource_group_name = azurerm_resource_group.aml_rg.name 18 | 19 | ip_configuration { 20 | name = "configuration" 21 | subnet_id = azurerm_subnet.bastion_subnet.id 22 | public_ip_address_id = azurerm_public_ip.bastion_ip.id 23 | } 24 | } -------------------------------------------------------------------------------- /container_registry.tf: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Microsoft 2 | # 3 | # This software is released under the MIT License. 4 | # https://opensource.org/licenses/MIT 5 | 6 | # Azure Container Registry (no VNET binding and/or Private Link) 7 | 8 | resource "azurerm_container_registry" "aml_acr" { 9 | name = "${var.prefix}acr${random_string.postfix.result}" 10 | resource_group_name = azurerm_resource_group.aml_rg.name 11 | location = azurerm_resource_group.aml_rg.location 12 | sku = "Standard" 13 | admin_enabled = true 14 | } -------------------------------------------------------------------------------- /jumphost.tf: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Microsoft 2 | # 3 | # This software is released under the MIT License. 4 | # https://opensource.org/licenses/MIT 5 | 6 | # Jump host for testing VNET and Private Link 7 | 8 | resource "azurerm_network_interface" "jumphost_nic" { 9 | name = "jumphost-nic" 10 | location = azurerm_resource_group.aml_rg.location 11 | resource_group_name = azurerm_resource_group.aml_rg.name 12 | 13 | ip_configuration { 14 | name = "configuration" 15 | private_ip_address_allocation = "Dynamic" 16 | subnet_id = azurerm_subnet.aml_subnet.id 17 | # public_ip_address_id = azurerm_public_ip.jumphost_public_ip.id 18 | } 19 | } 20 | 21 | resource "azurerm_network_security_group" "jumphost_nsg" { 22 | name = "jumphost-nsg" 23 | location = azurerm_resource_group.aml_rg.location 24 | resource_group_name = azurerm_resource_group.aml_rg.name 25 | 26 | security_rule { 27 | name = "RDP" 28 | priority = 1010 29 | direction = "Inbound" 30 | access = "Allow" 31 | protocol = "Tcp" 32 | source_port_range = "*" 33 | destination_port_range = 3389 34 | source_address_prefix = "*" 35 | destination_address_prefix = "*" 36 | } 37 | } 38 | 39 | resource "azurerm_network_interface_security_group_association" "jumphost_nsg_association" { 40 | network_interface_id = azurerm_network_interface.jumphost_nic.id 41 | network_security_group_id = azurerm_network_security_group.jumphost_nsg.id 42 | } 43 | 44 | resource "azurerm_virtual_machine" "jumphost" { 45 | name = "jumphost" 46 | location = azurerm_resource_group.aml_rg.location 47 | resource_group_name = azurerm_resource_group.aml_rg.name 48 | network_interface_ids = [azurerm_network_interface.jumphost_nic.id] 49 | vm_size = "Standard_DS3_v2" 50 | 51 | delete_os_disk_on_termination = true 52 | delete_data_disks_on_termination = true 53 | 54 | storage_image_reference { 55 | publisher = "microsoft-dsvm" 56 | offer = "dsvm-win-2019" 57 | sku = "server-2019" 58 | version = "latest" 59 | } 60 | 61 | os_profile { 62 | computer_name = "jumphost" 63 | admin_username = var.jumphost_username 64 | admin_password = var.jumphost_password 65 | } 66 | 67 | os_profile_windows_config { 68 | provision_vm_agent = true 69 | enable_automatic_upgrades = true 70 | } 71 | 72 | identity { 73 | type = "SystemAssigned" 74 | } 75 | 76 | storage_os_disk { 77 | name = "jumphost-osdisk" 78 | caching = "ReadWrite" 79 | create_option = "FromImage" 80 | managed_disk_type = "StandardSSD_LRS" 81 | } 82 | } 83 | 84 | resource "azurerm_dev_test_global_vm_shutdown_schedule" "jumphost_schedule" { 85 | virtual_machine_id = azurerm_virtual_machine.jumphost.id 86 | location = azurerm_resource_group.aml_rg.location 87 | enabled = true 88 | 89 | daily_recurrence_time = "2000" 90 | timezone = "W. Europe Standard Time" 91 | 92 | notification_settings { 93 | enabled = false 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /key_vault.tf: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Microsoft 2 | # 3 | # This software is released under the MIT License. 4 | # https://opensource.org/licenses/MIT 5 | 6 | # Key Vault with VNET binding and Private Endpoint 7 | 8 | resource "azurerm_key_vault" "aml_kv" { 9 | name = "${var.prefix}-kv-${random_string.postfix.result}" 10 | location = azurerm_resource_group.aml_rg.location 11 | resource_group_name = azurerm_resource_group.aml_rg.name 12 | tenant_id = data.azurerm_client_config.current.tenant_id 13 | sku_name = "standard" 14 | 15 | network_acls { 16 | default_action = "Deny" 17 | ip_rules = [] 18 | virtual_network_subnet_ids = [azurerm_subnet.aml_subnet.id, azurerm_subnet.compute_subnet.id, azurerm_subnet.aks_subnet.id] 19 | bypass = "AzureServices" 20 | } 21 | } 22 | 23 | # DNS Zones 24 | 25 | resource "azurerm_private_dns_zone" "kv_zone" { 26 | name = "privatelink.vaultcore.azure.net" 27 | resource_group_name = azurerm_resource_group.aml_rg.name 28 | } 29 | 30 | # Linking of DNS zones to Virtual Network 31 | 32 | resource "azurerm_private_dns_zone_virtual_network_link" "kv_zone_link" { 33 | name = "${random_string.postfix.result}_link_kv" 34 | resource_group_name = azurerm_resource_group.aml_rg.name 35 | private_dns_zone_name = azurerm_private_dns_zone.kv_zone.name 36 | virtual_network_id = azurerm_virtual_network.aml_vnet.id 37 | } 38 | 39 | # Private Endpoint configuration 40 | 41 | resource "azurerm_private_endpoint" "kv_pe" { 42 | name = "${var.prefix}-kv-pe-${random_string.postfix.result}" 43 | location = azurerm_resource_group.aml_rg.location 44 | resource_group_name = azurerm_resource_group.aml_rg.name 45 | subnet_id = azurerm_subnet.aml_subnet.id 46 | 47 | private_service_connection { 48 | name = "${var.prefix}-kv-psc-${random_string.postfix.result}" 49 | private_connection_resource_id = azurerm_key_vault.aml_kv.id 50 | subresource_names = ["vault"] 51 | is_manual_connection = false 52 | } 53 | 54 | private_dns_zone_group { 55 | name = "private-dns-zone-group-kv" 56 | private_dns_zone_ids = [azurerm_private_dns_zone.kv_zone.id] 57 | } 58 | } -------------------------------------------------------------------------------- /main.tf: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Microsoft 2 | # 3 | # This software is released under the MIT License. 4 | # https://opensource.org/licenses/MIT 5 | 6 | # Azure provide configuration 7 | 8 | terraform { 9 | required_providers { 10 | azurerm = { 11 | source = "hashicorp/azurerm" 12 | version = ">= 2.26" 13 | } 14 | } 15 | } 16 | 17 | provider "azurerm" { 18 | features {} 19 | } 20 | 21 | data "azurerm_client_config" "current" {} -------------------------------------------------------------------------------- /media/architecture_aml_pl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csiebler/azure-machine-learning-terraform/7f0d4dcc14e57dff5a54822d20a5e4df7f5cba08/media/architecture_aml_pl.png -------------------------------------------------------------------------------- /resource_group.tf: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Microsoft 2 | # 3 | # This software is released under the MIT License. 4 | # https://opensource.org/licenses/MIT 5 | 6 | resource "azurerm_resource_group" "aml_rg" { 7 | name = var.resource_group 8 | location = var.location 9 | } 10 | -------------------------------------------------------------------------------- /storage_account.tf: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Microsoft 2 | # 3 | # This software is released under the MIT License. 4 | # https://opensource.org/licenses/MIT 5 | 6 | # Storage Account with VNET binding and Private Endpoint for Blob and File 7 | 8 | resource "azurerm_storage_account" "aml_sa" { 9 | name = "${var.prefix}sa${random_string.postfix.result}" 10 | location = azurerm_resource_group.aml_rg.location 11 | resource_group_name = azurerm_resource_group.aml_rg.name 12 | account_tier = "Standard" 13 | account_replication_type = "LRS" 14 | } 15 | 16 | # Virtual Network & Firewall configuration 17 | 18 | resource "azurerm_storage_account_network_rules" "firewall_rules" { 19 | resource_group_name = azurerm_resource_group.aml_rg.name 20 | storage_account_name = azurerm_storage_account.aml_sa.name 21 | 22 | default_action = "Deny" 23 | ip_rules = [] 24 | virtual_network_subnet_ids = [azurerm_subnet.aml_subnet.id, azurerm_subnet.compute_subnet.id, azurerm_subnet.aks_subnet.id] 25 | bypass = ["AzureServices"] 26 | 27 | # Set network policies after Workspace has been created (will create File Share Datastore properly) 28 | depends_on = [azurerm_machine_learning_workspace.aml_ws] 29 | } 30 | 31 | # DNS Zones 32 | 33 | resource "azurerm_private_dns_zone" "sa_zone_blob" { 34 | name = "privatelink.blob.core.windows.net" 35 | resource_group_name = azurerm_resource_group.aml_rg.name 36 | } 37 | 38 | resource "azurerm_private_dns_zone" "sa_zone_file" { 39 | name = "privatelink.file.core.windows.net" 40 | resource_group_name = azurerm_resource_group.aml_rg.name 41 | } 42 | 43 | # Linking of DNS zones to Virtual Network 44 | 45 | resource "azurerm_private_dns_zone_virtual_network_link" "sa_zone_blob_link" { 46 | name = "${random_string.postfix.result}_link_blob" 47 | resource_group_name = azurerm_resource_group.aml_rg.name 48 | private_dns_zone_name = azurerm_private_dns_zone.sa_zone_blob.name 49 | virtual_network_id = azurerm_virtual_network.aml_vnet.id 50 | } 51 | 52 | resource "azurerm_private_dns_zone_virtual_network_link" "sa_zone_file_link" { 53 | name = "${random_string.postfix.result}_link_file" 54 | resource_group_name = azurerm_resource_group.aml_rg.name 55 | private_dns_zone_name = azurerm_private_dns_zone.sa_zone_file.name 56 | virtual_network_id = azurerm_virtual_network.aml_vnet.id 57 | } 58 | 59 | # Private Endpoint configuration 60 | 61 | resource "azurerm_private_endpoint" "sa_pe_blob" { 62 | name = "${var.prefix}-sa-pe-blob-${random_string.postfix.result}" 63 | location = azurerm_resource_group.aml_rg.location 64 | resource_group_name = azurerm_resource_group.aml_rg.name 65 | subnet_id = azurerm_subnet.aml_subnet.id 66 | 67 | private_service_connection { 68 | name = "${var.prefix}-sa-psc-blob-${random_string.postfix.result}" 69 | private_connection_resource_id = azurerm_storage_account.aml_sa.id 70 | subresource_names = ["blob"] 71 | is_manual_connection = false 72 | } 73 | 74 | private_dns_zone_group { 75 | name = "private-dns-zone-group-blob" 76 | private_dns_zone_ids = [azurerm_private_dns_zone.sa_zone_blob.id] 77 | } 78 | } 79 | 80 | resource "azurerm_private_endpoint" "sa_pe_file" { 81 | name = "${var.prefix}-sa-pe-file-${random_string.postfix.result}" 82 | location = azurerm_resource_group.aml_rg.location 83 | resource_group_name = azurerm_resource_group.aml_rg.name 84 | subnet_id = azurerm_subnet.aml_subnet.id 85 | 86 | private_service_connection { 87 | name = "${var.prefix}-sa-psc-file-${random_string.postfix.result}" 88 | private_connection_resource_id = azurerm_storage_account.aml_sa.id 89 | subresource_names = ["file"] 90 | is_manual_connection = false 91 | } 92 | 93 | private_dns_zone_group { 94 | name = "private-dns-zone-group-file" 95 | private_dns_zone_ids = [azurerm_private_dns_zone.sa_zone_file.id] 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /terraform.tfvars.example: -------------------------------------------------------------------------------- 1 | # Resource group name 2 | resource_group="aml-terraform-demo" 3 | 4 | # Resource name prefix 5 | prefix="aml" 6 | 7 | # Deployment location 8 | location="West Europe" 9 | 10 | # Friendly Workspace name 11 | workspace_display_name="aml-terraform-demo" 12 | 13 | # Jumpbox login credentials 14 | jumphost_username="azureuser" 15 | jumphost_password="ThisIsNotVerySecure!" 16 | 17 | # Deploy Azure Kubernetes Cluster 18 | deploy_aks=false -------------------------------------------------------------------------------- /variables.tf: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Microsoft 2 | # 3 | # This software is released under the MIT License. 4 | # https://opensource.org/licenses/MIT 5 | 6 | variable "resource_group" { 7 | default = "aml-terraform-demo" 8 | } 9 | 10 | variable "workspace_display_name" { 11 | default = "aml-terraform-demo" 12 | } 13 | 14 | variable "location" { 15 | default = "West Europe" 16 | } 17 | 18 | variable "deploy_aks" { 19 | default = false 20 | } 21 | 22 | variable "jumphost_username" { 23 | default = "azureuser" 24 | } 25 | 26 | variable "jumphost_password" { 27 | default = "ThisIsNotVerySecure!" 28 | } 29 | 30 | variable "prefix" { 31 | type = string 32 | default = "aml" 33 | } 34 | 35 | resource "random_string" "postfix" { 36 | length = 6 37 | special = false 38 | upper = false 39 | } -------------------------------------------------------------------------------- /virtual_network.tf: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Microsoft 2 | # 3 | # This software is released under the MIT License. 4 | # https://opensource.org/licenses/MIT 5 | 6 | # Virtual Network definition 7 | 8 | resource "azurerm_virtual_network" "aml_vnet" { 9 | name = "${var.prefix}-vnet-${random_string.postfix.result}" 10 | address_space = ["10.0.0.0/16"] 11 | location = azurerm_resource_group.aml_rg.location 12 | resource_group_name = azurerm_resource_group.aml_rg.name 13 | } 14 | 15 | resource "azurerm_subnet" "aml_subnet" { 16 | name = "${var.prefix}-aml-subnet-${random_string.postfix.result}" 17 | resource_group_name = azurerm_resource_group.aml_rg.name 18 | virtual_network_name = azurerm_virtual_network.aml_vnet.name 19 | address_prefixes = ["10.0.1.0/24"] 20 | service_endpoints = ["Microsoft.ContainerRegistry", "Microsoft.KeyVault", "Microsoft.Storage"] 21 | enforce_private_link_endpoint_network_policies = true 22 | } 23 | 24 | resource "azurerm_subnet" "compute_subnet" { 25 | name = "${var.prefix}-compute-subnet-${random_string.postfix.result}" 26 | resource_group_name = azurerm_resource_group.aml_rg.name 27 | virtual_network_name = azurerm_virtual_network.aml_vnet.name 28 | address_prefixes = ["10.0.2.0/24"] 29 | service_endpoints = ["Microsoft.ContainerRegistry", "Microsoft.KeyVault", "Microsoft.Storage"] 30 | enforce_private_link_service_network_policies = false 31 | enforce_private_link_endpoint_network_policies = false 32 | } 33 | 34 | resource "azurerm_subnet" "aks_subnet" { 35 | name = "${var.prefix}-aks-subnet-${random_string.postfix.result}" 36 | resource_group_name = azurerm_resource_group.aml_rg.name 37 | virtual_network_name = azurerm_virtual_network.aml_vnet.name 38 | address_prefixes = ["10.0.3.0/24"] 39 | service_endpoints = ["Microsoft.ContainerRegistry", "Microsoft.KeyVault", "Microsoft.Storage"] 40 | } 41 | 42 | resource "azurerm_subnet" "bastion_subnet" { 43 | name = "AzureBastionSubnet" 44 | resource_group_name = azurerm_resource_group.aml_rg.name 45 | virtual_network_name = azurerm_virtual_network.aml_vnet.name 46 | address_prefixes = ["10.0.10.0/27"] 47 | } -------------------------------------------------------------------------------- /workspace.tf: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Microsoft 2 | # 3 | # This software is released under the MIT License. 4 | # https://opensource.org/licenses/MIT 5 | 6 | # Azure Machine Learning Workspace with Private Link 7 | 8 | resource "azurerm_machine_learning_workspace" "aml_ws" { 9 | name = "${var.prefix}-ws-${random_string.postfix.result}" 10 | friendly_name = var.workspace_display_name 11 | location = azurerm_resource_group.aml_rg.location 12 | resource_group_name = azurerm_resource_group.aml_rg.name 13 | application_insights_id = azurerm_application_insights.aml_ai.id 14 | key_vault_id = azurerm_key_vault.aml_kv.id 15 | storage_account_id = azurerm_storage_account.aml_sa.id 16 | container_registry_id = azurerm_container_registry.aml_acr.id 17 | 18 | identity { 19 | type = "SystemAssigned" 20 | } 21 | } 22 | 23 | # Create Compute Resources in AML 24 | 25 | resource "null_resource" "compute_resouces" { 26 | provisioner "local-exec" { 27 | command="az ml computetarget create amlcompute --max-nodes 1 --min-nodes 0 --name cpu-cluster --vm-size Standard_DS3_v2 --idle-seconds-before-scaledown 600 --assign-identity [system] --vnet-name ${azurerm_subnet.compute_subnet.virtual_network_name} --subnet-name ${azurerm_subnet.compute_subnet.name} --vnet-resourcegroup-name ${azurerm_subnet.compute_subnet.resource_group_name} --resource-group ${azurerm_machine_learning_workspace.aml_ws.resource_group_name} --workspace-name ${azurerm_machine_learning_workspace.aml_ws.name}" 28 | } 29 | 30 | provisioner "local-exec" { 31 | command="az ml computetarget create computeinstance --name ci-${random_string.postfix.result}-test --vm-size Standard_DS3_v2 --vnet-name ${azurerm_subnet.compute_subnet.virtual_network_name} --subnet-name ${azurerm_subnet.compute_subnet.name} --vnet-resourcegroup-name ${azurerm_subnet.compute_subnet.resource_group_name} --resource-group ${azurerm_machine_learning_workspace.aml_ws.resource_group_name} --workspace-name ${azurerm_machine_learning_workspace.aml_ws.name}" 32 | } 33 | 34 | depends_on = [azurerm_machine_learning_workspace.aml_ws] 35 | } 36 | 37 | # DNS Zones 38 | 39 | resource "azurerm_private_dns_zone" "ws_zone_api" { 40 | name = "privatelink.api.azureml.ms" 41 | resource_group_name = azurerm_resource_group.aml_rg.name 42 | } 43 | 44 | resource "azurerm_private_dns_zone" "ws_zone_notebooks" { 45 | name = "privatelink.notebooks.azure.net" 46 | resource_group_name = azurerm_resource_group.aml_rg.name 47 | } 48 | 49 | # Linking of DNS zones to Virtual Network 50 | 51 | resource "azurerm_private_dns_zone_virtual_network_link" "ws_zone_api_link" { 52 | name = "${random_string.postfix.result}_link_api" 53 | resource_group_name = azurerm_resource_group.aml_rg.name 54 | private_dns_zone_name = azurerm_private_dns_zone.ws_zone_api.name 55 | virtual_network_id = azurerm_virtual_network.aml_vnet.id 56 | } 57 | 58 | resource "azurerm_private_dns_zone_virtual_network_link" "ws_zone_notebooks_link" { 59 | name = "${random_string.postfix.result}_link_notebooks" 60 | resource_group_name = azurerm_resource_group.aml_rg.name 61 | private_dns_zone_name = azurerm_private_dns_zone.ws_zone_notebooks.name 62 | virtual_network_id = azurerm_virtual_network.aml_vnet.id 63 | } 64 | 65 | # Private Endpoint configuration 66 | 67 | resource "azurerm_private_endpoint" "ws_pe" { 68 | name = "${var.prefix}-ws-pe-${random_string.postfix.result}" 69 | location = azurerm_resource_group.aml_rg.location 70 | resource_group_name = azurerm_resource_group.aml_rg.name 71 | subnet_id = azurerm_subnet.aml_subnet.id 72 | 73 | private_service_connection { 74 | name = "${var.prefix}-ws-psc-${random_string.postfix.result}" 75 | private_connection_resource_id = azurerm_machine_learning_workspace.aml_ws.id 76 | subresource_names = ["amlworkspace"] 77 | is_manual_connection = false 78 | } 79 | 80 | private_dns_zone_group { 81 | name = "private-dns-zone-group-ws" 82 | private_dns_zone_ids = [azurerm_private_dns_zone.ws_zone_api.id, azurerm_private_dns_zone.ws_zone_notebooks.id] 83 | } 84 | 85 | # Add Private Link after we configured the workspace and attached AKS 86 | depends_on = [null_resource.compute_resouces, azurerm_kubernetes_cluster.aml_aks] 87 | } 88 | --------------------------------------------------------------------------------