I am a Sr. Software Developer at Oracle Cloud. The opinions expressed here are my own and not necessarily those of my employer.
Infrastructure as Code with Packer, Ansible and Terraform
This article is meant demonstrate one possible way of integrating Packer, Ansible and Terraform. It assumes that the reader is somewhat familiar with these (or similar) tools. For more in depth info on each tool please consult other resources.
When building modern software applications we often follow MVC pattern reinforced by different frameworks. We have varous layers (DB, ORM, controlers, UI templaters, etc). And while we could hardcode SQL query in our presentation layer it is not a good long term solution.
With Infrastructure as Code we also have various layers. We have base images, we need to provision them (create folders / files, install software, etc). And we need to be able to manage them reliably at scale. In this article we will expore how to use Packer, Ansible and Terraform for this purpose.
Packer
Packer is an open source tool for creating identical machine images for multiple platforms from a single source configuration. We can run it like this packer build packer_example.json
using exampe below.
Builders
We are using oracle-oci
as type
of builder. Packer also supports different builders for various cloud platforms. We need to specify various parameters such as base image
, ssh username
, etc.
We also need to specify the credentials that Packer will use to communicate with Oracle Cloud APIs. In this case Packer will use the default creds in ~/.oci/config
file but that may vary depending on the cloud builder.
Variables
We can either hard code various params in the builders section or set variables and reference them. We also can use environment variables in case we do not want to put sensitive info in these files as they will be committed to our source code repository.
Provisioners - bash
Once we launch an instance from the base image we need to execute our provisioning steps before it saved to a new image. For simple tasks we can use inline bash commands or reference external .sh
files. In the example above we execute sleep statements for demo purposes.
Provisioners - Ansible
For more complex provisioning we can use a tool like Ansible. Here is a packer_playbook.yml
that we referenced in packer_example.json
. It will install specific versions of Redis and Nginx and then stop those services.
Testing the playbook
The process of launching an instance, running playbook and saving a new image can take 5-10 minutes. To speed up the process we can launch a separate instance from the same base image, test the playbook via Ansible directly, verify the provisioning steps and then use the playbook via Packer.
Another option is to run packer build -debug packer_example.json
which will pause at every step. This allows us to SSH to the instance while it’s running and verify the provisoning steps.
Terraform
The output of the Packer build process will be an image ID which can be used to launch instances via Terraform. Terraform supports many cloud providers but below we will be using examples with Oracle Cloud.
Creating a module
To keep our config files DRY we will first create this Terraform module main.tf
file.
Leveraging module
Now we can leverage the module above from another main.tf
:
When we run terrafrom apply
this will launch 1 worker and 2 web servers. Some of the params are hardcoded in the module but others can be configured via variables.
Here we are specifying many of the same cloud provider specific info as we did with Packer. To keep things DRY we could extract these attributers into environmental variables.
Ansible
Once our web
and worker
servers are launched we need to do additional provisioning. We need to launch Nginx service on the web
and Redis service on worker
servers.
We create web.yml
:
And worker.yml
:
Inventory
We also need a hosts.yml
file specifying IP addresses of the instances:
Now we can run ansible-playbook -i hosts.yml web.yml worker.yml
which will perform the tasks specified on the appropriate servers. We also can add other steps such instaling other packages, creating folders and deploying our code.
Summary
Creating one image with pre-installed Redis and Nginx simplifies our processes. If we need to update security patches or install new version of Redis / Nginx we simply modify the Packer JSON file and re-run it. Then we use the new image in the Terraform step. Finally Ansible step enables the services we need on the appropriate instances.
Using tools like Packer, Ansible and Terraform automates manual processes and significantly increases our productivity. We can also version control the YML, TF and JSON files. And this helps reuse more of our code by extracting logic in Terraform modules or Ansilble roles.
One downside is there is some overlap between these tools. For example, Terraform can use cloud-init provisioner instead of Ansible. The tools also use different formats / commands. Hopefully with time we will come up with more unified standards.
Links
- https://www.packer.io/docs/builders/oracle-oci.html
- https://www.packer.io/docs/provisioners/ansible-local.html
- https://www.terraform.io/docs/providers/oci/index.html
- https://registry.terraform.io/
- https://docs.ansible.com/ansible/latest/index.html
- https://github.com/oracle/oci-ansible-modules/tree/master/inventory-script