Creating Azure Custom Linux VM Image

The Packer configuration file (left) and the cloud-init file (right)

Introduction

I previously did an exercise in which I deployed my Clojure Simple Server to Azure managed Kubernetes Service — AKS, you can read about that story in my previous blog posts: “Creating Azure Kubernetes Service (AKS) the Right Way” and “Deploying Kubernetes Configuration to Azure AKS”.

Packer

I’m using Packer which is quite widely used tool to create custom VM images. I have used Packer previously in the AWS side (see my previous article “How to Create EC2 Images in AWS?”) and now it was interesting to see what it is like to use Packer also in the Azure side. Packer worked smoothly also in the Azure side — I’ll describe how I used Packer in the following chapters in more detail.

Create a Service Principal

You need a service principal that Packer is going to use. You can easily create the service principal e.g. using Azure command line interface:

az ad sp create-for-rbac --query "{ client_id: appId, client_secret: password, tenant_id: tenant }"

Create the Packer Configuration File

I mainly followed Microsoft’s own documentation how to use Packer — “How to use Packer to create Linux virtual machine images in Azure”. I created a Packer configuration file “ss-azure-vm-template.json” (see also the screenshot in the beginning of this article) in which I first define that my base image will be UbuntuServer 18 (which supports cloud-init that I’m going to use later). Then I install a couple of software packages that the server needs (OpenJDK) and I may need during debugging purposes (Emacs, my favorite editor).

Create Cloud-Init Configuration File

Ok. Now we have the new Azure custom Linux VM image. We have baked into the image the application untarred in a directory and also OpenJDK that the application uses as a runtime. Next we should provision the user for running the application (do not run anything as root), and some mechanism to start the application automatically on boot, and also inject in the parameters (e.g. which mode the application is running: single-node test mode or the real thing azure-table-storage mode + the storage account connection string to the Azure table storage which hosts the Tables that the application uses — in a real production system you should store all credentials to Azure Key Vault, of course).

az vm create --resource-group YOUR-RESOURCE-GROUP --name YOUR-VIRTUAL-MACHINE-NAME --image YOUR-VM-IMAGE-NAME --custom-data ../packer/cloud-init-set-env-mode-single-node.sh --ssh-key-value @vm_id_rsa.pub --vnet-name YOUR-VNET --subnet YOUR-SUBNET --admin-username YOUR-USER-NAME --location YOUR-LOCATION
ps aux | grep java
ssuser .... <some stuff> .... java -jar app.jar
ubuntu .... <some stuff> .... grep --color=auto java
sudo systemctl status rc-local
● rc-local.service - /etc/rc.local Compatibility
Loaded: loaded (/lib/systemd/system/rc-local.service; enabled-runtime; vendor preset: enabled)
...
Active: activating (start) since Mon 2019-01-28 19:43:28 UTC; 1min 28s ago
...
Jan 28 19:43:54 inittest5-vm rc.local[1017]: 2019-01-28 19:43:54 DE [main] DEBUG simpleserver.webs
Jan 28 19:43:54 inittest5-vm rc.local[1017]: 2019-01-28 19:43:54 DE [main] DEBUG simpleserver.webs
Jan 28 19:43:54 inittest5-vm rc.local[1017]: 2019-01-28 19:43:54 DE [main] DEBUG simpleserver.webs
Jan 28 19:43:55 inittest5-vm rc.local[1017]: Started server on port 3045
./call-all-ip-port.sh PUBLIC-IP 3045
... a lot of stuff, and finally last API call and return value:
{"ret":"ok","pg-id":"2","p-id":"49","product":["49","2","Once Upon a Time in the West","14.4","Leone, Sergio","1968","Italy-USA","Western"]}

Conclusions

Creating a custom VM image using Packer is pretty much the same in both AWS and Azure. Comparing building Docker container image and VM image the main thing is that building a VM image takes a really long time — you have to test all your provisioning and startup scripts in a live VM before you use them in the image building script.

I’m a Software architect and developer. Currently implementing systems on AWS / GCP / Azure / Docker / Kubernetes using Java, Python, Go and Clojure.