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.
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.
LikeLiked by 1 person
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.
LikeLike
I can seem to find a working way add this to AzAvailabilitySet, any ideas maybe I am missing something easy but it errors out
LikeLike
I’ve never actually tried, what error do you get?
LikeLike
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.
LikeLike
New-AzureRmVM -ResourceGroupName $rg -AvailabilitySetName $avail -Location $zone -VM $vm that should built it with the aviliabitity zone
LikeLike
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.
LikeLike
Did you put a space instead of underscore? Module should hb hv_storvsc and hv_vmbus
LikeLiked by 1 person
That was it. I feel soooo dumb… 🙂
Thanks for the help!
LikeLike