Running Arch Linux on Microsoft Azure in 2019

So, you love Arch Linux? It’s hugely configurable, well documented, comes with a large community and it’s not remotely bloated. It makes sense that you want to share the same platform across your desktop and servers in the cloud.
But, despite being one of the largest distributions, Arch isn’t supported out-the-box in a lot of cloud environments. There are community-contributed images for Amazon’s EC2 and a build-your-own image tool for Google’s Cloud Platform. But, there’s nothing easy if you want to use Microsoft Azure with Arch.

Now, I know a lot of you don’t like Microsoft – and that’s fine. But there are plenty of people who love Arch and have chosen to use Azure. For me – one of the reasons is Microsoft’s green energy mix compared to other providers. I’ve also worked with clients where corporate policy mandated it.

Since there’s no out-the-box option on Azure for an Arch Linux image we need to create our own. And that’s what we’re going to do today.

We’re basically going to create our own Arch installation inside a virtual machine, install some Azure tools, convert the disk image to the appropriate format, upload it and then use it to create virtual machines.

I’m going to work within an Arch Linux host to create the image. You could follow similar steps in another environment – virtual box, VMware, whatever, but I’m going to guess you’ve already got Arch somewhere if you’re going as far as putting it in the cloud.

We start by creating a folder for our work and moving into it.

mkdir azure-vm-template
cd azure-vm-template

Now we’re going to use QEMU to create an Arch Linux virtual running on our own PC. We’ll create an empty 30G disk image. This will be the size of your root filesystem will the size of the primary partition of VMs that you spin up in Azure using this template. You can create larger or smaller but 30GB should be more than enough.

qemu-img create -f raw azure-vm-template.raw 30G

Our second QEMU command will actually start an Arch Linux installer. Unlike our previous Arch Linux install video we’re using BIOS rather than EFI. Now, that’s important because EFI and GPT are not options for our Azure VMs at this time – plus, it’s a lot easier. Change the CD-ROM path for wherever your ISO is stored and away we go. We’re going to give it 2GB of RAM and 4 CPUs here – this is just for the install. What Azure eventually uses will be different for each VM that you start.

qemu-system-x86_64 -enable-kvm -cdrom ~/Downloads/archlinux-2018.11.01-x86_64.iso -boot order=d -drive file=azure-vm-template.raw,format=raw -m 2G -smp 4

Note: This assumes you are running in a graphical interface like X or Wayland.  If you’re running solely from the terminal you’ll need to see this comment from one of my readers.

The Arch Linux installer starts up and boots to a command prompt. The install then follows much like we did for our workstation build – but with far fewer components.

We set the keymap to whatever we want for our server and enable NTP before moving on to configure the disks. Check the name of our disks with lsblk and we see our single disk sda.

loadkeys uk
timedateectl set-ntp true
lsblk

This time we’re using the master boot record rather than GPT so we run fdisk on /dev/sda. A few keystrokes later will create a new primary partition 1 that fills the entire disk and sets it active. We then exit back out to the command prompt.

fdisk /dev/sda

Then enter these commands

n
p
1 

a
w

We’re now ready to format the disk as ext4, mount it and use pacstrap to install the base of Arch onto our disk. This will take while – so let’s just ahead.

mkfs.ext4 /dev/sda1
mount /dev/sda1 /mnt
pacstrap /mnt base

Once the core is installed we can generate our fstab and chroot into our freshly installed partition.

genfstab -U /mnt >> /mnt/etc/fstab
arch-chroot /mnt

Next we’ll configure our timezone. Now this might be an important one to actually consider. If this is for organisational deployments you may need to change this post-deployment if you have a policy of using a timezone based on the server’s physical location. Personally, I always prefer servers to report GMT wherever they are so let’s use the UTC timezone and then perform a clock sync.

ln -sf /usr/share/zoneinfo/UTC /etc/localtime
hwclock --systohc

Next up it’s locales by editing /etc/locale.gen. Being based in the UK I’m going to go with en_GB but I also enable en_US.

nano -w /etc/locale.gen

Then uncomment the appropriate locales for you (i.e. LANG=en_GB.UTF-8 or LANG=en_US.UTF-8).

If you chose a non-US keymap we’ll want to make that permanent by editing the vconsole file.

locale-gen
nano -w /etc/vconsole.conf

And enter this text replacing UK for your keymap.

KEYMAP=uk

