Packer Workflows with Jenkins - Building
Tags: packer, jenkins, tutorial
Published: 2024-02-29 03:26:47

This is the second part of a two-parter. Just realized I was about to write a book if I kept going. Now that we have the necessary instances and setup in part 1, we will continue and start building out our images. 

> Note: 
> I am using openSUSE Leap, which will soon be discontinued and only maintained for a few years after 15.6. Alternatives would be either Tumbleweed, MicroOS, and Slowroll. 

## Objectives

Primary: 

- Create a repository for Packer Templates. 
- Use Jenkins to build the images. 
	- I'm going to use a Jenkinsfile as well. 

Secondary:

-  Parallel Build in AWS and Azure.

### Prerequisites 

Assumption is a basic knowledge of Jenkins, Packer, and Azure. I'm not going to go into much details on how to set up Azure accounts, service accounts, and some other items in this. There are basic examples out the which can be referenced. 

1. Version Control System (GitHub, GitLab, etc.)
2. Azure Subscription
3. AWS Account 

## Repository Setup

**I'm going to keep the repository simple. In most cases the following are needed. **

- Folder/Files necessary for CI/CD pipelines
	- In this case, I will end up using Jenkinsfile after all. Didn't initially plan to. 
	- If this was GitHub actions, I'd have a .github with workflows folder. For Jenkins, I'll drop them under a Jenkins directory. 
- Packer Folder
	- In this folder I also have: 
		- Builds: for build templates
		- Files: used by the templatefile function or file provisioner. 
		- Scripts: for longer scripts which will be more frequently updated. I try to stay away from inline scripts with a provisioner. 
		- Var-Files: directory to store var-files which can get pulled in by the pipeline.

**For a tree view: **

```
.
├── jenkins
│   └── suse-linux-base
└── packer
    ├── builds
    │   ├── builds_suse.pkr.hcl
    │   └── variables.pkr.hcl
    ├── files
    │   └── README.md
    ├── scripts
    │   └── README.md
    └── var-files
        └── README.md

7 directories, 6 files

```

The repository was made public for anyone that wants to fork it as a demo. 

https://github.com/benjamin-lykins/demo-jenkins-packer.git

## Jenkins Setup

**Create a folder for packer builds.**

![Folder](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/skpbw3fm8dzvjhc1bo84.png)

### Jenkins Setup - Credentials

The next item you will want to set up is credentials which will be used by jobs in this folder. 

**On the left hand side, select `Credentials`. **

![Credentials](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3qt55zyjzcbzx71qmtrh.png)

**Select stores scoped to `packer builds` or the name of the folder you created. Click on the global domain. **

![Scope](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dep2vnhqq7kr7cv8emgt.png)

**Add credentials. **

![Add Credentials](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l1br5zancgh2s20e2url.png)

**For credentials, we will do secret text for all Azure related credentials. Create a secret for each: **
	1. Client ID (Service Account ID)
		- `azure_client_id`
	2. Client Secret (Service Account Secret)
		- `azure_client_secret`
	3. Subscription ID
		- `azure_subscription_id`
	4. Tenant ID
		-  `azure_tenant_id`

![Credential Setup](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vhf1w47v4hwoq2hq0tzw.png)

**When all the credentials have been added, it should look like this: **

![Credentials](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d1o2q46x2sgw3xbgh2s4.png)

### Jenkins Setup - First Workflow

This first workflow will be simple. Get the latest openSuse image and build and image. 

Go into the packer builds folder and create a new pipeline. I called mine `suse-linux-base`. 

**Nothing major setup wise, for Git: **

![Pipeline Setup](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u1o68k0zj9htyhxdzvl8.png)

**For Jenkinsfile:**

![Jenkinsfile](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h02tfp2kp0lbqy8qop64.png)

## Jenkins - First Build

If forking the repository I provided, the first run should build and run properly. 

**This is coming from a fresh Azure Account and Subscription.**

![Azure Welcome Screen](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qafm5uu8jc1rv65xag0h.png)

**No resource groups exist.**

![Resource Groups](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bu2aduahm2v2jbpnbhuw.png)

At this point we can kick off the first build. 

**In `suse-linux-base`, select Build Now.** 

In my case I had a small hiccup, took me a couple of tries to get it working: 

![Pipeline Runs](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lvkfkwu4blb2727asszi.png)

Ended up changing the Create Azure Resource step a couple of times, it was running commands out of order, once I got it running, it was doing its thing properly. 

- packer-rg was created.  
- packer-rg-temp was created as a temporary resource group to build images in. 

![Resource Groups](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wiv1b01tfelgleeisj8h.png)

**Build Completion**

You can see the build was completed in the both Jenkins and in the Azure console. 

![Managed Image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h1u705y077470zs9oy7g.png)

**Create VM from Image** 

From the image, select create VM. Once ready, ssh to connect. 

```
openSUSE Leap 15.5 x86_64 (64-bit)

If you are using extensions consider to enable the auto-update feature
of the extension agent and restarting the service. As root execute:
  - sed -i s/AutoUpdate.Enabled=n/AutoUpdate.Enabled=y/ /etc/waagent.conf
  - rcwaagent restart

As "root" use the:
- zypper command for package management
- yast command for configuration management

Have a lot of fun...
```

