Tuesday, October 17, 2023

Launching FreeBSD VMs in Azure with Bicep Templates

I have followed Colin Percival's excellent work on getting FreeBSD images supported on the public cloud provider infrastructure since 2010. I spent some time this weekend looking at the latest images and writing an Azure Bicep template to launch a FreeBSD VM. Microsoft now has an article about launching FreeBSD VM images with their relatively excellent free azure documentation and training. That doc makes a great starting point, but it doesn't show how to find the latest FreeBSD images or use Bicep templates. As of this writing, FreeBSD 13.2 is the latest image in the Azure MarketPlace, and VMs can be launched in Azure at least half a dozen different ways:

  • az command-line tool
  • Start-AzVM PowerShell
  • Bicep template engine
  • ARM Templates
  • Terraform
  • Pulumi
  • etc..

I'll show the az command-line needed to quickly launch a VM, but will focus mostly on Bicep templates, as this appears to be the current Microsoft-recommended Azure-specific template engine for automating Infrastructure as Code deployments on Azure. Before experimenting with this you need:

  • An active Azure subscription.
  • A user with owner/contributor roles.
  • A resource group.
These can all easily be created at the Azure Portal if this is your first time working with Azure.

Command Line VM Creation:

The easiest method to create a new VM is simply to use the az command. I created a resource group called "FreeBSD" and then looked for the latest FreeBSD image names:

To find a list of publishers of FreeBSD images you can use something like:

# az vm image list-publishers --location westus --output table|grep thefreebsdfoundation
westus      thefreebsdfoundation

To find "offers" and "skus" from this publisher:

# az vm image list-offers --location westus --publisher thefreebsdfoundation --output table
#westus      freebsd-13_2
# az vm image list-skus --location westus --publisher thefreebsdfoundation --offer freebsd-13_2 --output table
#westus      13_2-release
#az vm image list \
#    --location westus \
#    --publisher thefreebsdfoundation \
#    --offer freebsd-13_2 \
#    --sku 13_2-release \
#    --all --output table
#x64             freebsd-13_2  thefreebsdfoundation  13_2-release  thefreebsdfoundation:freebsd-13_2:13_2-release:13.2.0  13.2.0

Once we have identified a specific FreeBSD image string, you can create a VM instance with:

az vm create --name MyFreeBSD13 --resource-group FreeBSD --image thefreebsdfoundation:freebsd-13_2:13_2-release:13.2.0 --admin-username murray --generate-ssh-keys
ResourceGroup    PowerState    PublicIpAddress    Fqdns    PrivateIpAddress    MacAddress         Location    Zones
---------------  ------------  -----------------  -------  ------------------  -----------------  ----------  -------
FreeBSD          VM running    172.191.154.200             10.0.0.4            00-0D-3A-8B-22-FD  eastus

Behind the scenes, Azure is creating a number of resources on my behalf in addition to the VM instance:

  • A virtual network (VNET)
  • A virtual network interface (VNIC)
  • A public IPv4 address
  • A network security group (stateful level 4 firewall rules)
  • A persistent disk for the OS.
  • The virtual machine

All of these are created in my specified "FreeBSD" resource group, and I can ssh directly into the new VM, start installing packages or configuring it with Ansible, and otherwise prepare it for my use case. However, using a template engine can allow us to provide more configuration details about the VM to control costs and control how the resources are deployed.

Windows Azure Linux Agent

One thing to notice about the VM is that there are some Python 3.9 processes running by default. These are for the Windows Azure Linux Agent running from /usr/local/sbin/waagent and is outside the scope of this post.

Bicep Automation

The recommended way to author and deploy Bicep configurations is with Visual Studio Code and the Bicep extension. This extension allows you to visualize the resource graph that will be created by the config, handle autocompletion of long Azure resource names, and lets you deploy a configuration.

@description('Location for all resources.')
param location string = resourceGroup().location

@description('Prefix to use for VM names')
param vmNamePrefix string = 'BackendVM'

@description('Size of the virtual machines')
param vmSize string = 'Standard_D2s_v3'

// Unique DNS Name for the Public IP used to access the Virtual Machine.
@description('DNS Label Prefix for Public IP used by Azure to access the VM')
param dnsLabelPrefix string = toLower('FreeBSD-${uniqueString(resourceGroup().id)}')

var publicIPAddressName = 'FreeBSDPublicIP'
var virtualNetworkName = 'FreeBSDVnet'
var subnet1Name = 'FreeBSD-subnet-1'
var subnetRef1 = resourceId('Microsoft.Network/virtualNetworks/subnets', virtualNetworkName, subnet1Name)
var networkInterfaceName = 'nic'

//var numberOfInstances = 2

resource virtualNetwork 'Microsoft.Network/virtualNetworks@2021-05-01' = {
  name: virtualNetworkName
  location: location
  properties: {
    addressSpace: {
      addressPrefixes: [
        '10.0.0.0/16'
      ]
    }
    subnets: [
      {
        name: subnet1Name
        properties: {
          addressPrefix: '10.0.0.0/24'
        }
      }
    ]
  }

  resource subnet1 'subnets' existing = {
    name: subnet1Name
  }

}

output subnet1ResourceId string = virtualNetwork::subnet1.id

// public IP Address
resource publicIP 'Microsoft.Network/publicIPAddresses@2020-06-01' = {
  name: publicIPAddressName
  location: location
  properties: {
    publicIPAllocationMethod: 'Dynamic'
    publicIPAddressVersion: 'IPv4'
    dnsSettings: {
      domainNameLabel: dnsLabelPrefix
    }
    idleTimeoutInMinutes: 4
  }
  sku: {
    name: 'Basic'
  }
}

resource networkInterface1 'Microsoft.Network/networkInterfaces@2021-05-01' = {
  name: '${networkInterfaceName}1'
  location: location
  properties: {
    ipConfigurations: [
      {
        name: 'ipconfig1'
        properties: {
          privateIPAllocationMethod: 'Dynamic'
          subnet: {
            id: subnetRef1
          }
          publicIPAddress: {
            id: publicIP.id
          }
        }
      }
    ]
  }
  dependsOn: [
    virtualNetwork
  ]
}

resource vm1 'Microsoft.Compute/virtualMachines@2021-11-01' = {
  name: '${vmNamePrefix}1'
  location: location
  plan: {
    name: '13_2-release'
    product: 'freebsd-13_2'
    publisher: 'thefreebsdfoundation'
  }
  properties: {
    hardwareProfile: {
      vmSize: vmSize
    }
    osProfile: {
      computerName: '${vmNamePrefix}1'
      adminUsername: 'murray'
      adminPassword: 'XXXXXXX' // Use SSH keys instead, for testing with hard-coded password need 6+ chars, one lower, one upper, one special character
    }
    storageProfile: {
      imageReference: {
        publisher: 'thefreebsdfoundation'
        offer: 'freebsd-13_2'
        sku: '13_2-release'
        version: '13.2.0'
      }
      osDisk: {
        createOption: 'FromImage'
      }
    }
    networkProfile: {
      networkInterfaces: [
        {
          id: networkInterface1.id
        }
      ]
    }
//    diagnosticsProfile: {
//      bootDiagnostics: {
//        enabled: true
//        storageUri: storageAccount.properties.primaryEndpoints.blob
//      }
//    }
  }
}

Thats it! The Azure docs cover myriad configuration parameters to customize all aspects of the network and VM deployment, but this basic template should get you started with FreeBSD in Azure.