Some really important stuff coming up next. We need to make sure that DHCP is enabled. If it’s not your machine will never get an IP from Azure and won’t be able to boot-up. So let’s make absolutely sure we enable DHCP before we go on to give the machine a temporary hostname and set up the /etc/hosts file accordingly.

systemctl enable dhcpcd.service
echo AzureVmTemplate > /etc/hostname
nano - w /etc/hosts

And enter the following:

127.0.0.1 localhost
::1 localhost
127.0.1.1 AzureVmTemplate AzureVmTemplate.localdomain

We’ll now setup a root password. You’ll certainly need this for the install and you’re probably going to want to keep it set as a fallback. With Azure you’ve got options to run a post-install script that could be used to configure passwords once a VM is initialised – but for my purposes I’m happy with a couple of initially baked in users.

passwd

We’re now good to install Grub. This is another part where we differ from my previous Arch install video. Since we’re not using EFI there’s no doubt Grub is probably the easiest thing to do here so we install grub, edit the configuration for it and run the grub-mkconfig command.

pacman -S grub
grub-install --target=i386-pc /dev/sdX
nano -w /etc/default/grub
grub-mkconfig -o /boot/grub/grub.cfg

That’s it and we’ve got our basic image ready.

Press CTRL+D to drop out of our chroot and then let’s shutdown the QEMU VM.

poweroff

We now want to restart the QEMU machine. This time we exclude the CD image so that we boot into our new install. Once we’re booted we can login with our root password.

qemu-system-x86_64 -enable-kvm  -drive file=azure-vm-template.raw,format=raw -m 2G -smp 4

Here’s where you need to decide what it is that you want to install by default in your Azure VMs. SSH is a must for administration and you probably want sudo but beyond that it’s up to you. I install some basic admin tools I always like to have as well as a development stack. Whatever you want install it now with pacman.

pacman -S git openssh wget whois sudo dnsutils base-devel htop

Assuming you installed sudo let’s edit that now and set it up. On my desktop I don’t require a password but I always do on production servers – so edit the sudo config appropriately.

nano -w /etc/sudoers

Uncomment this part near the bottom by removing the # prefix:

%wheel ALL=(ALL) ALL

Now we can enable any services we just installed. For me that’s just going to be SSH.

systemctl enable sshd

Some more important Azure-specific bits next. We need to tell Arch to include some kernel modules that support Azure. Azure sits on the Microsoft Hyper-V hypervisor so we need to enable the appropriate modules in /etc/mkinitcpio.conf. Once we’ve edited the config we can then regenerate our image using mkinitcpio. It’s really important you don’t miss this. If you miss this step your VM won’t boot in Azure. That’s a lot of wasted uploading a broken VM image. Make sure you both edit the mkinitcpio.conf file and then actually call mkinitcpio.

nano -w /etc/mkinitcpio.conf

Change the modules line near the top to be as follows.

MODULES=(hv_storvsc hv_vmbus)
mkinitcpio -p linux

With that done we’re ready to add our non-root user. I setup an account for myself, create the home directory, set a password and add myself to the wheel group to allow me to use sudo. You could install SSH authorized keys now as well for users if that’s what you want to do.

groupadd -g 150 guytp
useradd -g 150 -u 150 guytp
mkdir /home/guytp
chown guytp.guytp /home/guytp
passwd guytp
usermod -aG wheel guytp

We’re going to need a non-root account for the next step so let’s login using the account I just created.

su - guytp

The final important step is to install Microsoft’s Azure Linux agent. This is a service that deals with all Azure-specific aspects of the machine – from provisioning a secondary temporary storage disk, managing swapfiles on this temporary disk and dealing with one-time and per-boot settings you may want to apply.

We install this like any other AUR package – we create a folder for it, clone it, move into that folder and run the makepkg command. As always I tend to use -sirc as parameters which will ensure a clean state, install it and remove any unused dependencies once it’s done.

mkdir aur
cd aur
git clone https://aur.archlinux.org/walinuxagent.git
cd walinuxagent
makepkg -sirc

After a while, and a couple of password inputs, it’s installed so we can use CTRL+D to logout of the user account and back to root.

We now need to enable the waagent service. Again – don’t miss this one or your image isn’t going to work properly. Once you’ve enabled it you can edit the /etc/waagent.conf file and make any changes for your needs. Personally, all I do is enable an 8GB swap file to be created on the temporary storage disk.

systemctl enable waagent

Change this part about half way down:

ResourceDisk.EnableSwap=y
ResourceDisk.SwapSizeMB=8192

