How to provision a virtual machine in azure using python SDK from an image in azure? - azure-python-sdk

I want to write a python code equivalent of creating a virtual machine from the azure portal using Create Vm button in an image resource which is present in a resource group in azure.
Any pointers or documentation related to this?

Try the sample code # provision_vm.py
# Import the needed credential and management objects from the libraries.
from azure.identity import AzureCliCredential
from azure.mgmt.resource import ResourceManagementClient
from azure.mgmt.network import NetworkManagementClient
from azure.mgmt.compute import ComputeManagementClient
import os
print(f"Provisioning a virtual machine...some operations might take a minute or two.")
# Acquire a credential object using CLI-based authentication.
credential = AzureCliCredential()
# Retrieve subscription ID from environment variable.
subscription_id = os.environ["AZURE_SUBSCRIPTION_ID"]
# Step 1: Provision a resource group
# Obtain the management object for resources, using the credentials from the CLI login.
resource_client = ResourceManagementClient(credential, subscription_id)
# Constants we need in multiple places: the resource group name and the region
# in which we provision resources. You can change these values however you want.
RESOURCE_GROUP_NAME = "PythonAzureExample-VM-rg"
LOCATION = "westus2"
# Provision the resource group.
rg_result = resource_client.resource_groups.create_or_update(RESOURCE_GROUP_NAME,
{
"location": LOCATION
}
)
print(f"Provisioned resource group {rg_result.name} in the {rg_result.location} region")
# For details on the previous code, see Example: Provision a resource group
# at https://learn.microsoft.com/azure/developer/python/azure-sdk-example-resource-group
# Step 2: provision a virtual network
# A virtual machine requires a network interface client (NIC). A NIC requires
# a virtual network and subnet along with an IP address. Therefore we must provision
# these downstream components first, then provision the NIC, after which we
# can provision the VM.
# Network and IP address names
VNET_NAME = "python-example-vnet"
SUBNET_NAME = "python-example-subnet"
IP_NAME = "python-example-ip"
IP_CONFIG_NAME = "python-example-ip-config"
NIC_NAME = "python-example-nic"
# Obtain the management object for networks
network_client = NetworkManagementClient(credential, subscription_id)
# Provision the virtual network and wait for completion
poller = network_client.virtual_networks.begin_create_or_update(RESOURCE_GROUP_NAME,
VNET_NAME,
{
"location": LOCATION,
"address_space": {
"address_prefixes": ["10.0.0.0/16"]
}
}
)
vnet_result = poller.result()
print(f"Provisioned virtual network {vnet_result.name} with address prefixes {vnet_result.address_space.address_prefixes}")
# Step 3: Provision the subnet and wait for completion
poller = network_client.subnets.begin_create_or_update(RESOURCE_GROUP_NAME,
VNET_NAME, SUBNET_NAME,
{ "address_prefix": "10.0.0.0/24" }
)
subnet_result = poller.result()
print(f"Provisioned virtual subnet {subnet_result.name} with address prefix {subnet_result.address_prefix}")
# Step 4: Provision an IP address and wait for completion
poller = network_client.public_ip_addresses.begin_create_or_update(RESOURCE_GROUP_NAME,
IP_NAME,
{
"location": LOCATION,
"sku": { "name": "Standard" },
"public_ip_allocation_method": "Static",
"public_ip_address_version" : "IPV4"
}
)
ip_address_result = poller.result()
print(f"Provisioned public IP address {ip_address_result.name} with address {ip_address_result.ip_address}")
# Step 5: Provision the network interface client
poller = network_client.network_interfaces.begin_create_or_update(RESOURCE_GROUP_NAME,
NIC_NAME,
{
"location": LOCATION,
"ip_configurations": [ {
"name": IP_CONFIG_NAME,
"subnet": { "id": subnet_result.id },
"public_ip_address": {"id": ip_address_result.id }
}]
}
)
nic_result = poller.result()
print(f"Provisioned network interface client {nic_result.name}")
# Step 6: Provision the virtual machine
# Obtain the management object for virtual machines
compute_client = ComputeManagementClient(credential, subscription_id)
VM_NAME = "ExampleVM"
USERNAME = "azureuser"
PASSWORD = "ChangePa$$w0rd24"
print(f"Provisioning virtual machine {VM_NAME}; this operation might take a few minutes.")
# Provision the VM specifying only minimal arguments, which defaults to an Ubuntu 18.04 VM
# on a Standard DS1 v2 plan with a public IP address and a default virtual network/subnet.
poller = compute_client.virtual_machines.begin_create_or_update(RESOURCE_GROUP_NAME, VM_NAME,
{
"location": LOCATION,
"storage_profile": {
"image_reference": {
"publisher": 'Canonical',
"offer": "UbuntuServer",
"sku": "16.04.0-LTS",
"version": "latest"
}
},
"hardware_profile": {
"vm_size": "Standard_DS1_v2"
},
"os_profile": {
"computer_name": VM_NAME,
"admin_username": USERNAME,
"admin_password": PASSWORD
},
"network_profile": {
"network_interfaces": [{
"id": nic_result.id,
}]
}
}
)
vm_result = poller.result()
print(f"Provisioned virtual machine {vm_result.name}")

