Creating Azure VM images with Packer and Puppet Bolt

See more posts about: Tips & How To and Tools & Integrations

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 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 here.

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 them and then 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.

Safeguard 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 providing 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.

Puppet sites use proprietary and third-party cookies. By using our sites, you agree to our cookie policy.