Now that is it. Pretty straight-forward setup. If this is all I wanted, then this would be the stopping point. Very basic usage of Packer, but looking to expand upon what is possible. 

## Parallel Builds

One of my old jobs I was a vSphere administrator. Every month the virtual machine templates would need to be patched and updated in some way. It was a very manual process and we did not use a tool like Packer to automate or store the configuration in code. It was document and click-ops heavy. 

Now, I will not be building images in vSphere, but I will like to build the same image in two public clouds, AWS and Azure. 

### AWS - Setup

Nothing major needed here, you will need two items. Packer will use default VPC and subnet.  

1. AWS Secret and Key 
2. Subscription to openSUSE Leap

![openSUSE Leap Subscription](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wy9b56cym2nq64aatbhq.png)

### Packer - Setup

To set this up, I will need to add in an additional plugin to the configuration. 

**Create New Branch** 

I am going to create a new branch called `parallel-aws-azure` for this use case. 

```hcl
packer {
  required_plugins {
    azure = {
      source  = "github.com/hashicorp/azure"
      version = "~> 2"
    }
    amazon = {
      source  = "github.com/hashicorp/amazon"
      version = "~> 1"
    }
  }
}
```

**Add a new source block for AWS.** 

> Check for ssh_username: [Default user names](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/managing-users.html). In this scenario `ec2-user` or `root` would have worked. 

```hcl
source "amazon-ebs" "suse" {
  ami_name      = "suse-image-${local.time}"
  instance_type = "t2.micro"
  region        = "us-east-2"
  source_ami_filter {
    filters = {
      name                = "openSUSE-Leap-*-hvm-ssd-x86_64-*"
      root-device-type    = "ebs"
      virtualization-type = "hvm"
    }
    most_recent = true
    owners      = ["679593333241"]
  }
  ssh_username = "ec2-user"
}
```

And lastly, update the build block with the new source. 

```hcl
build {
  sources = ["source.azure-arm.suse", "source.amazon-ebs.suse"]

  provisioner "shell" {
    inline = ["echo foo"]
  }
}
```

### Jenkins - Setup

 I'll keep the same jenkinsfile for this. All I will update is adding credentials for AWS. 

```
    environment {
        AZURE_SUBSCRIPTION_ID = credentials('azure_subscription_id')
        AZURE_TENANT_ID = credentials('azure_tenant_id')
        AZURE_CLIENT_ID = credentials('azure_client_id')
        AZURE_CLIENT_SECRET = credentials('azure_client_secret')
        AWS_ACCESS_KEY_ID= credentials('aws_access_key_id')
        AWS_SECRET_ACCESS_KEY= credentials('aws_secret_access_key')
    }
```

Also, ensure the`packer build` folder has these keys. 

![AWS secrets](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ih10ivbqfox4knclcjvl.png)

**Create New Pipeline**

I called mine `parallel-build`. 

Point to the new branch: 

![Point to new branch](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/65f9ugf0ywodcwnkl9pm.png)

Keep the same Jenkinsfile path: 

![Jenkinsfile path](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ihnn3w888k57spfytykt.png)

Once ready, hit build and see how it goes. 

Packer by default will run both builds concurrently. 

```
[1;32mazure-arm.suse: output will be in this color.[0m
[1;36mamazon-ebs.suse: output will be in this color.[0m
```

When completed, Packer will output the AMI id and Azure managed image: 

**Packer:** 

```
==> Builds finished. The artifacts of successful builds are:
--> amazon-ebs.suse: AMIs were created:
us-east-2: ami-09de620f124c9bc00
```

**Azure:** 

```
--> azure-arm.suse: Azure.ResourceManagement.VMImage:

OSType: Linux
ManagedImageResourceGroupName: packer-rg
ManagedImageName: suse-image-20240226195516
ManagedImageId: /subscriptions/****/resourceGroups/packer-rg/providers/Microsoft.Compute/images/suse-image-20240226195516
ManagedImageLocation: East US
```

**In AWS console:** 

![AWS AMI](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2rwk8ij964utbyx3xgd6.png)

**In Azure console:** 

![Azure Image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qclm7zbbkvfvgeuefcu8.png)

Nothing too drastically different when it comes to parallel builds in multiple public clouds when setting up in Jenkins.

## Conclusion

This is a very surface level example of using Packer with Jenkins. Additional use cases would be: 

- Layering Builds
	- Reference newly build images and add components necessary for hosting an application, such as IIS or Tomcat on the host. 
- Chaining Pipelines
	- This can be done similar to layering builds. Have the base image pipeline run first to build a base image, if success, have a subsequent pipeline trigger to build on the image built in the base pipeline. 
- Automate Provisioning of Images
	- When a new image is built, trigger a Terraform Cloud workspace to run a plan and apply to pick up the latest image. 
		- In AWS, Autoscaling Groups can trigger a refresh. 
			- https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/autoscaling_group#triggers
- Automate Testing of Images
	- I honestly do not have many ideas for this. For me if an image is built with Packer and can be provisioned whether in AWS or Azure, and finally an application can be deployed on it, then that is enough for me. I could see security and compliance tools. Probably just do not have the exposure for this.