Related

Azure - Provisioning VM fail with Message="OS Provisioning for VM 'VMNAME' did not finish in the allotted time." even though timeouts set to 60 min

Azure VM fails to provision in allotted time, fails out at 20m49s, even though timeout in code set to 60m for create. VM actually completes provisioning in 24 min. Unfortunately this means I must subsequently import the VM into the TF state. Error below:
Error: waiting for creation of Linux Virtual Machine: (Name "VMNAME" / Resource Group "uvi_waf_rg"): Code="OSProvisioningTimedOut" Message="OS Provisioning for VM 'uviwaft01' did not finish in the allotted time. The VM may still finish provisioning successfully. Please check provisioning state later. For details on how to check current provisioning state of Windows VMs, refer to https://aka.ms/WindowsVMLifecycle and Linux VMs, refer to https://aka.ms/LinuxVMLifecycle."
Here is the code that built the VM:
resource "azurerm_linux_virtual_machine" "mytestwaf" {
name = "mytestwaf"
resource_group_name = azurerm_resource_group.waf_rg.name
location = azurerm_resource_group.waf_rg.location
size = "Standard_B2ms"
admin_username = "azureuser"
network_interface_ids = [
azurerm_network_interface.mytestwaf_vnic00.id,
]
admin_ssh_key {
username = "azureuser"
public_key = file("id_rsa.pub")
}
os_disk {
caching = "ReadWrite"
storage_account_type = "Standard_LRS"
}
source_image_reference {
publisher = "kemptech"
offer = "vlm-azure"
sku = "freeloadmaster"
version = "latest"
}
boot_diagnostics {
# enabled = true
storage_account_uri = azurerm_storage_account.wafdiags.primary_blob_endpoint
}
timeouts {
create = "60m"
delete = "60m"
}
plan {
name = "freeloadmaster"
publisher = "kemptech"
product = "vlm-azure"
}
# choose correct tag when testing or running in production
tags = var.development_resource_tags
# tags = var.production_resource_tags
depends_on = [
azurerm_network_interface.mytestwaf_vnic00
]
}

Is there a way in terraform to create a replacement group of related resources before destroying the original group?

