How to: test cloud-init setup locally

So, I was about to setup my new cloud server in Hetzner when I realised my provisioning problem. How can I easily setup my environment?

Well, I have a repository containing Saltstack states but between having a machine and running my states on it there’s a long way and Hetzner doesn’t play well with the likes of Terraform. Sure, I could Ansible my way into it with a repository using my user/pass but I don’t want to keep an exposed root more than I have to.

Fortunately, Hetzner allows sending some cloud-init user data. I copy/paste some stuff I cooked up based on examples and … fail. How to debug?

Turns out it’s not exactly straightforward. Cloud-init was designed with cloud providers in mind and its “no-cloud” provider that accepts files reads them off a dedicated drive. Fortunately, there’s a way to manually run your own files locally. You can also do the following inside a vagrant box (or Docker container)

  1. Install “genisoimage” (sudo apt-get -y install genisoimage for Linux or sudo port install cdrtools for MacOS, if you dare use macports)
  2. Create a metadata file (named meta-data)

Sample contents:

instance-id: iid-0123456789abcdefg
local-hostname: ubuntu-bionic

3. You will also need a file called user-data, containing your cloud-init yaml.

3. To have a reusable method, you can setup a minimalistic Makefile (make sure there’s a tab at the beginning of the second line):

no-cloud.iso: meta-data user-data
mkisofs -joliet -rock -volid "cidata" -output nocloud.iso meta-data user-data

4. Create this Vagrantfile (I’ve tried doing this inside a Docker container as well, but failed — you need to attach the ISO as a device)

# -*- mode: ruby -*-
# vi: set ft=ruby :

IMAGE_PATH = File.join(File.dirname(__FILE__), "no-cloud.iso")

Vagrant.configure(2) do |config|
config.vm.box = "ubuntu/bionic64"

# Disable SSH password - we will use Vagrant key
config.ssh.password = nil

# Disable shared folders
config.vm.synced_folder ".", "/vagrant", disabled: true

# Tweak virtualbox
config.vm.provider :virtualbox do |vb|
# Attach no-cloud.iso to the VM
vb.customize [
"storageattach", :id,
"--storagectl", "SCSI",
"--port", "1",
"--type", "dvddrive",
"--medium", IMAGE_PATH
]

vb.linked_clone = true
end
end

When you run vagrant up, cloud-init will do its thing. Depending on how long it takes, it may still be running when you ssh into vagrant (it’s not necessarily done when vagrant says it’s up). You should look into /var/log/cloud-init.log and see what’s there. Depending on your cloud-init user-data, the VM may even restart before it’s done.

However, the logs and cloud-init’s analyze command should help figure out what’s working as expected and what’s not.