Published on

Creating VM Images with Packer in Microsoft Azure

blog1

If you’ve ever found yourself in the situation of needing to create and maintain custom machine images, you probably already know there are a number of different ways to go about it. Furthermore, once created, these images can be deployed in containers and customized, which can be unwieldy work.

In this post, I’ll dive into some very specific reasons for why I like to use Packer for this task, and we’ll also take a look at automating the creation of machine images with BitBucket Pipeline

Packer is an open source tool by HashiCorp for creating machine images, allowing you to ensure all machine images meet a baseline standard as well as speeding up provisioning time for application instances.

Why Use Packer?

  • Packer makes it quick and easy to automate the creation and customization of machine images.
  • Packer installs and configures software at build time. It checks for bugs in installation scripts at the time the image is built which reduces provisioning time of virtual machines, improves stability and testability.
  • Allows us to use a single configuration file to create custom images for multiple platforms.
  • Embraces modern configuration management.
  • Encourages use of automated scripts to install and configure the software within your Packer-made images.

Installing Packer

Packer is contained in a single binary packer. To create an image with Packer, download and install Packer in one of the following ways:

  • Download Packer binary for macOs, Linux, or Windows
  • Install using Homebrew by executing brew install packer
  • Install using apt-get by executing apt-get install packer
blog2

Things to Know

Template:

Templates are the configuration files for Packer Images written in JSON format. Thepacker build command runs the builds defined in the template, creating the custom images.

template.json
{
    "builders": [
        {
            "type": "azure-arm",
            "subscription_id": "YOUR_SUBSCRIPTION_ID",
            "client_id": "{{user `client_id`}}",
            "client_secret": "{{user `client_secret`}}",
            "tenant_id": "{{user `tenant_id`}}",
            "managed_image_resource_group_name": "myResourceGroup",
            "managed_image_name": "customLinux-{{isotime \"2006-01-02-24\"}}",
            "os_type": "Linux",
            "image_publisher": "Canonical",
            "image_offer": "UbuntuServer",
            "image_sku": "16.04-LTS",
            "azure_tags": {
                "dept": "DevOps",
                "task": "Image deployment"
            },
            "location": "East US",
            "vm_size": "Standard_DS2_v2"
        }
    ],
    "provisioners": [
        {
            "type": "shell",
            "inline": [
                "sudo apt-get -y update"
            ]
        }
    ]
}

Builders:

builders is an array of objects that Packer uses to generate machine images. Builders create temporary Azure resources as Packer builds the source VM based on the template. Learn more about Builders from the Packer documentation, here.

Provisioners:

Provisioners can be used to pre-install and configure software within the running VM prior to turning it into a machine image. There can be multiple provisioners in a Packer template.

For example, in the below code snippet there are three types of provisioners: shell, file, and powershell.

provisioners.json
"provisioners": [{
        "type": "shell",
        "inline": [
            "sudo apt-get update"
            "mkdir ~/app"
        ]
    },
    {
        "type": "shell",
        "scripts": [
            "tasks/packages.sh"
        ]
    },
    {
        "type": "file",
        "destination": "~/Users/home/app1/",
        "source": "downloads/app1"
    },
    {
        "type": "powershell",
        "execute_command": "powershell -executionpolicy bypass \"& { if (Test-Path variable:global:ProgressPreference){\\$ProgressPreference='SilentlyContinue'};. {{.Vars}}; &'{{.Path}}'; exit \\$LastExitCode }\"",
        "inline": [
            "Add-WindowsFeature Web-Server",
            "& $env:SystemRoot\\System32\\Sysprep\\Sysprep.exe /oobe /generalize /quiet /quit",
            "while($true) { $imageState = Get-ItemProperty HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Setup\\State | Select ImageState; if($imageState.ImageState -ne 'IMAGE_STATE_GENERALIZE_RESEAL_TO_OOBE') { Write-Output $imageState.ImageState; Start-Sleep -s 10  } else { break } }"
        ]
    }
]

Packer has many provisioners. Each provisioner has it’s own configuration options. Check out this Packer Documentation section to learn more about each provisioner and its use case.

Communicator:

Communicators allow packer template to communicate with the remote host to build images. Consider them as the “transport” layer for Packer builders to execute scripts, upload files etc. Packer currently support three communicators :

  • None
  • SSH
  • WinRM

Configuration options for each communicator can be found here

Variables:

Variables block contains any user provided variables that packer uses to create image. You can parameterize the packer template by configuring variables from the command line, storing them in a separate JSON file, or environment variables.

Packer gives some guidance how configuration templates work. For example, calling these three variables in builder sections as:

variable.json
{
"client_id": "{{user `client_id_app1`}}",
"client_secret": "{{user `client_secret_app1`}}",
"tenant_id": "{{user `tenant_id_app1`}}",
}

Parameterizing variables can be useful in these ways:

  • A shortcut to values you use multiple times
  • To make the template portable
  • Default value with an empty string can be overridden at build time
  • Allows keeping secret tokens and environment-specific values out of templates

Read more on Packer variables here.

Running Packer

Packer builds the images using the build sub-command followed by a JSON template.

packer build template.json