Quickstart: Create a public load balancer to load balance VMs using a Bicep file
In this quickstart, you learn to use a BICEP file to create a public Azure load balancer. The public load balancer distributes traffic to virtual machines in a virtual network located in the load balancer's backend pool. Along with the public load balancer, this template creates a virtual network, network interfaces, a NAT Gateway, and an Azure Bastion instance.
Using a Bicep file takes fewer steps comparing to other deployment methods.
Bicep is a domain-specific language (DSL) that uses declarative syntax to deploy Azure resources. It provides concise syntax, reliable type safety, and support for code reuse. Bicep offers the best authoring experience for your infrastructure-as-code solutions in Azure.
Prerequisites
If you don't have an Azure subscription, create a free account before you begin.
Review the Bicep file
The Bicep file used in this quickstart is from Azure Quickstart Templates.
Load balancer and public IP SKUs must match. When you create a standard load balancer, you must also create a new standard public IP address that is configured as the frontend for the standard load balancer. If you want to create a basic load balancer, use this template. Microsoft recommends using standard SKU for production workloads.
@description('Specifies a project name that is used for generating resource names.')
param projectName string
@description('Specifies the location for all of the resources created by this template.')
param location string = resourceGroup().location
@description('Specifies the virtual machine administrator username.')
param adminUsername string
@description('Specifies the virtual machine administrator password.')
@secure()
param adminPassword string
@description('Size of the virtual machine')
param vmSize string = 'Standard_D2s_v3'
@description('The Windows version for the VM. This will pick a fully patched image of this given Windows version.')
@allowed([
'2016-datacenter-gensecond'
'2016-datacenter-server-core-g2'
'2016-datacenter-server-core-smalldisk-g2'
'2016-datacenter-smalldisk-g2'
'2016-datacenter-with-containers-g2'
'2016-datacenter-zhcn-g2'
'2019-datacenter-core-g2'
'2019-datacenter-core-smalldisk-g2'
'2019-datacenter-core-with-containers-g2'
'2019-datacenter-core-with-containers-smalldisk-g2'
'2019-datacenter-gensecond'
'2019-datacenter-smalldisk-g2'
'2019-datacenter-with-containers-g2'
'2019-datacenter-with-containers-smalldisk-g2'
'2019-datacenter-zhcn-g2'
'2022-datacenter-azure-edition'
'2022-datacenter-azure-edition-core'
'2022-datacenter-azure-edition-core-smalldisk'
'2022-datacenter-azure-edition-smalldisk'
'2022-datacenter-core-g2'
'2022-datacenter-core-smalldisk-g2'
'2022-datacenter-g2'
'2022-datacenter-smalldisk-g2'
])
param OSVersion string = '2022-datacenter-azure-edition'
@description('Linux Sku')
@allowed([
'vs-2019-ent-latest-win11-n-gen2'
'vs-2019-pro-general-win11-m365-gen2'
'vs-2019-comm-latest-win11-n-gen2'
'vs-2019-ent-general-win10-m365-gen2'
'vs-2019-ent-general-win11-m365-gen2'
'vs-2019-pro-general-win10-m365-gen2'
])
param imageSku string = 'vs-2019-ent-latest-win11-n-gen2'
@description('Security Type of the Virtual Machine.')
@allowed([
'Standard'
'TrustedLaunch'
])
param securityType string = 'TrustedLaunch'
var securityProfileJson = {
uefiSettings: {
secureBootEnabled: true
vTpmEnabled: true
}
securityType: securityType
}
var lbName = '${projectName}-lb'
var lbSkuName = 'Standard'
var lbPublicIpAddressName = '${projectName}-lbPublicIP'
var lbFrontEndName = 'LoadBalancerFrontEnd'
var lbBackendPoolName = 'LoadBalancerBackEndPool'
var lbProbeName = 'loadBalancerHealthProbe'
var nsgName = '${projectName}-nsg'
var vNetName = '${projectName}-vnet'
var vNetAddressPrefix = '10.0.0.0/16'
var vNetSubnetName = 'BackendSubnet'
var vNetSubnetAddressPrefix = '10.0.0.0/24'
var bastionName = '${projectName}-bastion'
var bastionSubnetName = 'AzureBastionSubnet'
var vNetBastionSubnetAddressPrefix = '10.0.1.0/24'
var bastionPublicIPAddressName = '${projectName}-bastionPublicIP'
var vmStorageAccountType = 'Premium_LRS'
var extensionName = 'GuestAttestation'
var extensionPublisher = 'Microsoft.Azure.Security.WindowsAttestation'
var extensionVersion = '1.0'
var maaTenantName = 'GuestAttestation'
var maaEndpoint = substring('emptyString', 0, 0)
var ascReportingEndpoint = substring('emptystring', 0, 0)
var natGatewayName = '${projectName}-natgateway'
var natGatewayPublicIPAddressName = '${projectName}-natPublicIP'
resource project_vm_1_networkInterface 'Microsoft.Network/networkInterfaces@2021-08-01' = [for i in range(0, 3): {
name: '${projectName}-vm${(i + 1)}-networkInterface'
location: location
properties: {
ipConfigurations: [
{
name: 'ipconfig1'
properties: {
privateIPAllocationMethod: 'Dynamic'
subnet: {
id: vNetName_vNetSubnetName.id
}
loadBalancerBackendAddressPools: [
{
id: resourceId('Microsoft.Network/loadBalancers/backendAddressPools', lbName, lbBackendPoolName)
}
]
}
}
]
networkSecurityGroup: {
id: nsg.id
}
}
dependsOn: [
lb
]
}]
resource project_vm_1_InstallWebServer 'Microsoft.Compute/virtualMachines/extensions@2021-11-01' = [for i in range(0, 3): {
name: '${projectName}-vm${(i + 1)}/InstallWebServer'
location: location
properties: {
publisher: 'Microsoft.Compute'
type: 'CustomScriptExtension'
typeHandlerVersion: '1.10'
autoUpgradeMinorVersion: true
settings: {
commandToExecute: 'powershell.exe Install-WindowsFeature -name Web-Server -IncludeManagementTools && powershell.exe remove-item \'C:\\inetpub\\wwwroot\\iisstart.htm\' && powershell.exe Add-Content -Path \'C:\\inetpub\\wwwroot\\iisstart.htm\' -Value $(\'Hello World from \' + $env:computername)'
}
}
dependsOn: [
project_vm_1
]
}]
resource project_vm_1 'Microsoft.Compute/virtualMachines@2021-11-01' = [for i in range(1, 3): {
name: '${projectName}-vm${i}'
location: location
zones: [
string(i)
]
properties: {
hardwareProfile: {
vmSize: vmSize
}
storageProfile: {
imageReference: {
publisher: 'MicrosoftWindowsServer'
offer: 'WindowsServer'
sku: OSVersion
version: 'latest'
}
osDisk: {
createOption: 'FromImage'
managedDisk: {
storageAccountType: vmStorageAccountType
}
}
}
networkProfile: {
networkInterfaces: [
{
id: resourceId('Microsoft.Network/networkInterfaces', '${projectName}-vm${i}-networkInterface')
}
]
}
osProfile: {
computerName: '${projectName}-vm${i}'
adminUsername: adminUsername
adminPassword: adminPassword
windowsConfiguration: {
enableAutomaticUpdates: true
provisionVMAgent: true
}
}
securityProfile: ((securityType == 'TrustedLaunch') ? securityProfileJson : null)
}
dependsOn: [
project_vm_1_networkInterface
]
}]
resource projectName_vm_1_3_GuestAttestation 'Microsoft.Compute/virtualMachines/extensions@2022-03-01' = [for i in range(1, 3): if ((securityType == 'TrustedLaunch') && ((securityProfileJson.uefiSettings.secureBootEnabled == true) && (securityProfileJson.uefiSettings.vTpmEnabled == true))) {
name: '${projectName}-vm${i}/GuestAttestation'
location: location
properties: {
publisher: extensionPublisher
type: extensionName
typeHandlerVersion: extensionVersion
autoUpgradeMinorVersion: true
enableAutomaticUpgrade: true
settings: {
AttestationConfig: {
MaaSettings: {
maaEndpoint: maaEndpoint
maaTenantName: maaTenantName
}
AscSettings: {
ascReportingEndpoint: ascReportingEndpoint
ascReportingFrequency: ''
}
useCustomToken: 'false'
disableAlerts: 'false'
}
}
}
dependsOn: [
project_vm_1
]
}]
resource natGateway 'Microsoft.Network/natGateways@2021-05-01' = {
name: natGatewayName
location: location
sku: {
name: 'Standard'
}
properties: {
idleTimeoutInMinutes: 4
publicIpAddresses: [
{
id: natGatewayPublicIPAddress.id
}
]
}
}
resource natGatewayPublicIPAddress 'Microsoft.Network/publicIPAddresses@2021-05-01' = {
name: natGatewayPublicIPAddressName
location: location
sku: {
name: 'Standard'
}
properties: {
publicIPAddressVersion: 'IPv4'
publicIPAllocationMethod: 'Static'
idleTimeoutInMinutes: 4
}
}
resource vNetName_bastionSubnet 'Microsoft.Network/virtualNetworks/subnets@2021-08-01' = {
parent: vNet
name: bastionSubnetName
properties: {
addressPrefix: vNetBastionSubnetAddressPrefix
}
dependsOn: [
vNetName_vNetSubnetName
]
}
resource vNetName_vNetSubnetName 'Microsoft.Network/virtualNetworks/subnets@2021-08-01' = {
parent: vNet
name: vNetSubnetName
properties: {
addressPrefix: vNetSubnetAddressPrefix
natGateway: {
id: natGateway.id
}
}
}
resource bastion 'Microsoft.Network/bastionHosts@2021-08-01' = {
name: bastionName
location: location
properties: {
ipConfigurations: [
{
name: 'IpConf'
properties: {
privateIPAllocationMethod: 'Dynamic'
publicIPAddress: {
id: bastionPublicIPAddress.id
}
subnet: {
id: vNetName_bastionSubnet.id
}
}
}
]
}
}
resource bastionPublicIPAddress 'Microsoft.Network/publicIPAddresses@2021-08-01' = {
name: bastionPublicIPAddressName
location: location
sku: {
name: lbSkuName
}
properties: {
publicIPAddressVersion: 'IPv4'
publicIPAllocationMethod: 'Static'
}
}
resource lb 'Microsoft.Network/loadBalancers@2021-08-01' = {
name: lbName
location: location
sku: {
name: lbSkuName
}
properties: {
frontendIPConfigurations: [
{
name: lbFrontEndName
properties: {
publicIPAddress: {
id: lbPublicIPAddress.id
}
}
}
]
backendAddressPools: [
{
name: lbBackendPoolName
}
]
loadBalancingRules: [
{
name: 'myHTTPRule'
properties: {
frontendIPConfiguration: {
id: resourceId('Microsoft.Network/loadBalancers/frontendIPConfigurations', lbName, lbFrontEndName)
}
backendAddressPool: {
id: resourceId('Microsoft.Network/loadBalancers/backendAddressPools', lbName, lbBackendPoolName)
}
frontendPort: 80
backendPort: 80
enableFloatingIP: false
idleTimeoutInMinutes: 15
protocol: 'Tcp'
enableTcpReset: true
loadDistribution: 'Default'
disableOutboundSnat: true
probe: {
id: resourceId('Microsoft.Network/loadBalancers/probes', lbName, lbProbeName)
}
}
}
]
probes: [
{
name: lbProbeName
properties: {
protocol: 'Tcp'
port: 80
intervalInSeconds: 5
numberOfProbes: 2
}
}
]
outboundRules: [
]
}
}
resource lbPublicIPAddress 'Microsoft.Network/publicIPAddresses@2021-08-01' = {
name: lbPublicIpAddressName
location: location
sku: {
name: lbSkuName
}
properties: {
publicIPAddressVersion: 'IPv4'
publicIPAllocationMethod: 'Static'
}
}
resource nsg 'Microsoft.Network/networkSecurityGroups@2021-08-01' = {
name: nsgName
location: location
properties: {
securityRules: [
{
name: 'AllowHTTPInbound'
properties: {
protocol: '*'
sourcePortRange: '*'
destinationPortRange: '80'
sourceAddressPrefix: 'Internet'
destinationAddressPrefix: '*'
access: 'Allow'
priority: 100
direction: 'Inbound'
}
}
]
}
}
resource vNet 'Microsoft.Network/virtualNetworks@2021-08-01' = {
name: vNetName
location: location
properties: {
addressSpace: {
addressPrefixes: [
vNetAddressPrefix
]
}
}
}
output location string = location
output name string = lb.name
output resourceGroupName string = resourceGroup().name
output resourceId string = lb.id
Multiple Azure resources have been defined in the bicep file:
- Microsoft.Network/loadBalancers
- Microsoft.Network/publicIPAddresses: for the load balancer, bastion host, and the NAT gateway.
- Microsoft.Network/bastionHosts
- Microsoft.Network/networkSecurityGroups
- Microsoft.Network/virtualNetworks
- Microsoft.Compute/virtualMachines (3).
- Microsoft.Network/networkInterfaces (3).
- Microsoft.Compute/virtualMachine/extensions (3): use to configure the Internet Information Server (IIS), and the web pages.
- Microsoft.Network/natGateways: for the NAT gateway.
Important
Hourly pricing starts from the moment that Bastion is deployed, regardless of outbound data usage. For more information, see Pricing and SKUs. If you're deploying Bastion as part of a tutorial or test, we recommend that you delete this resource after you finish using it.
To find more Bicep files or ARM templates that are related to Azure Load Balancer, see Azure Quickstart Templates.
Deploy the Bicep file
Save the Bicep file as main.bicep to your local computer.
Deploy the Bicep file using either Azure CLI or Azure PowerShell.
az group create --name exampleRG --location EastUS az deployment group create --resource-group exampleRG --template-file main.bicep
Note
The Bicep file deployment creates three availability zones. Availability zones are supported only in certain regions. Use one of the supported regions. If you aren't sure, enter EastUS.
You're prompted to enter the following values:
- projectName: used for generating resource names.
- adminUsername: virtual machine administrator username.
- adminPassword: virtual machine administrator password.
It takes about 10 minutes to deploy the Bicep file.
Review deployed resources
Sign in to the Azure portal.
Select Resource groups from the left pane.
Select the resource group that you created in the previous section. The default resource group name is exampleRG.
Select the load balancer. Its default name is the project name with -lb appended.
Copy only the IP address part of the public IP address, and then paste it into the address bar of your browser.
The browser displays the default page of the Internet Information Services (IIS) web server.
To see the load balancer distribute traffic across all three VMs, you can force a refresh of your web browser from the client machine.
Clean up resources
When you no longer need them, delete the:
- Resource group
- Load balancer
- Related resources
Go to the Azure portal, select the resource group that contains the load balancer, and then select Delete resource group.
Next steps
In this quickstart, you:
- Created a virtual network for the load balancer and virtual machines.
- Created an Azure Bastion host for management.
- Created a standard load balancer and attached VMs to it.
- Configured the load-balancer traffic rule, and the health probe.
- Tested the load balancer.
To learn more, continue to the tutorials for Azure Load Balancer.