Packer workflow: how to avoid rebuilding and safely iterate provisioning


I’m trying to move a very standard Rails app to Packer + Ansible + Terraform, on AWS.

After following some of the Pluralsight courses, this was my understanding of a possible workflow:

  1. Create an AMI in packer (based of a pre-defined AWS AMI - says RHEL);
  2. Provision with Ansible;
  3. Pipe through to Terraform somehow.

However being new to Ansible and having to port my existing configuration over, I don’t really understand the workflow because Packer creates a new image every single time, which doesn’t take ages but is slow enough to discourage anyone from iterating on the provisioning config (i.e my playbook.yml).

How do you people work with it? My current hack/solution is to use the vagrant postprocessors to build a box that I would import in Vagrant*, and then run Ansible against that instead of retrying every time against a live AWS agent.

Any pointers appreciated.

* although I’m even struggling with that step.

I have come across similar frustration and confusion. Although I haven’t yet found this particular Tao, a few realisations did help to illuminate.

The first is that there are different kinds of “Declarative Infrastructure as Code”. Ansible, Packer and Terraform are all declarative IaC, but Packer is not idempotent.

  1. a Terraform statement is statement of reality. You are telling the world to be in a particular state. If it is not in such a state, Terraform will try to manipulate whatever resources it has at its disposal to make the world be so; but if it is, Terraform’s plan will consist of zero actions, and all will be well with the world.
  2. An Ansible playbook is a statement of state. You are telling a given artifact in the world to be in a particular state. If it is not, Ansible will manipulate whatever resources it has at its disposal to make the artifact be so.
  3. a Packer template is a recipe. Start with this, use these tools and produce the following. Using the same ingredients and recipe will always make cakes that taste the same, but they will be different cakes – if that renders the idea. This is not the same as “if there is already a cake, don’t make it again”, ie Packer is not idempotent.

Realisation 1: Ensure that Terraform and Ansible act on different levels of the world. Don’t use Ansible to change things that are in Terraform’s state, e.g. If you can separate out what Ansible is repsonsible for from what Terraform is responsible for, they are essentially blind to each other and won’t argue over the nature of reality. IE they won’t see a difference in their perception of reality.
Realisation 2: Trick Terraform into thinking that it’s resources are immutable. If your Terraform state includes an AMI, but doesn’t make statements about the content of that AMI, then Terraform won’t try to change it.

This led me to the following workflow:

  1. Create AMI with Packer + Ansible playbook
  2. Plan/Apply with Terraform
  3. Deployed state s with image i in state i_s
    1. Make changes to playbook
    2. Run playbook against image state i_s :arrow_right: image state i_s’
      1. Ansible has made changes to the image state, but Terraform thinks it’s the same image!
    3. Commit changes to playbook

What you can see now are two processes - the outer loop (Packer + Ansible + Terraform) which produces versioned, well-known desired states, and the inner loop where changes to a single artifact’s state.

The inner loop is where you get your fast feedback.

The outer loop is something you can codify into a delivery pipeline, since it’s triggered by commits to your playbook (or other changes to the declaration of state).

Hope that helps :slight_smile:

1 Like