Speaking of temporary storage, it’s going to be mounted into /mnt/resource so let’s create the mountpoint for it now.

mkdir  /mnt/resource

Finally, I’m going to remove my bash histories and the temporary AUR folder before telling waagent to prepare the current machine for being used as a template (with the deprovision flag). Then we can shut it down.

rm -Rf ~guytp/.bash_history ~guytp/aur ~/.bash_history
waagent -force -deprovision
export HISTSIZE=0
shutdown -h now

And that’s it – we have the image. All we have to do now is get it in the correct format. We need to convert it from RAW to VHD format for our upload. There’s also a bunch of specific requirements here that require disks to be rounded to very specific boundaries for Azure.

Thankfully there’s a script for that. So let’s type it into a new script file.

nano -w resize.sh

Then enter the script below.

# Prints the size of raw and vhd disk
rawdisk="azure-vm-template.raw"
vhddisk="azure-vm-template.vhd"

echo "RAW Info:"
MB=$((1024*1024))
size=$(qemu-img info -f raw --output json "$rawdisk" | \
	   gawk 'match($0, /"virtual-size": ([0-9]+),/, val) {print val[1]}')

echo "Current Size = $size"
rounded_size=$((($size/$MB + 1)*$MB))
echo "Rounded Size = $rounded_size"

echo "VHD Info:"
size=$(qemu-img info -f raw --output json "$vhddisk" | \
	   gawk 'match($0, /"virtual-size": ([0-9]+),/, val) {print val[1]}')

echo "Current Size = $size"
rounded_size=$((($size/$MB + 1)*$MB))
echo "Rounded Size = $rounded_size"

Once you’ve saved that we can make it executable and run it.

chmod +x resize.sh
./resize.sh

The script outputs a current size and a rounded size. We call qemu-img and resize our raw disk to the rounded size that was shown when we called the script. We can then convert from raw format to Microsoft’s VHD format. In the example below 32213303296 is the rounded output from the last step.

qemu-img resize -f raw azure-vm-template.raw 32213303296
qemu-img convert -f raw -o subformat=fixed,force_size -O vpc azure-vm-template.raw azure-vm-template.vhd

Once that’s done we call resize.sh again. We need to make sure that the two values for rounded_size shown – for both raw and VHD disks – match perfectly. If they don’t we’re going to have issues at upload.

./resize.sh

And we’re ready – we can now upload our file.

The easiest two ways to do this are either using a web browser or via PowerShell. For this example, I’m going to use PowerShell on a Windows machine. If you haven’t got a Windows machine you could use the web interface if your connection is good enough or try Powershell for Linux. I’m going to assume you’ve already got a resource group and storage container setup in Azure – if not go and create those now.

It’s only two lines of PowerShell that we need. First, we login. Then, using the Add-AzureRmVhd command, we specify the resource group that contains our storage container, the destination URL that we want upload to and our local file.

Connect-AzureRmAccount
Add-AzureRmVhd -ResourceGroupName RG-EasternUs -Destination https://StorageAccountName.blob.core.windows.net/os-images/azure-vm-template.vhd -LocalFilePath "C:\Users\guytp\azure-vm-template.vhd"

This takes a while to upload so go and grab a drink.

If you don’t want to use PowerShell and don’t want to use the browser you’ve also got the option of the Azure CLI tools.  I’ve not used these myself so the steps are untested but this should work (famous last words). You’ll already need the Azure CLI tools installed. You can skip the next couple of paragraphs if you’ve used PowerShell or web upload.

First you’ll have to login. You can either do this with a username and password (if 2FA is not enabled and replacing the e-mail address and password as per your account):

az login -u azure@email.address -p AzurePassword


or a service principal if configured in the Azure portal (again replacing principal and password as per your account).


az login --service-principal -u http://azure-cli-2016-08-05-14-31-15 -p VerySecret --tenant contoso.onmicrosoft.com


Then you can do the upload (assuming the azure-vm-template.vhd is in the current folder):


az storage blob upload --container-name StorageAccountName --file azure-vm-template.vhd --name os-images/azure-vm-template.vhd

Once it's uploaded we can create our Azure VM from the template we just uploaded. For that there's no GUI so we're going to have to use PowerShell. An alternative to Windows here is to use the PowerShell CloudShell within the Azure portal.

