Generate custom VM images using packer and ansible
This HowTo explains how to use packer and ansible to generate easily multiple VM images to ease the installation of many VMs.
Packer
When you want to deploy many VMs and you don't want to lose time installing each VM manually from the ISO installation image, you can use Packer to generate an custom image before hand. Then, you will be able to boot directly on this image instead of the regular ISO installation image. This can save you a lot of time.
Packer needs a template to build the image. This template is in JSON format and details everything you want to build inside your VM image. At the core of this template, there will be the ISO installation image you want to build, and then you can specify many things to do the installation automatically with al lthe parameters you chose. The documentation for packer template can be found here: https://www.packer.io/docs/templates/.
Let's look at an example of template file (this file is our debian10.json template):
{
"variables": {
"user": "slapos",
"password": "slapos",
"domain": "",
"disk_size": "100",
"name": "image",
"custom_script": "scripts/empty.sh"
},
"builders":
[
{
"name": "debian10-{{ user `disk_size`}}G-{{ user `name`}}",
"type": "qemu",
"format": "qcow2",
"accelerator": "kvm",
"disk_size": "{{ user `disk_size`}}000",
"iso_url": "https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/debian-10.2.0-amd64-netinst.iso",
"iso_checksum": "36de671429939e90f2a31ce3fbed0aaf",
"iso_checksum_type": "md5",
"http_directory": "http",
"ssh_username": "{{user `user`}}",
"ssh_password": "{{user `password`}}",
"ssh_wait_timeout": "1800s",
"shutdown_command": "echo '{{user `password`}}'|sudo -S shutdown -h now",
"headless": true,
"boot_wait": "2s",
"boot_command": [
"",
"auto preseed/url=http://{{ .HTTPIP }}:{{ .HTTPPort }}/preseed-debian10.cfg ",
"debian-installer=en_US.UTF-8 locale=en_US keymap=fr ",
"netcfg/get_hostname={{ .Name }} ",
"netcfg/get_domain={{ user `domain`}} ",
"fb=false debconf/frontend=noninteractive ",
"passwd/user-fullname={{user `user`}} ",
"passwd/user-password={{user `password`}} ",
"passwd/user-password-again={{user `password`}} ",
"passwd/username={{user `user`}} ",
""
]
}
],
"provisioners": [
{
"type": "shell",
"execute_command": "echo '{{user `password`}}' | {{.Vars}} sudo -E -S bash '{{.Path}}'",
"scripts": [
"scripts/update.sh",
"scripts/packages.sh",
"scripts/network-debian.sh",
"scripts/cleanup.sh",
"{{ user `custom_script` }}"
]
}
]
}
The "builder
" section is the most important and the only one mandatory. There are also 2 other sections:
- "
variables
" which defines variables we use in other sections. It can be useful to do some minor variations of your images
- "
provisionners
" which is the step executed after the installation itself and let you execute some command inside the newly installed system via ssh. This step will let us customize everything we want inside the image (user, packages, etc.)
Please note that the file preseed.cfg
needs to be located inside a directory named http
. This http directory needs to be alongside the template. For example, look at our http directory at https://lab.nexedi.com/nexedi/slapos.package/tree/master/packer/http. For more information about the preseed file see: https://wiki.debian.org/fr/DebianInstaller/Preseed
When you wrote your template (like the one above), you can make sure it syntaxically correct using the command packer validate debian10.json
Once the file is validated, you can use the command packer build debian10.json to build the image.
Please note that packer supports any operating systems. You just need to look how to automatically install the OS of your choice and then configure a template for it. Of course, there are a lot of available template examples (see "Templates" chapter in https://www.packer.io/community-tools.html).
Automate the building of images with Ansible
If you need to build many different images, you can use Ansible to generate them all instead of generating them one by one. For example, you may want to build images for several distributions (Ubuntu, CentOS, Debian) or with several disk sizes (50G, 100G, 200G, ...).
Building each image manually with the command "packer build" can be tedious. You can simply create an Ansible playbook to generate them all.
Here is an example of Ansible task to create an image with packer:
- shell: PATH=$PATH:/opt/packer/ packer build debian10.json >> log/debian10.log
args:
creates: output-debian10
ignore_errors: True
As you can see this task will simply call packer and save the output image in output-debian10 directory.
Then if you want to compress the image (since RapidSpace VM service can boot from a compressed image) to save space on your disk:
- shell: gzip output-debian10/packer-debian10
args:
creates: output-debian10/packer-debian10.gz
ignore_errors: True
And you can also imagine to create a task to upload this image to a web server to be used by RapidSpace VM.
If you want to see a real example of playbook we are using at nexedi to generate our images, you can have a look at https://lab.nexedi.com/nexedi/slapos.package/blob/master/packer/build-vm-bootstrap.yml.
If your playbook is named build.yml
, you can run it with this simple command:
ansible-playbook build.yml -i localhost