I have a VM template I'm deploying an Azure Virtual Desktop environment with terraform (via octopus deploy) to Azure. On top of the Virtual Machines, I'm installing a number of extensions which culminates with a vm extension to register the VM with the Host Pool.
I'd like to rebuild the VM each time the custom script extension is applied (Extension #2, after domain join). But in rebuilding the VM, I'd like to build out a new VM, complete with the host pool registration before any part of the existing VM is destroyed.
Please accept the cut down version below to understand what I am trying to do.
I expect the largest number of machine recreations to come from enhancements to the configuration scripts that configure the server on creation. Not all of the commands are expected to be idempotent and we want the AVD vms to be ephemeral. If an issue is encountered, the support team is expected to be able to drain a server and destroy it once empty to get a replacement by terraform apply. In a case where the script gets updated though, we want to be able to replace all VMs quickly in an emergency, or at the very least minimize the nightly maintenance window.
Script Process: parameterized script > gets filled out as a template file > gets stored as an az blob > called by custom script extension > executed on the machine.
VM build process: VM is provisioned > currently 8 extensions get applied one at a time, starting with the domain join, then the custom script extension, followed by several Azure monitoring extensions, and finally the host pool registration extension.
I've been trying to use the create_before_destroy lifecycle feature, but I can't get it to spin up the VM, and apply all extensions before it begins removing the hostpool registration from the existing VMs. I assume there's a way to do it using the triggers, but I'm not sure how to do it in such a way that it always has at least the current number of VMs.
It would also need to be able to stop if it encounters an error on the new vm, before destroying the existing vm (or better yet, be authorized to rebuild VMs if an extension fails part way through).
resource "random_pet" "avd_vm" {
prefix = var.client_name
length = 1
keepers = {
# Generate a new pet name each time we update the setup_host script
source_content = "${data.template_file.setup_host.rendered}"
}
}
data "template_file" "setup_host" {
template = file("${path.module}\\scripts\\setup-host.tpl")
vars = {
storageAccountName = azurerm_storage_account.storage.name
storageAccountKey = azurerm_storage_account.storage.primary_access_key
domain = var.domain
aad_group_name = var.aad_group_name
}
}
resource "azurerm_storage_blob" "setup_host" {
name = "setup-host.ps1"
storage_account_name = azurerm_storage_account.scripts.name
storage_container_name = time_sleep.container_rbac.triggers["name"]
type = "Block"
source_content = data.template_file.setup_host.rendered #"${path.module}\\scripts\\setup-host.ps1"
depends_on = [
azurerm_role_assignment.account1_write,
data.template_file.setup_host,
time_sleep.container_rbac
]
}
data "template_file" "client_r_drive_mapping" {
template = file("${path.module}\\scripts\\client_r_drive_mapping.tpl")
vars = {
storageAccountName = azurerm_storage_account.storage.name
storageAccountKey = azurerm_storage_account.storage.primary_access_key
}
}
resource "azurerm_windows_virtual_machine" "example" {
count = length(random_pet.avd_vm)
name = "${random_pet.avd_vm[count.index].id}"
...
lifecycle {
ignore_changes = [
boot_diagnostics,
identity
]
}
}
resource "azurerm_virtual_machine_extension" "first-domain_join_extension" {
count = var.rdsh_count
name = "${var.client_name}-avd-${random_pet.avd_vm[count.index].id}-domainJoin"
virtual_machine_id = azurerm_windows_virtual_machine.avd_vm.*.id[count.index]
publisher = "Microsoft.Compute"
type = "JsonADDomainExtension"
type_handler_version = "1.3"
auto_upgrade_minor_version = true
settings = <<SETTINGS
{
"Name": "${var.domain_name}",
"OUPath": "${var.ou_path}",
"User": "${var.domain_user_upn}#${var.domain_name}",
"Restart": "true",
"Options": "3"
}
SETTINGS
protected_settings = <<PROTECTED_SETTINGS
{
"Password": "${var.admin_password}"
}
PROTECTED_SETTINGS
lifecycle {
ignore_changes = [settings, protected_settings]
}
depends_on = [
azurerm_virtual_network_peering.out-primary,
azurerm_virtual_network_peering.in-primary,
azurerm_virtual_network_peering.in-secondary
]
}
# Multiple scripts called by ./<scriptname referencing them in follow-up scripts
# https://web.archive.org/web/20220127015539/https://learn.microsoft.com/en-us/azure/virtual-machines/extensions/custom-script-windows
# https://learn.microsoft.com/en-us/azure/virtual-machines/extensions/custom-script-windows#using-multiple-scripts
resource "azurerm_virtual_machine_extension" "second-custom_scripts" {
count = var.rdsh_count
name = "${random_pet.avd_vm[count.index].id}-setup-host"
virtual_machine_id = azurerm_windows_virtual_machine.avd_vm.*.id[count.index]
publisher = "Microsoft.Compute"
type = "CustomScriptExtension"
type_handler_version = "1.10"
auto_upgrade_minor_version = "true"
protected_settings = <<PROTECTED_SETTINGS
{
"storageAccountName": "${azurerm_storage_account.scripts.name}",
"storageAccountKey": "${azurerm_storage_account.scripts.primary_access_key}"
}
PROTECTED_SETTINGS
settings = <<SETTINGS
{
"fileUris": ["https://${azurerm_storage_account.scripts.name}.blob.core.windows.net/scripts/setup-host.ps1","https://${azurerm_storage_account.scripts.name}.blob.core.windows.net/scripts/client_r_drive_mapping.ps1"],
"commandToExecute": "powershell -ExecutionPolicy Unrestricted -file setup-host.ps1"
}
SETTINGS
depends_on = [
azurerm_virtual_machine_extension.first-domain_join_extension,
azurerm_storage_blob.setup_host
]
}
resource "azurerm_virtual_machine_extension" "last_host_extension_hp_registration" {
count = var.rdsh_count
name = "${var.client_name}-${random_pet.avd_vm[count.index].id}-avd_dsc"
virtual_machine_id = azurerm_windows_virtual_machine.avd_vm.*.id[count.index]
publisher = "Microsoft.Powershell"
type = "DSC"
type_handler_version = "2.73"
auto_upgrade_minor_version = true
settings = <<-SETTINGS
{
"modulesUrl": "https://wvdportalstorageblob.blob.core.windows.net/galleryartifacts/Configuration_3-10-2021.zip",
"configurationFunction": "Configuration.ps1\\AddSessionHost",
"properties": {
"HostPoolName":"${azurerm_virtual_desktop_host_pool.pooleddepthfirst.name}"
}
}
SETTINGS
protected_settings = <<PROTECTED_SETTINGS
{
"properties": {
"registrationInfoToken": "${azurerm_virtual_desktop_host_pool_registration_info.pooleddepthfirst.token}"
}
}
PROTECTED_SETTINGS
lifecycle {
ignore_changes = [settings, protected_settings]
}
depends_on = [
azurerm_virtual_machine_extension.second-custom_scripts
]
}

Error in terraform module mainly to do with log analytics