The script provided in the linked blog post will provision a VM with a single NIC that uses the first subnet in the specified network. The user-configurable settings are at the top and allow you to enter the name of the resource group that you wish to place this VM in, the name of the VM, which Azure zone it is to be located in, the name of the virtual network, the type of VM and the location of the VHD image that you just uploaded.

You need to make sure you've already created the resource group and virtual network. I'm going to assume you're comfortable doing that - and you if not it's pretty easy to do so through the web portal.

Unless you want to change the naming structure of components like disks and NICs or to use a different subnet then you shouldn't need to change any of the script. The only exception is if you created a root disk some size other than 30 gigabytes in which case change the DiskSizeInGB on the penultimate line of the script.

Please note this script does not create a public IP - only a private one on your vnet.

$rg = "rgname"
$vmName = "VirtualMachineName"
$zone = "eastus"
$vnName = "VnetName"
$size = "Standard_B1ms"
$uploadUrl = "https://StorageAccountName.blob.core.windows.net/os-images/azure-vm-template.vhd"

$disk = New-AzureRmDisk -DiskName ($vmName + '-OS') -Disk(New-AzureRmDiskConfig -AccountType Standard_LRS -Location $zone -CreateOption Import -SourceUri $uploadUrl) -ResourceGroupName $rg
$vnet = Get-AzureRmVirtualNetwork -Name $vnName -ResourceGroupName $rg
$nic = New-AzureRmNetworkInterface -Name ($vmName + '-NIC-01') -ResourceGroupName $rg -Location $zone -SubnetId $vnet.Subnets[0].Id
$vmconfig = New-AzureRmVMConfig -VMName $vmName -VMSize $size
$vm = Add-AzureRmVMNetworkInterface -VM $vmConfig -Id $nic.Id
$vm = Set-AzureRmVMOSDisk -VM $vm -ManagedDiskId $disk.Id -StorageAccountType Standard_LRS -DiskSizeInGB 30 -CreateOption Attach -Linux
New-AzureRmVM -ResourceGroupName $rg -Location $zone -VM $vm

Run the script, wait for confirmation and in a few minutes your VM will be live. I've found a first boot can take 5-10 minutes to provision so don't get scared if the machine takes a while to be accessible. You can go into the Azure portal, see the IP of the newly provisioned machine and SSH in.

If all's good then you've now got your first Arch Linux VM running in Azure.

That was quite a few steps to go through but it's then as easy as running a couple of lines of PowerShell whenever you want to provision a new virtual machine.

As new Arch Linux packages become available you can refresh your virtual image and re-create and re-upload the VHD or just run pacman -Syu after a VM's first boot and take the time hit there instead.

If you're doing more than just having a little fun then you'll want to look at custom first-boot tasks to further configure the individual VM to your needs - rather than baking in specific details like SSH keys or passwords.

I hope you found this video useful. All I could find was really outdated tutorials that didn't show the current Azure setup terribly well. The Microsoft documentation is pretty poor in this area as well.

This end-to-end solution will hopefully help anybody else looking at the same scenario that I was in. And even if you don't like Microsoft you can still get a free Arch VM for a year with this method if you sign-up for Azure and $300 in first-month credits to play around with.


9 thoughts on “Running Arch Linux on Microsoft Azure in 2019

  1. Please note when you are running the qemu commands please add the argument `-nographic` otherwise the command will try to run a VNC server instead of running things on tty. Also, when you us the command to start from the CDROM disk image, you need to press tab and specify the boot argument `console=ttyS0`. When the system is installed, this same argument needs to be passed in the default grub argument in `/etc/default/grub` and the grub config file regenerated.

    Liked by 1 person

    1. I’ve assumed that you’re running in X when creating template so haven’t put this in, but will refer to this comment for anyone else who may be wanting to run this in a tty or without VNC window.

      Like

      1. when i use either update-azavailablitityset or new, it errors out with command not known. I will try it again today and see what the error. Yeah it should just work.

        Like

      2. New-AzureRmVM -ResourceGroupName $rg -AvailabilitySetName $avail -Location $zone -VM $vm that should built it with the aviliabitity zone

        Like

  2. Hi, thanks for the detailed explanation.

    I’m stuck at the mkinitcpio step, though, and would appreciate your help. When I run the command, after having enabled the modules hv, storvsc, and vmbus. I get this:

    ==> ERROR: module not found: `hv’
    ==> ERROR: module not found: `storvsc’
    ==> ERROR: module not found: `vmbus’

    It feels as if some package with these kernel modules is missing. What am I missing?

    Thanks in advance for any help.

    Like

Leave a comment