Packer and the Ansible Provisioner

Packer and the Ansible Provisioner

· Read in about 5 min · (866 words) ·

Playing with Packer

Packer is some seriously magic stuff. It was the first HashiCorp product I ever used and continues to be one of my favorites. Recently, Packer got a version bump to 1.5 which included more than a few nice features. One of the bigger features was the inclusion of the JetBrains vSphere builder which I've covered a few times previously. This builder makes it wicked easy to build images on vSphere, and removes some of the more frustrating requirements around the previous version. If you haven't taken Packer for a spin yet - you absolutely should. All that being said, this post isn't going to be a dive into using Packer. We're going to be talking about a specific feature, the Ansible provisioner. Packer provisioners execute after the initial build process completes, and allow customization of the OS. Do you want to learn more about getting started with Packer? Check out the HashiCorp Learn guides for Packer, or my earlier posts on the topic.

I've been doing a bunch of work with HashiCorp Consul (obviously, as mentioned in the previous post from almost 3 months ago). Recently, I've been working on a few scenarios that have had me redeploying my clusters, sometimes repeatedly. I wanted a consistent way to do this, that I could ultimately trigger using Terraform. Traditional “DevOps Ethos” (that's a thing, right?) would say to throw this process into some sort of automation tool (Ansible, for example) to automate configuration after the fact. Keep the base image light, layer customizations on, and all that. I wanted to take a different route this time.

When I was at VMware, we'd often talk about the concept of “template sprawl”. What if template sprawl wasn't automatically a bad thing? In the public cloud, it's extremely common to build custom images for point use cases. The idea being, it's easy to build them, and easy to destroy them after. If manageability is unimpacted, what does it matter the method? Anywho, enough questions - let's get into taking a look!

Exploring the Ansible Provisioner

In Packer, we invoke a Provisioner by including it aside our Builder section of our image JSON (or HCL now!). You can see a sample of this code below

{
...
"provisioners": [
    {
      "type": "ansible",
      "playbook_file": "./playbook.yml"
    }
  ]
}

This snippet of code tells packer to stand up a local ansible control host, and executes the playbook that's located in the same directory as our Packer build file against the resulting build image. What's especially cool about this is that many of the normal capabilities we expect from Ansible are present here as well - including the ability to bring in internal (collected by ansible using the include_vars primitive) or external variables(defined via a variables file). In the case of my environment, my playbook completes these tasks…

  • Pulls down the Consul Binaries based from HashiCorp Releases
  • Unpacks the archive and copies them to the right PATH directory
  • Copies a systemd file for Consul to the /etc/systemd/system directory
  • Enables the service to start at machine boot (but after network.service, thanks systemd unit files)
  • Create a Consul server (or client) configuration file
  • Create the Consul data directory

This play runs in about 10 minutes, and the end result is an image that I'm able to deploy N amount of times to deploy a Consul cluster. In my Consul configuration, I've set the expected number of bootstrap nodes to 3. I deploy the cluster using Terraform, and in about 15 minutes, I've got a new Consul cluster. I've also replicated this same process for client machines in my environment, so that when I deploy additional systems for testing (hello new Nomad cluster in my environment…) they automatically are joined to Consul. In my environment, I've also created a Makefile that allows me to issue a simple make command to build my images and replace the previous template.

In the End, What's the Goal?

I'm always careful when it comes to “purpose built” automation (for example, automation that exists for a single platform) - but I try to always ask myself “What's the ultimate goal?”

In my case, I wanted a way to rapidly rebuild clusters that were consistently the same. Likewise, I wanted my base images to be standardized so that when I deploy additional systems in my lab, they are also consistent. Finally, I also wanted the ability to switch the deployment target easily (i.e. to move this same model to Azure, AWS, etc…). This solution hits all those points with minimal administrative overhead as well as removes the need for any external systems to accomplish this. We see this model executed often against images built in the public cloud, why not extend that to private cloud as well? That's one of my favorite things about working for HashiCorp, and the tools we build. We make tools that make it easy to consistently operate between these environments.

Code Samples

For the sake of future discussion, I've thrown the samples of this work into a GitHub repo named “packer-ansible-sample” for your review. As with all automation, there's always a number of perspectives people take on “how” to do things, so be kind :)