I am in the process of learning TF, and on the subject of modules, at the same time I have decided to only create resources on my Azure account using TF as a way to accelerate my learning. To this note, I found this gitbub repo https://github.com/kumarvna/terraform-azurerm-virtual-machine
I have been following the contents and trying to reproduce on my test system, I have tried to contact the author to no avail and felt I have already wasted 2 weeks trying to fix the problem, let me ask on here for help.
My setup.
Pulled the code from the repo onto my laptop.
logged onto my Azure account from a powershell console.
created a folder called create_vm
and in that folder
On my main.tf file, I have the following. This is a linux example, but I had the same issues with a windows example also.
# Azurerm provider configuration
provider "azurerm" {
features {}
}
# Creates a new resource group
resource "azurerm_resource_group" "test_build" {
name = "testBuild"
location = "West Europe"
}
# Creates a new network
resource "azurerm_virtual_network" "example" {
name = "example-network"
location = azurerm_resource_group.test_build.location
resource_group_name = azurerm_resource_group.test_build.name
address_space = ["10.0.0.0/16"]
dns_servers = ["10.0.0.4", "10.0.0.5"]
subnet {
name = "subnet1"
address_prefix = "10.0.1.0/24"
}
}
# Creates a new la workspace
resource "azurerm_log_analytics_workspace" "la" {
name = "loganalytics-we-sharedtest2"
resource_group_name = azurerm_resource_group.test_build.name
}
module "virtual-machine" {
source = "kumarvna/virtual-machine/azurerm"
version = "2.3.0"
# Resource Group, location, VNet and Subnet details
resource_group_name = azurerm_resource_group.test_build.name
location = "westeurope"
virtual_network_name = azurerm_virtual_network.example.name
subnet_name = "subnet1"
virtual_machine_name = "vm-linux"
# This module support multiple Pre-Defined Linux and Windows Distributions.
# Check the README.md file for more pre-defined images for Ubuntu, Centos, RedHat.
# Please make sure to use gen2 images supported VM sizes if you use gen2 distributions
# Specify `disable_password_authentication = false` to create random admin password
# Specify a valid password with `admin_password` argument to use your own password
# To generate SSH key pair, specify `generate_admin_ssh_key = true`
# To use existing key pair, specify `admin_ssh_key_data` to a valid SSH public key path.
os_flavor = "linux"
linux_distribution_name = "ubuntu2004"
virtual_machine_size = "Standard_B2s"
generate_admin_ssh_key = true
instances_count = 2
# Proxymity placement group, Availability Set and adding Public IP to VM's are optional.
# remove these argument from module if you dont want to use it.
enable_proximity_placement_group = true
enable_vm_availability_set = true
enable_public_ip_address = true
# Network Seurity group port allow definitions for each Virtual Machine
# NSG association to be added automatically for all network interfaces.
# Remove this NSG rules block, if `existing_network_security_group_id` is specified
nsg_inbound_rules = [
{
name = "ssh"
destination_port_range = "22"
source_address_prefix = "*"
},
{
name = "http"
destination_port_range = "80"
source_address_prefix = "*"
},
]
# Boot diagnostics to troubleshoot virtual machines, by default uses managed
# To use custom storage account, specify `storage_account_name` with a valid name
# Passing a `null` value will utilize a Managed Storage Account to store Boot Diagnostics
enable_boot_diagnostics = true
# Attach a managed data disk to a Windows/Linux VM's. Possible Storage account type are:
# `Standard_LRS`, `StandardSSD_ZRS`, `Premium_LRS`, `Premium_ZRS`, `StandardSSD_LRS`
# or `UltraSSD_LRS` (UltraSSD_LRS only available in a region that support availability zones)
# Initialize a new data disk - you need to connect to the VM and run diskmanagemnet or fdisk
data_disks = [
{
name = "disk1"
disk_size_gb = 100
storage_account_type = "StandardSSD_LRS"
},
{
name = "disk2"
disk_size_gb = 200
storage_account_type = "Standard_LRS"
}
]
# (Optional) To enable Azure Monitoring and install log analytics agents
# (Optional) Specify `storage_account_name` to save monitoring logs to storage.
log_analytics_workspace_id = azurerm_log_analytics_workspace.la.id
# Deploy log analytics agents to virtual machine.
# Log analytics workspace customer id and primary shared key required.
deploy_log_analytics_agent = true
log_analytics_customer_id = azurerm_log_analytics_workspace.la.workspace_id
log_analytics_workspace_primary_shared_key = azurerm_log_analytics_workspace.la.primary_shared_key
# Adding additional TAG's to your Azure resources
tags = {
ProjectName = "demo-project"
Env = "dev"
Owner = "user#example.com"
BusinessUnit = "CORP"
ServiceClass = "Gold"
}
}
on variables.tf.
variable "log_analytics_workspace_name" {
description = "The name of log analytics workspace name"
default = null
}
variable "storage_account_name" {
description = "The name of the hub storage account to store logs"
default = null
}
variable "create_resource_group" {
description = "Whether to create resource group and use it for all networking resources"
default = true
}
Please note that I added the create_resource_group variable to try to resolve my issue to no avail.
I then run
terraform init
terraform plan
I get the following error with terraform plan
│ Error: Error: Log Analytics workspaces "loganalytics-we-sharedtest2" (Resource Group "rg-shared-westeurope-01") was not found
│
│ with data.azurerm_log_analytics_workspace.example,
│ on main.tf line 6, in data "azurerm_log_analytics_workspace" "example":
│ 6: data "azurerm_log_analytics_workspace" "example" {
│
╵
╷
│ Error: Error: Resource Group "rg-shared-westeurope-01" was not found
│
│ with module.virtual-machine.data.azurerm_resource_group.rg,
│ on .terraform\modules\virtual-machine\main.tf line 27, in data "azurerm_resource_group" "rg":
│ 27: data "azurerm_resource_group" "rg" {
│
What have I done ?
Looked through the code to see what I am missing.
Added the variable at the top. Tried to contact the author to no
avail.
Tried to use an existing resource group, I feel this defeats the
purpose of having a variable that asks if a new resource group can
be created in case it doesn't already exist.
What else is confusing ?
I initially had another folder for modules, i later came to realise that the module is a public one being pulled down whenever I ran terraform init, now is there a way to have this as a localised module ?
I have made the changes recommended by the answer below, however in order not to turn the question into a long winded one, I have placed the error that I got below.
│ Error: Error: Subnet: (Name "subnet1" / Virtual Network Name "testBuild_vnet" / Resource Group "testBuild") was not found
│
│ with module.virtual-machine.data.azurerm_subnet.snet,
│ on .terraform\modules\virtual-machine\main.tf line 36, in data "azurerm_subnet" "snet":
│ 36: data "azurerm_subnet" "snet" {
│
╵
╷
│ Error: Invalid count argument
│
│ on .terraform\modules\virtual-machine\main.tf line 443, in resource "azurerm_monitor_diagnostic_setting" "nsg":
│ 443: count = var.existing_network_security_group_id == null && var.log_analytics_workspace_id != null ? 1 : 0
│
│ The "count" value depends on resource attributes that cannot be determined until apply, so Terraform cannot predict how many instances will be created. To work around this, use the -target argument to first apply only the resources that the count depends on.
I think the misunderstanding is that you think the module creates a resource group, but that is not the case. This module expects an already existing resource group as var.resource_group_name (same goes for the input variables virtual_network_name, subnet_name and log_analytics_workspace_id).
The main difference between the resource_ and data_ prefix is that data sources are read-only and "only" fetch already existing infrastructure for further use:
Data sources allow Terraform to use information defined outside of
Terraform, defined by another separate Terraform configuration, or
modified by functions.
https://www.terraform.io/language/data-sources
So in your case it should work like (not tested):
# Azurerm provider configuration
provider "azurerm" {
features {}
}
# Creates a new resource group
resource "azurerm_resource_group" "test_build" {
name = "testBuild"
location = "West Europe"
}
# Creates a new network
resource "azurerm_virtual_network" "example" {
name = "example-network"
location = azurerm_resource_group.test_build.location
resource_group_name = azurerm_resource_group.test_build.name
address_space = ["10.0.0.0/16"]
dns_servers = ["10.0.0.4", "10.0.0.5"]
subnet {
name = "subnet1"
address_prefix = "10.0.1.0/24"
}
}
# Creates a new la workspace
resource "azurerm_log_analytics_workspace" "la" {
name = "loganalytics-we-sharedtest2"
resource_group_name = azurerm_resource_group.test_build.name
}
module "virtual-machine" {
source = "kumarvna/virtual-machine/azurerm"
version = "2.3.0"
# Resource Group, location, VNet and Subnet details
resource_group_name = azurerm_resource_group.test_build.name
location = "westeurope"
virtual_network_name = azurerm_virtual_network.example.name
subnet_name = "subnet1"
virtual_machine_name = "vm-linux"
# This module support multiple Pre-Defined Linux and Windows Distributions.
# Check the README.md file for more pre-defined images for Ubuntu, Centos, RedHat.
# Please make sure to use gen2 images supported VM sizes if you use gen2 distributions
# Specify `disable_password_authentication = false` to create random admin password
# Specify a valid password with `admin_password` argument to use your own password
# To generate SSH key pair, specify `generate_admin_ssh_key = true`
# To use existing key pair, specify `admin_ssh_key_data` to a valid SSH public key path.
os_flavor = "linux"
linux_distribution_name = "ubuntu2004"
virtual_machine_size = "Standard_B2s"
generate_admin_ssh_key = true
instances_count = 2
# Proxymity placement group, Availability Set and adding Public IP to VM's are optional.
# remove these argument from module if you dont want to use it.
enable_proximity_placement_group = true
enable_vm_availability_set = true
enable_public_ip_address = true
# Network Seurity group port allow definitions for each Virtual Machine
# NSG association to be added automatically for all network interfaces.
# Remove this NSG rules block, if `existing_network_security_group_id` is specified
nsg_inbound_rules = [
{
name = "ssh"
destination_port_range = "22"
source_address_prefix = "*"
},
{
name = "http"
destination_port_range = "80"
source_address_prefix = "*"
},
]
# Boot diagnostics to troubleshoot virtual machines, by default uses managed
# To use custom storage account, specify `storage_account_name` with a valid name
# Passing a `null` value will utilize a Managed Storage Account to store Boot Diagnostics
enable_boot_diagnostics = true
# Attach a managed data disk to a Windows/Linux VM's. Possible Storage account type are:
# `Standard_LRS`, `StandardSSD_ZRS`, `Premium_LRS`, `Premium_ZRS`, `StandardSSD_LRS`
# or `UltraSSD_LRS` (UltraSSD_LRS only available in a region that support availability zones)
# Initialize a new data disk - you need to connect to the VM and run diskmanagemnet or fdisk
data_disks = [
{
name = "disk1"
disk_size_gb = 100
storage_account_type = "StandardSSD_LRS"
},
{
name = "disk2"
disk_size_gb = 200
storage_account_type = "Standard_LRS"
}
]
# (Optional) To enable Azure Monitoring and install log analytics agents
# (Optional) Specify `storage_account_name` to save monitoring logs to storage.
log_analytics_workspace_id = azurerm_log_analytics_workspace.la.id
# Deploy log analytics agents to virtual machine.
# Log analytics workspace customer id and primary shared key required.
deploy_log_analytics_agent = true
log_analytics_customer_id = azurerm_log_analytics_workspace.la.workspace_id
log_analytics_workspace_primary_shared_key = azurerm_log_analytics_workspace.la.primary_shared_key
# Adding additional TAG's to your Azure resources
tags = {
ProjectName = "demo-project"
Env = "dev"
Owner = "user#example.com"
BusinessUnit = "CORP"
ServiceClass = "Gold"
}
}
Just adding a new variable called create_resource_group will not do anything as long as there is no corresponding logic/code behind it.

Azure Isolated App Service Environment fails to create with Terraform with "An error has occurred."

I am attempting to create an Isolated App Service Environment (ASE) in Azure using Terraform. I have succeeded once and have an ASE running. Attempts to create a second ASE in the same subscription, but within a separate resource group, fail. The only error message available is "An error has occurred". Investigation within Azure Monitoring reveals that the create request which was delivered via an ARM template has encountered a 500 error (Internal Server Error) somewhere in the process of creating the ASE. However, no details are available in the log message to indicate where/when/how Azure encountered the 500 error.
Environment:
azure cli (2.26.1)
terraform (0.14.11)
hashicorp/azurerm provider (2.67.0)
Details
I am creating multiple environments for my project: dev, test, and staging. They are each in separate resource groups within the same Azure subscription. All resources in my terraform are constructed with names unique to the environment/resource group. The intended lifecycle is for infrastructure changes to be deployed to dev, then test, then staging (and eventually a prod environment in a separate subscription). The initial configuration and deployment to dev has succeeded. Attempts to deploy to test, or to deploy a different ASE to dev, fail abjectly with very little feedback.
The original dev ASE is a v1 ASE. I have attempted to create a second ASE in test using the same terraform code. I have also tried creating a v3 ASE in dev (because the v3 will be cheaper). If the v3 ASE deploys successfully I will cut over to it in dev and will use it as the basis for test and stage instead of the v1 ASE. Regardless whether I try to deploy a v1 ASE to a separate resource group, or whether I try to deploy a v3 ASE to the same resource group as the v1 ASE, I get the same error.
This is the Terraform for the v1 ASE, including the subnet which will host it:
resource "azurerm_subnet" "subnet" {
name = "${local.prefix}-subnet"
resource_group_name = var.resource_group_name
virtual_network_name = var.vnet_name
address_prefixes = var.cidrs
enforce_private_link_endpoint_network_policies = var.enforce_private_link_endpoint_network_policies
enforce_private_link_service_network_policies = var.enforce_private_link_service_network_policies
dynamic "delegation" {
for_each = var.delegations
content {
name = "${local.prefix}-delegation-${delegation.key}"
service_delegation {
name = delegation.value.name
actions = delegation.value.actions
}
}
}
// List of Service endpoints to associate with the subnet.
service_endpoints = var.service_endpoints
}
resource "azurerm_network_security_group" "nsg" {
name = "${local.prefix}-nsg"
location = var.resource_group_location
resource_group_name = var.resource_group_name
tags = merge(map("Name", "${local.prefix}-nsg"), local.tags)
}
resource "azurerm_subnet_network_security_group_association" "nsg_assoc" {
subnet_id = azurerm_subnet.subnet.id
network_security_group_id = azurerm_network_security_group.nsg.id
}
resource "azurerm_network_security_rule" "ase_mgmt" {
name = "${local.prefix}-ase-mgmt"
priority = 100
direction = "Inbound"
access = "Allow"
protocol = "*"
source_port_range = "*"
source_address_prefix = "AppServiceManagement"
destination_port_range = "454-455"
destination_address_prefix = var.subnet_cidr
resource_group_name = var.resource_group_name
network_security_group_name = azurerm_network_security_group.nsg.name
}
resource "azurerm_network_security_rule" "ingress" {
for_each = {
for idx, cidr in var.ingress_cidrs : idx => cidr
}
name = "${local.prefix}-ingress-${each.key}"
priority = 200 + each.key
direction = "Inbound"
access = "Allow"
protocol = "*"
source_port_range = "*"
source_address_prefix = each.value
destination_port_range = "*"
destination_address_prefix = var.subnet_cidr
resource_group_name = var.resource_group_name
network_security_group_name = azurerm_network_security_group.nsg.name
}
resource "azurerm_app_service_environment" "env" {
name = "${local.prefix}-ase"
subnet_id = azurerm_subnet.subnet.id
pricing_tier = var.pricing_tier
front_end_scale_factor = var.front_scale_factor
internal_load_balancing_mode = "Web, Publishing"
allowed_user_ip_cidrs = var.allowed_user_ip_cidrs
cluster_setting {
name = "DisableTls1.0"
value = "1"
}
depends_on = [
azurerm_network_security_rule.ase_mgmt
]
}
The v3 ASE is configured identically, except for azurerm_app_service_environment.env, which is replaced with:
resource "azurerm_app_service_environment_v3" "env" {
name = "${local.prefix}-ase-v3"
resource_group_name = var.resource_group_name
subnet_id = azurerm_subnet.subnet.id
cluster_setting {
name = "DisableTls1.0"
value = "1"
}
depends_on = [
azurerm_network_security_rule.ase_mgmt
]
}
Results
Terraform generates this ARM request (identifiers have been redacted):
2021/07/19 09:07:44 [TRACE] dag/walk: vertex "root" is waiting for "meta.count-boundary (EachMode fixup)"
2021-07-19T09:07:45.121-0700 [DEBUG] plugin.terraform-provider-azurerm_v2.67.0_x5: AzureRM Request:
PUT /subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/xxxxxxxx-dev-rg/providers/Microsoft.Web/hostingEnvironments/xxxxxxxx-dev-ase-v3?api-version=2020-06-01 HTTP/1.1
Host: management.azure.com
User-Agent: Go/go1.16.3 (amd64-darwin) go-autorest/v14.2.1 Azure-SDK-For-Go/v55.4.0 web/2020-06-01 HashiCorp Terraform/0.14.11 (+https://www.terraform.io) Terraform Plugin SDK/2.7.0 terraform-provider-azurerm/2.67.0 pid-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
Content-Length: 382
Content-Type: application/json; charset=utf-8
X-Ms-Correlation-Request-Id: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
Accept-Encoding: gzip
{
"kind":"ASEV3",
"location":"centralus",
"properties":
{
"clusterSettings":[{
"name":"DisableTls1.0",
"value":"1"
}],
"name":"xxxxxxxx-dev-ase-v3",
"virtualNetwork":{
"id":"/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/xxxxxxxx-dev-rg/providers/Microsoft.Network/virtualNetworks/xxxxxxxx-dev-vnet/subnets/xxxxxxxx-dev-ase-v3-ase-subnet",
"subnet":"xxxxxxxx-dev-ase-v3-ase-subnet"
}
},
"tags":{}
}
The error eventually reported by Terraform looks like this in the debug output:
2021/07/19 09:13:53 [DEBUG] azurerm_app_service_environment_v3.env: apply errored, but we're indicating that via the Error pointer rather than returning it: creating App Service Environment: (Hosting Environment Name "xxxxxxxx-dev-ase-v3" / Resource Group "xxxxxxxx-dev-rg"): web.AppServiceEnvironmentsClient#CreateOrUpdate: Failure sending request: StatusCode=0 -- Original Error: Code="Failed" Message="The async operation failed." AdditionalInfo=[{"Message":"An error has occurred."}]: creating App Service Environment: (Hosting Environment Name "xxxxxxxx-dev-ase-v3" / Resource Group "xxxxxxxx-dev-rg"): web.AppServiceEnvironmentsClient#CreateOrUpdate: Failure sending request: StatusCode=0 -- Original Error: Code="Failed" Message="The async operation failed." AdditionalInfo=[{"Message":"An error has occurred."}]
Reviewing the logs within Azure Monitor, I find a similarly vague error message. The message is summarized as InternalServerError. The JSON detail is included here for reference:
{
"authorization": {
"action": "Microsoft.Web/hostingEnvironments/write",
"scope": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/resourceGroups/xxxxxxxx-dev-rg/providers/Microsoft.Web/hostingEnvironments/xxxxxxxx-dev-ase-v3"
},
"caller": "duffy.gillman#presencepg.com",
"channels": "Operation",
"claims": {
//REDACTED
},
"correlationId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx",
"description": "",
"eventDataId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx",
"eventName": {
"value": "EndRequest",
"localizedValue": "End request"
},
"category": {
"value": "Administrative",
"localizedValue": "Administrative"
},
"eventTimestamp": "2021-07-19T15:51:45.4835627Z",
"id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/resourceGroups/xxxxxxxx-dev-rg/providers/Microsoft.Web/hostingEnvironments/xxxxxxxx-dev-ase-v3/events/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/ticks/637623067054835627",
"level": "Error",
"operationId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx",
"operationName": {
"value": "Microsoft.Web/hostingEnvironments/write",
"localizedValue": "Create or Update App Service Environment"
},
"resourceGroupName": "xxxxxxxx-dev-rg",
"resourceProviderName": {
"value": "Microsoft.Web",
"localizedValue": "Azure Web Sites"
},
"resourceType": {
"value": "Microsoft.Web/hostingEnvironments",
"localizedValue": "Microsoft.Web/hostingEnvironments"
},
"resourceId": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/resourceGroups/xxxxxxxx-dev-rg/providers/Microsoft.Web/hostingEnvironments/xxxxxxxx-dev-ase-v3",
"status": {
"value": "Failed",
"localizedValue": "Failed"
},
"subStatus": {
"value": "InternalServerError",
"localizedValue": "Internal Server Error (HTTP Status Code: 500)"
},
"submissionTimestamp": "2021-07-19T15:52:29.177138Z",
"subscriptionId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx",
"tenantId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx",
"properties": {
"statusCode": "InternalServerError",
"serviceRequestId": null,
"statusMessage": "{\"Message\":\"An error has occurred.\"}",
"eventCategory": "Administrative",
"entity": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/resourceGroups/xxxxxxxx-dev-rg/providers/Microsoft.Web/hostingEnvironments/xxxxxxxx-dev-ase-v3",
"message": "Microsoft.Web/hostingEnvironments/write",
"hierarchy": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx"
},
"relatedEvents": []
}
This to me looks like you have hit some subscription limitations. If you try create the same ASE via the Azure Portal does it provision for you? It will be good to know if you get an error trying to do the same thing via UI, if you did hit the same error in the GUI it will give you a better error message.

deploy a machine (with qcow2 image) on KVM automatically via Terraform

I am new to terraform and i am trying to deploy a machine (with qcow2 image) on KVM automatically via Terraform.
i found this tf file:
provider "libvirt" {
uri = "qemu:///system"
}
#provider "libvirt" {
# alias = "server2"
# uri = "qemu+ssh://root#192.168.100.10/system"
#}
resource "libvirt_volume" "centos7-qcow2" {
name = "centos7.qcow2"
pool = "default"
source = "https://cloud.centos.org/centos/7/images/CentOS-7-x86_64-GenericCloud.qcow2"
#source = "./CentOS-7-x86_64-GenericCloud.qcow2"
format = "qcow2"
}
# Define KVM domain to create
resource "libvirt_domain" "db1" {
name = "db1"
memory = "1024"
vcpu = 1
network_interface {
network_name = "default"
}
disk {
volume_id = "${libvirt_volume.centos7-qcow2.id}"
}
console {
type = "pty"
target_type = "serial"
target_port = "0"
}
graphics {
type = "spice"
listen_type = "address"
autoport = true
}
}
my questions are:
(source) the path of my qcow file has to be localy on my computer ?
I have a KVM machine ip that i connected it remotely by its ip. where should i put this ip in this tf file?
when i did it manually, i run "virt manager", do i need to write it here anywhere?
thank's a lot.
No. It can be https also.
Do you mean a KVM host that VMs will be created ? Then you need to configure remote kvm access on that host and in the uri section of provider block you need to write its ip.
uri = "qemu+ssh://username#IP_OF_HOST/system"
You dont need virt-manager when you use terraform. You should use terraform resources for managing VM.
https://registry.terraform.io/providers/dmacvicar/libvirt/latest/docs
https://github.com/dmacvicar/terraform-provider-libvirt/tree/main/examples/v0.13

Resources