Defining the Packer configuration – Immutable Infrastructure with Packer

Packer allows us to define configuration in JSON as well as HCL files. As JSON is now deprecated and HCL is preferred, let’s define the Packer configuration using HCL.

To access resources for this section, switch to the following directory:
$ cd ~/modern-devops/ch10/packer

We will create the following files in the packer directory:
• variables.pkr.hcl: Contains a list of variables we would use while applying the configuration
• plugins.pkr.hcl: Contains the Packer plugin configuration
• webserver.pkr.hcl: Contains the Packer configuration for building the web server image
• dbserver.pkr.hcl: Contains the Packer configuration for building the dbserver image
• variables.pkrvars.hcl: Contains the values of the Packer variables defined in the variables.pkr.hcl file

The variables.pkr.hcl file contains the following:
variable “client_id” {
type = string
}
variable “client_secret” {
type = string
}
variable “subscription_id” {
type = string
}
variable “tenant_id” {
type = string
}

The variables.pkr.hcl file defines a list of user variables that we can use within the source and build blocks of the Packer configuration. We’ve defined four string variables – client_id, client_secret, tenant_id, and subscription_id. We can pass the values of these variables by using the variables.pkrvars.hcl variable file we defined in the last section.

Tip

Always provide sensitive data from external variables, such as a variable file, environment variables, or a secret manager, such as HashiCorp’s Vault. You should never commit sensitive information with code.

The plugins.pkr.hcl file contains the following block:

packer: This section defines the common configuration for Packer. In this case, we’ve defined the plugins required to build the image. There are two plugins defined here – ansible and azure. Plugins contain a source and version attribute. They contain everything you would need to interact with the technology component:
packer {
required_plugins {
ansible = {
source = “github.com/hashicorp/ansible”
version = “=1.1.0”
}
azure = {
source = “github.com/hashicorp/azure”
version = “=1.4.5”
}
}
}

The webserver.pkr.hcl file contains the following sections:

• source: The source block contains the configuration we would use to build the VM. As we build an azure-arm image, we define the source as follows:
source “azure-arm” “webserver” {
client_id = var.client_id
client_secret = var.client_secret
image_offer = “UbuntuServer”
image_publisher = “Canonical”
image_sku = “18.04-LTS”
location = “East US”
managed_image_name = “apache-webserver”
managed_image_resource_group_name = “packer-rg”
os_type = “Linux”
subscription_id = var.subscription_id
tenant_id = var.tenant_id
vm_size = “Standard_DS2_v2”
}

Different types of sources have different attributes that help us connect and authenticate with the cloud provider that the source is associated with. Other attributes define the build VM’s specification and the base image that the build VM will use. It also describes the properties of the custom image we’re trying to create. Since we’re using Azure in this case, its source type is azure-arm and consists of client_id, client_secret, tenant_id, and subscription_id, which helps Packer authenticate with the Azure API server. These attributes’ values are sourced from the variables.pkr.hcl file.

Tip

The managed image name can also contain a version. That will help you build a new image for every new version you want to deploy.

• build: The build block consists of sources and provisioner attributes. It contains all the sources we want to use, and the provisioner attribute allows us to configure the build VM to achieve the desired configuration. We’ve defined the following build block:
build {
sources = [“source.azure-arm.webserver”]
provisioner “ansible” {
playbook_file = “../ansible/webserver-playbook.yaml”
}
}

We’ve defined an Ansible provisioner to customize our VM. There are a lot of provisioners that Packer provides. Luckily, Packer provides the Ansible provisioner out of the box. The Ansible provisioner requires the path to the playbook file; therefore, in this case, we’ve provided ../ansible/ webserver-playbook.yaml.

Tip

You can specify multiple sources in the build block, each with the same or different types. Similarly, we can have numerous provisioners, each executed in parallel. So, if you want to build the same configuration for multiple cloud providers, you can specify multiple sources for each cloud provider.

Similarly, we’ve defined the following dbserver.pkr.hcl file:
source “azure-arm” “dbserver” {

managed_image_name = “mysql-dbserver”

}
build {
sources = [“source.azure-arm.dbserver”]
provisioner “ansible” {
playbook_file = “../ansible/dbserver-playbook.yaml”
}
}

The source block has the same configuration as the web server apart from managed_image_name. The build block is also like the web server, but instead, it uses the ../ansible/dbserver-playbook.yaml playbook.

Now, let’s look at the Packer workflow and how to use it to build the image.