How to test cloud-init locally with Vagrant

How to test a cloud-init file locally? The most straight forward choice seems to be VirtualBox and Vagrant. I will show you how to set up the Vagrant experimental cloud_init flag to test your cloud-init files on the locally deployed VM. This way you can quickly validate the expected results without spinning up a real VM inside your cloud provider panel.

TLDR; Clone https://github.com/jmarceli/test-cloud-init-vagrant, adjust the cloud-init-test.yml file, execute the vagrant up command and check the results with vagrant ssh.

Prerequisites

It will be useful to have at least basic knowledge about the following tools:

In this tutorial I will be using Ubuntu Server Cloud Images from https://cloud-images.ubuntu.com. For the Virtualbox an appropriate image with the .box extension might be https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-amd64-vagrant.box You don't have to download it manually as this will URL will be included in your Vagrantfile and handled automatically.

Vagrant setup

Create a directory for this project somewhere in your local filesystem e.g. ~/cloud-init-tests/. Then create a Vagrantfile inside that directory. This file will contain all the required configuration:

Vagrant.configure("2") do |config|
  config.env.enable # enable .env support plugin (it will let us easily enable cloud_init support)

  # Give a custom name for a VM created by this script for a Vagrant CLI
  config.vm.define "focal-server-cloudimg-amd64-vagrant"

  # Name for the box image downloaded from the box_url
  # it will be used to create a folder inside ~/.vagrant.d/boxes to avoid re-downloading
  config.vm.box = "focal-server-cloudimg-amd64-vagrant"

  # URL used as a source for the vm.box defined above
  config.vm.box_url = "https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-amd64-vagrant.box"

  config.vm.provider "virtualbox" do |v|
    # Name visible inside your Virtualbox UI
    v.name = "cloud-init-ubuntu-test"
  end

  # cloud-init script
  config.vm.cloud_init do |cloud_init|
    # With Ubuntu cloud images you have to use cloud_init to get an access
    cloud_init.content_type = "text/cloud-config"
    cloud_init.path = "cloud-init-test.yml"
  end
end

Vagrant experimental flags

The next file which has to be created is .env. It should be placed in the same directory as your Vagrantfile. Content of the .env file should be as follows:

VAGRANT_EXPERIMENTAL="cloud_init,disks"

Together with previously mentioned line config.env.enable this will enable cloud_init and disks experimental flags.

NOTE

cloud_init will NOT work without a disks flag, so this is very important to enable both of them. What is strange this information is not currently provided on the Vagrant docs pages.

Tested cloud-init file

The last but not least, your cloud-init file which in my example is named cloud-init-test.yml. It can have any content supported by the cloud-init and in my example, it will be a yml file with the following lines:

packages:
  - python-is-python3
users:
  - name: test_user_unique_name
    groups: sudo
    homedir: /custom/home/dir
    shell: /bin/bash
    sudo: ["ALL=(ALL) NOPASSWD:ALL"]
    # ssh-authorized-keys:
    #   - ssh-rsa USE_YOUR_RSA_KEY_HERE_IF_NEEDED

Executing the test

Now it is time to test the setup. Just execute vagrant up inside the folder where you placed the Vagrantfile. If you set everything properly the following output should appear:

==> vagrant: You have requested to enabled the experimental flag with the following features:
==> vagrant:
==> vagrant: Features:  cloud_init, disks
==> vagrant:
==> vagrant: Please use with caution, as some of the features may not be fully
==> vagrant: functional yet.
Bringing machine 'focal-server-cloudimg-amd64-vagrant' up with 'virtualbox' provider...
==> focal-server-cloudimg-amd64-vagrant: Importing base box 'focal-server-cloudimg-amd64-vagrant'...

After a few seconds (maybe minutes depending on your cloud-init file) your VM should be up and running. Now you can check the results by executing vagrant ssh. In my example it is easy to verify that a user test_user_unique_name is present by executing cat /etc/passwords and that a package python-is-python3 was installed with python --version (it outputs Python 3.)

After you will finish with the examination of the provisioned VM you can safely destroy it to retrieve all resources allocated for its existence. Just execute the vagrant destroy command followed by y answer to the confirmation question.

Sources

https://www.vagrantup.com/docs/cloud-init/usage - official Vagrant docs https://www.vagrantup.com/docs/cloud-init/configuration - different formats of cloud-init supported by Vagrant https://cloudinit.readthedocs.io/en/latest/topics/modules.html - cloud-init modules list https://cloudinit.readthedocs.io/en/latest/topics/examples.html - cloud-init examples