How to create an immutable Azure VM image for an application using packer and ansible

In this post I will discuss about how build immutable VM images using packer and ansible with latest application code baked in. In my subsequent post I discuss how we can combine this with Visual studio team services to create a continuous deployment pipeline to provision new Azure VM scaleset / or update existing VM scaleset based on the VM image created using packer.

In the diagram below I have highlighted areas which this particular post will focus on.

Azure VM image build with latest application code using packer Packer is a tool from HashiCorp for creating machine and container images for various platforms. In a typical flow each time application code changes (or at desired interval), a packer build is triggered which would create a new VM Image (with the latest application code baked in) in the configured Azure storage (when using the packer azurerm builder). This image can then be used by deployment pipelines to refresh the desired environments with the latest application code. If you wanted to revert to a previous version of the application in an environment you could rollback by creating VMs based on the previously created VM Image.

The beauty with packer is that that it supports a number of builders including azurerm, docker, virtualbox etc, and a number of provisioners including chef, puppet, ansible etc. So as an example you can create a VM image, a docker container image and a virtual box image (for a developers VM) which are almost similar in terms of infrastructure. Packer also gives us complete environment change tracebility as this is truly infrastructure as code.

For packer installation see Packer installation instructions.

Let us see how easy it is to create the VM image for a sample php application. The code repository is on my github repo.
I have considered a simple single file (index.php) php application . The packer file phpapp-packer.json creates the VM image, and this packer file uses the ansible file phpapp-packer-ansible-provisioner.yml for provisioning the VM.

 

Let us first take a look at some of the important lines from phpapp-packer.json "variables": { "tenant_id": "{{env `ARM_TENANT_ID`}}", "client_id": "{{env `ARM_CLIENT_ID`}}", "client_secret": "{{env `ARM_CLIENT_SECRET`}}", "resource_group": "{{env `ARM_RESOURCE_GROUP`}}", "storage_account": "{{env `ARM_STORAGE_ACCOUNT`}}", "subscription_id": "{{env `ARM_SUBSCRIPTION_ID`}}" }In above lines the packer file variables are set from corresponding environment variable. The link /en-us/azure/virtual-machines/linux/build-image-with-packer gives details on how to get values for these variables to enable packer builds in azure.

23 "os_type": "Linux", 24 "image_publisher": "Canonical", 25 "image_offer": "UbuntuServer", 26 "image_sku": "16.04.0-LTS",Above lines specify the base image sku, which is Ubuntu 16.04.

32 "provisioners": [ 34 { 35 "execute_command": "chmod +x {{ .Path }}; {{ .Vars }} sudo -E sh '{{ .Path }}'", 36 "inline": [ 37 "apt-get update", 38 "apt-get upgrade -y", 39 "apt-get install ansible -y" 40

41 ],
42 "inline_shebang": "/bin/sh -x",
43 "type": "shell"
44 },
45 {
46 "type": "ansible-local",
47 "playbook_file": "./phpapp-packer-ansible-provisioner.yml"
48 }

In the provisioners array, we first have a shell script provisioner where we are installing ansible, and then we have an ansible-local provisioner, to which we pass our ansible playbook to install our PHP application.

Next let us look some of the key lines from the phpapp-packer-ansible-provisioner.yml ansible playbook, which ultimately provisions the PHP application
4 tasks: 5 - name: Install Packages 6 apt: name={{ item }} update_cache=yes state=present 7 with_items: 8 - apache2 9 - libapache2-mod-php 10 - git 11 - curl In the above lines we install the dependencies which we need for setting up our php application

14 - name: Git Clone Repo 15 git: repo=https://github.com/maniSbindra/spinnaker-packer-ansible-php-azure-demo.git dest=/opt/phpapp update=yes force=yes accept_hostkey=yes 16 register: git_finished In the above lines we clone the git repository, where the application is hosted

18 - name: copy index.php 19 sudo: yes 20 copy: src=/opt/phpapp/index.php dest=/var/www/html/index.php mode=644 21 notify: Restart Apache In the above lines we copy the index.php files to the apache default directory, set permissions and restart apache.

 

Typically the packer build which generates the VM image would be triggered automatically, however here lets see the result of running the packer build manually.

environment variables set and packer build run The setupenv.sh file sets the environment variables required by the packer file. After this the packer build command is fired.

packer build command finished packer build command finished

. After the build command finishes get the path of the vhd, and we also get the uri of the ARM template (TemplateUriReadOnlySas) which can used to provision VM based on the created VM image.

In the next post we look at using Visual Studio Team Services to automate creation of VM scalesets based on the VM image generated by packer.