DEV Community

Ederson Brilhante
Ederson Brilhante

Posted on

Combining Packer, QEMU, Ubuntu Cloud Images, and Ansible

Hello everyone! I want to share a current use case at my company where I have the opportunity to work with Packer, QEMU, Ansible and Ubuntu Cloud Images leveraging the concept of Infrastructure as Code (IaC).

Infrastructure as Code (IaC) is a software engineering practice that enables the management and provisioning of infrastructure resources through code. Instead of manually configuring servers and infrastructure components, IaC allows you to define your desired infrastructure state using declarative or imperative code. It brings automation, version control, and consistency to infrastructure management.

In our case, we utilize Packer, which is a powerful tool falling under the umbrella of IaC. Packer enables the creation of identical machine images for multiple platforms, such as virtual machines, containers, or cloud instances. With Packer, we define the configuration of our desired machine image, including the operating system, software stack, and customizations, all through code. Packer then automates the process of building these machine images, ensuring consistency and reproducibility.

To further enhance our image-building process, we integrate Ansible as the provisioner for Packer. Ansible is an open-source automation tool that enables the configuration and management of systems through simple, human-readable YAML files. With Ansible, we define the desired state of our machine image, including the installation of packages, configuration files, and any other necessary setup steps. Ansible seamlessly integrates with Packer, allowing us to provision our machine image with ease.

In our deployments, we rely on Ubuntu images to meet our diverse cloud computing needs. Ubuntu offers three types of images: live, server, and cloud. Live images provide a fully functional Ubuntu desktop environment that can be run directly from a USB drive or DVD without the need for installation, while server images are optimized for server deployments. However, for our specific use case, we have different requirements depending on the deployment environment. For our deployments in public cloud environments, we leverage the official Ubuntu images provided by the cloud provider, which are tailored and certified for their specific platform. Similarly, in our private on-premises cloud, we utilize the cloud version of Ubuntu images. These cloud images are specifically designed and pre-configured for cloud computing platforms, offering optimized performance and scalability. They enable us to efficiently deploy and manage Ubuntu instances in both our public and private cloud environments.

Now, let's delve into our challenge. The process of building VM images for the public cloud involves the use of appropriate plugins for Packer. However, when it comes to our on-premises cloud, we encountered an obstacle. Our existing process relied on a deprecated plugin relying on QEMU as its underlying technology. QEMU, an open-source virtualization tool, empowers us to operate and manage virtual machines in various formats, including qcow2. To overcome this hurdle, our aim was to leverage QEMU using an official and updated plugin for Packer. This integration would seamlessly incorporate QEMU into our image-building process, delivering enhanced efficiency and reliability.

While I had prior experience with Packer, my familiarity with QEMU was limited, especially when it came to using Packer with QEMU. To address this knowledge gap, I referred to the official documentation of Packer. However, I encountered a challenge: the documentation provided an example using a server version of CentOS, which wasn't suitable for my requirements. I needed a cloud version of Ubuntu, which does not come with default user and password credentials. To overcome this hurdle, I created a seed image that included user-data and meta-data. This seed image allows us to "emulate" the cloud-init functionality. By combining this seed image with the Ubuntu image, Packer can establish an SSH connection to the virtual machine successfully.

In the seed image, we create a user with the necessary credentials for the initial build process. It's important to note that the user created in the seed image is only intended for the build phase and is not present in the final image. This approach ensures that the final image does not contain any unnecessary or insecure credentials, maintaining a clean and secure environment.

Here's the code to generate the seed image for the packer_qemu.seed.pkr.hcl script:

source "file" "user_data" {
  content = <<EOF
#cloud-config
ssh_pwauth: True
users:
  - name: user
    plain_text_passwd: packer
    sudo: ALL=(ALL) NOPASSWD:ALL
    shell: /bin/bash
    lock_passwd: false
EOF
  target  = "user-data"
}

source "file" "meta_data" {
  content = <<EOF
{"instance-id":"packer-worker.tenant-local","local-hostname":"packer-worker"}
EOF
  target  = "meta-data"
}

build {
  sources = ["sources.file.user_data", "sources.file.meta_data"]

  provisioner "shell-local" {
    inline = ["genisoimage -output cidata.iso -input-charset utf-8 -volid cidata -joliet -r user-data meta-data"]
  }
}
Enter fullscreen mode Exit fullscreen mode

In the provided code, the genisoimage command-line tool plays a crucial role in generating the necessary configuration files for cloud-init. Specifically, it is used to create the cidata.iso file, which encapsulates the user_data and meta_data files. These files contain important cloud-init configuration data, such as user credentials and metadata information for the instance.
By utilizing genisoimage, we can create a bootable ISO image that incorporates the required configuration data. This ISO image is then seamlessly integrated into the image-building process by Packer.
To gain a better understanding of the genisoimage command-line options and functionality, you can refer to the official documentation at Genisoimage Documentation. The documentation provides detailed explanations and examples to help you effectively utilize genisoimage in your image-building workflow.

Here's the code for the packer_qemu.qcow2.pkr.hcl script that uses the seed image and cloud image to build a new image and then runs an Ansible playbook to configure the new image:

packer {
  required_plugins {
    vagrant = {
      version = "1.0.9"
      source  = "github.com/hashicorp/qemu"
    }
    ansible = {
      version = "1.0.4"
      source  = "github.com/hashicorp/ansible"
    }
  }
}

source "qemu" "ubuntu" {
  format           = "qcow2"
  disk_image       = true
  disk_size        = "10G"
  headless         = true
  iso_checksum     = "file:https://cloud-images.ubuntu.com/focal/current/SHA256SUMS"
  iso_url          = "https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-amd64.img"
  qemuargs         = [["-m", "12G"], ["-smp", "8"], ["-cdrom", "cidata.iso"], ["-serial", "mon:stdio"]]
  shutdown_command = "echo 'packer' | sudo -S shutdown -P now"
  ssh_password     = "packer"
  ssh_username     = "user"
  vm_name          = "build.qcow2"
  output_directory = "output"
}

build {
  sources = ["source.qemu.ubuntu"]

  provisioner "ansible" {
    playbook_file = "ansible/qemu.yml"
  }
}
Enter fullscreen mode Exit fullscreen mode

This code snippet showcases the Packer configuration language to orchestrate the build process. It begins with the packer block, which outlines the essential plugins required for Packer, including qemu and ansible. The source block focuses on configuring the QEMU source, encompassing various settings such as the format, disk size, ISO checksum and URL, QEMU arguments, SSH credentials, and more. Within the build block, the QEMU source is designated for the build process.
Additionally, the provisioner section incorporates the ansible provisioner, specifying the Ansible playbook (ansible/qemu.yml) to execute for further customization of the newly created image. For a comprehensive understanding of the packer plugin arguments pertaining to qemu, you can refer to the official documentation at Packer QEMU Plugin. The documentation offers detailed insights into the various plugin options and configurations available for QEMU integration within Packer.

By combining Packer, QEMU, Ubuntu Cloud Images, and Ansible, we are able to automate the process of building consistent and reproducible machine images for our on-premises Data Center. This streamlined approach saves time, ensures consistency across our environments, and maintains a secure image without unnecessary credentials.

I hope sharing our experience and providing these code snippets will help the community facing similar challenges in the future. Let's continue building and automating together! If you have any questions or suggestions, feel free to reach out.

Top comments (0)