Introduction

Ansible is an open-source automation tool that simplifies the management and configuration of systems. It uses simple, human-readable YAML files called playbooks to automate tasks like software provisioning, configuration management, and application deployment across multiple servers, making it easier to maintain consistent environments.

Packer is a tool designed to automate the creation of machine images for multiple platforms from a single configuration file. It allows you to define, build, and distribute virtual machine images and Docker containers images. Given the compatibility with all the major cloud providers and the ability to run parallel builds, it is a great choice for organizations that have a multi-cloud infrastructure estate and prefer to remain cloud-agnostic.

Packer allows for VM configuration via multiple provisioners, such as Ansible, Puppet, Chef or simple shell scripts. In this blog we will focus on Ansible since it is the preferred configuration tool due to its agentless architecture and simple declarative YAML language.

Prerequisites

  1. Install Packer
  2. Install Ansible (you should have Python installed on your local machine)
  3. Install Terraform
  4. Have an AWS account and AWS credentials set on your local machine

Demo

At the end of this blog post you will have a running Kubernetes control node deployed from an AMI built with Packer and configured with Ansible. The repository hosting the code is available at ansible-packer-demo, clone it and change your directory inside the repo directory.

The Packer build template is k8s-controller-ubuntu.pkr.hcl. It contains an amazon-ebs source which has a vanilla Ubuntu 22 image as the source AMI. This source AMI will be used for the provisioning of the Packer instance.

Run the command below to build the new AMI:

packer build k8s-controller-ubuntu.pkr.hcl

As part of the build process with the current build configuration, Packer will create a temporary EC2 instance in the default VPC in the eu-west-1 region, a temporary security group which allows inbound SSH access and a key-pair for SSH access to the temporary instance. The Ansible provisioner will run the Ansible playbook mentioned in the provisioner block through a SSH connection from the local machine running the Packer build to the temporary instance. There is a custom ansible.cfg file where several default values are defined. This is passed to the Ansible provisioner as an environment variable.

The main.yml file executes several Ansible roles, and the Ansible Packer provisioner effectively manages and applies them during the build process. The Ansible roles will configure:

  • kernel modules for bridge networking and overlay filesystems
  • containerd as a container runtime and crictl for container debugging
  • kubectl, kubeadm and kubelet
  • Calico installation manifest files

After the Ansible playbook finished running, Packer will stop the temporary instance, create an AMI and consequently take a snapshot of the instance EBS volume. After the new AMI is created Packer will terminate the instance and delete the security group and key-pair.

alt

Copy the AMI ID from the packer build command output and replace the REPLACE_ME placeholder in the terraform.tfvars file.

The Terraform files contain the code necessary to deploy a new EC2 instance with the new AMI together with the supporting infrastructure(VPC, security group, SSM endpoints, NAT GW, IGW, route tables). SSM endpoints are deployed so that the new EC2 instance can be accessed from the AWS console. There is an user-data script which makes use of the installed kubeadm binary to bootstrap a kubernetes control node.

Run terraform apply and wait for the infrastructure to be deployed and the EC2 instance status checks to pass. It can take up to 5 minutes before you can connect to the instance via SSM (SSM registration via the SSM Agent takes its time), so please be patient.

The user-data script will add a kubeconfig file for both the root and ubuntu users. Below you can see kubectl commands run as root, showing that the instance was successfully setup as a kubernetes node:

alt

Combining Ansible with Packer is a powerful approach because it allows you to configure instances according to your needs and save substantial time by embedding these configurations into AMIs. In our case, we only needed to run the kubeadm init command for the Kubernetes controller setup, which streamlined the process by eliminating the need for repetitive configuration and dependency installation. I hope you found this demo informative and enjoyed seeing some of the powerful automation capabilities in action. Until next time, happy learning!