Microsoft Azure Logo

Creating Azure VM Images With Packer and Puppet Bolt

Martez Reed

--

HashiCorp Packer is a free and open source tool for creating golden images for multiple platforms from a single source configuration. Packer makes it easy to codify VM images for Microsoft Azure.

In this blog post we’ll look at how to use HashiCorp Packer and Puppet Bolt to define our VM templates in code.

Puppet Bolt Packer Plugin

HashiCorp Packer doesn’t natively integrate with Puppet Bolt. A Packer plugin has been created to simplify this integration. To begin using the plugin, the latest release bundle for your operating system should be downloaded from the https://github.com/martezr/packer-provisioner-puppet-bolt/releases/latest Github repository and unpacked.

Once the packer-provisioner-puppet-bolt binary has been unpacked, it should be moved to a path on the system where Packer can find it as covered in the link below.

https://www.packer.io/docs/extending/plugins#installing-plugins

Puppet Bolt Plan

Ensure that the latest version of Puppet Bolt is installed before getting started.

In this post we’ll be using Puppet Bolt to install NGINX as a simple example of the integration between Packer and Bolt. The Bolt YAML plan below installs the epel-release repository, nginx and enables the service to start at boot.

parameters:
targets:
type: TargetSpec
steps:
- command: yum -y install epel-release
targets: $targets
description: "Install epel-release"
- command: yum -y install nginx
targets: $targets
description: "Install nginx"
- command: systemctl enable nginx
targets: $targets
description: "Start nginx on boot"

Packer Template

We now need to create our Packer template that defines the settings for our VM image such as the operating system and hardware configuration. Before we create our template we’ll generate our Azure credentials if we don’t already have credentials and create a dedicated resource group for the VM image generated by Packer.

Create a new Azure resource group for the VM image or using an existing resource group. We’ll specify a resource group in our Packer template later on.

az group create -n packerbolt -l centralus

We need to generate Azure credentials for Packer to use when building the VM image. The following command generates the necessary credentials assuming you are logged into Azure.

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

The Azure credentials should be displayed on the screen similar to those displayed below.

Safe guard the generated credentials, they should not be shared.

{
"client_id": "b27e2468-e9ad-5ea8-c043-196fc8d2q1mw",
"client_secret": "91f28cwg-49e3-1qr2-825a-42fne279fd01",
"tenant_id": "tg4b7md3-630k-8664-2t45-d1w923dww21w"
}

We can pass the credentials at the command line, include them in a variables file or add them as environment variables as seen below.

export ARM_CLIENT_ID="b27e2468-e9ad-5ea8-c043-196fc8d2q1mw"
export ARM_CLIENT_SECRET="91f28cwg-49e3-1qr2-825a-42fne279fd01"
export ARM_TENANT_ID="tg4b7md3-630k-8664-2t45-d1w923dww21w"

With the Azure credentials set we can now create our Packer template file to define our VM image. The managed_image_resource_group_name field is set to the Azure resource group we created earlier.

{
"variables": {
"client_id": "{{env `ARM_CLIENT_ID`}}",
"client_secret": "{{env `ARM_CLIENT_SECRET`}}",
"subscription_id": "{{env `ARM_SUBSCRIPTION_ID`}}",
"tenant_id": "{{env `ARM_TENANT_ID`}}",
"ssh_user": "centos",
"ssh_pass": "{{env `ARM_SSH_PASS`}}"
},
"builders": [{
"type": "azure-arm",
"client_id": "{{user `client_id`}}",
"client_secret": "{{user `client_secret`}}",
"subscription_id": "{{user `subscription_id`}}",
"tenant_id": "{{user `tenant_id`}}",
"managed_image_resource_group_name": "packerbolt",
"managed_image_name": "MyCentOSImage",
"ssh_username": "{{user `ssh_user`}}",
"ssh_password": "{{user `ssh_pass`}}",
"os_type": "Linux",
"image_publisher": "OpenLogic",
"image_offer": "CentOS",
"image_sku": "8_2",
"image_version": "latest",
"ssh_pty": "true",
"location": "Central US",
"vm_size": "Standard_B1MS"
}],
"provisioners": [
{
"type": "puppet-bolt",
"user": "centos",
"run_as": "root",
"bolt_module_path": "/Users/martez.reed/Documents/GitHub/puppet-on-azure/Bolt",
"bolt_plan": "azure::web",
"bolt_params": {}
},
{
"execute_command": "echo '{{user `ssh_pass`}}' | {{ .Vars }} sudo -S -E sh '{{ .Path }}'",
"inline": [
"yum update -y",
"/usr/sbin/waagent -force -deprovision+user && export HISTSIZE=0 && sync"
],
"inline_shebang": "/bin/sh -x",
"type": "shell",
"skip_clean": true
}
]
}

The Puppet Bolt provisioner section from the full template above shows that we’ve specified a few settings for our Puppet Bolt provisioner. We specified a Bolt plan, a path for where to look for our modules and authentication along with privilege escalation information.

{
"type": "puppet-bolt",
"user": "centos",
"run_as": "root",
"bolt_module_path": "./puppet-on-azure/Bolt",
"bolt_plan": "azure::web",
"bolt_params": {}
}

With the Packer template created we can now build our Azure image by running the packer build command and provide the name of the template file.

packer build centos8.json

The build will take a few minutes and should display output similar to that shown below:

