Creating a Image for MaaS with Packer

In this post we are going to build an image with Packer which will be used to deploy via MaaS . After image built and uploaded to MaaS, it can be used to provision virtual machine or deploy OS on Bare-Metal machines. In order to build an image that is deployable with MaaS, we need couple of files which you can clone here .

For this post, It will be created minimal CentOS7 image including httpd package to test. One of the cool thing is with Packer that you can also run execute your Ansible playbook inside the machine being provisioned by Packer. Ansible(remote) provisioner is used to configure ntp server and install httpd.

As Qemu used as a builder, qemu-system-x64 has to be installed on the host where Packer runs. Packer will create an qcow2 image after successful image creation. But we are going to use tar.gz image file as we deploy image via MaaS.

centos7.json (do not forget to change iso_url in accordance with your environment.)

    "builders": [
            "type": "qemu",
      "iso_url": "/home/tesla/packer/centos7/isos/CentOS-7-x86_64-NetInstall-2003.iso",
            "iso_checksum_type": "sha256",
      "iso_checksum": "101bc813d2af9ccf534d112cbe8670e6d900425b297d1a4d2529c5ad5f226372",
            "boot_command": [
                "<tab> ",
                "inst.ks=http://{{ .HTTPIP }}:{{ .HTTPPort }}/centos7.ks ",
      "ssh_username": "tesla",
      "ssh_password": "tesla",
      "ssh_wait_timeout": "12000s",
            "boot_wait": "3s",
            "disk_size": "4G",
      "display": "none",
            "headless": false,
            "memory": 4096,
      "accelerator": "kvm",
      "cpus": 4,
            "http_directory": "http",
            "shutdown_timeout": "20m",
      "disk_interface": "virtio",
            "format": "qcow2",
            "net_device": "virtio-net"


"post-processors": [
            "type": "shell-local",
            "inline_shebang": "/bin/bash -e",
            "inline": [
                "TMP_DIR=$(mktemp -d /tmp/packer-maas-XXXX)",
                "echo 'Mounting image...'",
                "modprobe nbd",
                "qemu-nbd -d /dev/nbd4",
                "qemu-nbd -c /dev/nbd4 -n output-qemu/packer-qemu",
                "echo 'Waiting for partitions to be created...'",
                "while [ ! -e /dev/nbd4p1 -a $tries -lt 60 ]; do",
                "    sleep 1",
                "    tries=$((tries+1))",
                "echo 'Tarring up image...'",
                "mount /dev/nbd4p1 $TMP_DIR",
                "tar -Sczpf centos7.tar.gz --selinux -C $TMP_DIR .",
                "echo 'Unmounting image...'",
                "umount $TMP_DIR",
                "qemu-nbd -d /dev/nbd4",
                "rmdir $TMP_DIR"
    "provisioners": [
  "type": "shell",
  "pause_before": "5s",
  "inline": [
  "sudo yum -y install epel-release",
  "sudo yum -y update",
  "sudo yum -y remove cloud-init",
  "sudo yum -y install python-jsonschema python-devel",
  "sudo yum -y install cloud-init --disablerepo=* --enablerepo=group_cloud-init-el-stable",
  "sudo yum -y install qemu-guest-agent wget"
      "user": "tesla",
      "type": "ansible",
      "playbook_file": "./ansible/main.yml"
     "type": "shell",
     "inline": [
  "sudo systemctl enable cloud-init",
  "sudo rm -rf /var/lib/cloud/",
  "sudo rm -rf /etc/cloud/cloud-init.disabled",
  "sudo /usr/bin/truncate -s 0 /etc/fstab",
  "sudo /usr/bin/truncate -s 0 /etc/resolv.conf",
        "sudo rm -f /etc/sysconfig/network-scripts/ifcfg-[^lo]*",
        "sudo sync"

- hosts: default
  become: yes
    - configure_httpd
    - configure_chrony


url --mirrorlist=""
firewall --enabled --service=ssh,http
firstboot --disable
ignoredisk --only-use=vda
lang en_US.UTF-8
keyboard us
network --bootproto=dhcp
selinux --enforcing
timezone UTC --isUtc
bootloader --location=mbr --driveorder="vda" --timeout=1
rootpw --plaintext root1234
user --name=tesla --groups=wheel --plaintext --password=tesla

repo --name="Base" --mirrorlist=""
repo --name="Updates" --mirrorlist=""
repo --name="Extras" --mirrorlist=""
repo --name="cloud-init" --baseurl=""

clearpart --all --initlabel
part / --size=1 --grow --asprimary --fstype=ext4

# cloud-init only requires python-oauthlib with MAAS. As such upstream
# has removed python-oauthlib from cloud-init's deps.
# bridge-utils is required by cloud-init to configure networking. Without it
# installed cloud-init will try to install it itself which will not work in
# isolated environments.
# Tools needed to allow custom storage to be deployed without acessing the
# Internet.
# Older versions of Curtin do not support secure boot and setup grub by
# generating grubx64.efi with grub2-efi-x64-modules.
# Remove ALSA firmware
# Remove Intel wireless firmware

%post --erroronfail
systemctl disable cloud-init
touch /etc/cloud/cloud-init.disabled
yum install -y sudo
echo "tesla        ALL=(ALL)       NOPASSWD: ALL" >> /etc/sudoers.d/tesla
sed -i "s/^.*requiretty/#Defaults requiretty/" /etc/sudoers
yum clean all

Validating Packer config.

sudo PACKER_LOG=1 packer validate centos7.json

After successful validation, we can start building machine image.

sudo PACKER_LOG=1 packer build centos7.json

Once packer finished successfully, centos7.tar.gz file should be created, which will be uploaded to MaaS to provision VMs or Install OS in Bare-Metal servers.

Uploading image to MaaS.

maas $PROFILE boot-resources create name="centos/centos7Packer1" architecture=amd64/generic content@=centos7.tar.gz

Important Takeaways on Packer.

Do not forget to issue sync command inside machine provisioned by Packer. Otherwise, you will experience with the empty systemd unit files. So, sync command will flush data from cache to disk.

As MaaS uses cloud-init to setup various host settings such as partitioning disk, dns server, network configuration etc. Some of the files are removed or truncated prior to image creation. Because MaaS will populate these configurations during the first boot of the system. If you deploy the OS via MaaS, configure dns setting on the MaaS, it will be anyway overwritten by cloud-init even, you configure resolv.conf inside the image.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.