azure-arm: output will be in this color.==> azure-arm: Running builder ...
==> azure-arm: Getting tokens using client secret
==> azure-arm: Getting tokens using client secret
azure-arm: Creating Azure Resource Manager (ARM) client ...
==> azure-arm: WARNING: Zone resiliency may not be supported in Central US, checkout the docs at https://docs.microsoft.com/en-us/azure/availability-zones/
==> azure-arm: Creating resource group ...
==> azure-arm: -> ResourceGroupName : 'pkr-Resource-Group-rivksir0po'
==> azure-arm: -> Location : 'Central US'
==> azure-arm: -> Tags :
==> azure-arm: Validating deployment template ...
==> azure-arm: -> ResourceGroupName : 'pkr-Resource-Group-rivksir0po'
==> azure-arm: -> DeploymentName : 'pkrdprivksir0po'
==> azure-arm: Deploying deployment template ...
==> azure-arm: -> ResourceGroupName : 'pkr-Resource-Group-rivksir0po'
==> azure-arm: -> DeploymentName : 'pkrdprivksir0po'
==> azure-arm: Getting the VM's IP address ...
==> azure-arm: -> ResourceGroupName : 'pkr-Resource-Group-rivksir0po'
==> azure-arm: -> PublicIPAddressName : 'pkriprivksir0po'
==> azure-arm: -> NicName : 'pkrnirivksir0po'
==> azure-arm: -> Network Connection : 'PublicEndpoint'
==> azure-arm: -> IP Address : '23.101.127.134'
==> azure-arm: Waiting for SSH to become available...
==> azure-arm: Connected to SSH!
==> azure-arm: Provisioning with Puppet Bolt...
==> azure-arm: Executing Bolt: bolt plan run azure::web --params {} --modulepath /Users/martez.reed/Documents/GitHub/puppet-on-azure/Bolt --targets ssh://127.0.0.1:65059 --user centos --no-host-key-check --private-key /var/folders/ly/bwpnd5gn5tv7549rgn80x4jw0000z_/T/packer-provisioner-bolt.164237326.key --run-as root
azure-arm: Starting: plan azure::web
azure-arm: Starting: Install epel-release on ssh://127.0.0.1:65059
azure-arm: Finished: Install epel-release with 0 failures in 11.75 sec
azure-arm: Starting: Install nginx on ssh://127.0.0.1:65059
azure-arm: Finished: Install nginx with 0 failures in 17.38 sec
azure-arm: Finished: plan azure::web in 29.15 sec
azure-arm: Plan completed successfully with no result
==> azure-arm: Provisioning with shell script: /var/folders/ly/bwpnd5gn5tv7549rgn80x4jw0000z_/T/packer-shell758099055
==> azure-arm: Querying the machine's properties ...
==> azure-arm: -> ResourceGroupName : 'pkr-Resource-Group-rivksir0po'
==> azure-arm: -> ComputeName : 'pkrvmrivksir0po'
==> azure-arm: -> Managed OS Disk : '/subscriptions/2a646183-6919-4320-a1f3-c6985fc5d87e/resourceGroups/PKR-RESOURCE-GROUP-RIVKSIR0PO/providers/Microsoft.Compute/disks/pkrosrivksir0po'
==> azure-arm: Querying the machine's additional disks properties ...
==> azure-arm: -> ResourceGroupName : 'pkr-Resource-Group-rivksir0po'
==> azure-arm: -> ComputeName : 'pkrvmrivksir0po'
==> azure-arm: Powering off machine ...
==> azure-arm: -> ResourceGroupName : 'pkr-Resource-Group-rivksir0po'
==> azure-arm: -> ComputeName : 'pkrvmrivksir0po'
==> azure-arm: Capturing image ...
==> azure-arm: -> Compute ResourceGroupName : 'pkr-Resource-Group-rivksir0po'
==> azure-arm: -> Compute Name : 'pkrvmrivksir0po'
==> azure-arm: -> Compute Location : 'Central US'
==> azure-arm: -> Image ResourceGroupName : 'packerbolt'
==> azure-arm: -> Image Name : 'MyCentOSImage'
==> azure-arm: -> Image Location : 'Central US'
==> azure-arm: Deleting resource group ...
==> azure-arm: -> ResourceGroupName : 'pkr-Resource-Group-rivksir0po'
==> azure-arm:
==> azure-arm: The resource group was created by Packer, deleting ...
==> azure-arm: Deleting the temporary OS disk ...
==> azure-arm: -> OS Disk : skipping, managed disk was used...
==> azure-arm: Deleting the temporary Additional disk ...
==> azure-arm: -> Additional Disk : skipping, managed disk was used...
==> azure-arm: Removing the created Deployment object: 'pkrdprivksir0po'
==> azure-arm: ERROR: -> ResourceGroupNotFound : Resource group 'pkr-Resource-Group-rivksir0po' could not be found.
==> azure-arm:
Build 'azure-arm' finished.
==> Builds finished. The artifacts of successful builds are:
--> azure-arm: Azure.ResourceManagement.VMImage:
OSType: Linux
ManagedImageResourceGroupName: packerbolt
ManagedImageName: MyCentOSImage
ManagedImageId: /subscriptions/2a646183-6919-4320-a1f3-c6985fc5d87e/resourceGroups/packerbolt/providers/Microsoft.Compute/images/MyCentOSImage
ManagedImageLocation: Central US

The Puppet Bolt plan can be much more complex but the goal of this post was to showcase how easy it is to integrate the two together.

--

--

Martez Reed

Director of Technical Marketing at Morpheus Data. Operations background with an interest in automation and orchestration.