Getting Started with cloud-init

cloud-init is an awesome technology that can be used to customize Linux images for deployment, that lets you do all kinds of neat things such as automatically creating users, installing packages, resetting SSH keys, and more. However, it’s often shrouded in mystery. In this video, I’ll walk you through using it to create a user, set the hostname, and install some packages.

YouTube player

Note: Ubuntu 20.04 is the example Linux distribution used in this video. While most (if not all) of the concepts should work in other distributions, cross-distro compatibility cannot be guaranteed.

To get started, we first need to see whether or not cloud-init is already installed:

dpkg --get-selections | grep cloud-init

If it’s not installed, install it:

sudo apt update
sudo apt install cloud-init

If you already have cloud-init installed, you can purge it and then reinstall it with the following commands:

sudo apt remove --purge cloud-init
sudo apt install cloud-init

Once that package is installed, we will have the /etc/cloud directory on our system with some defaults in it:

cd /etc/cloud

The file that we’re most concerned with is the cloud.cfg file. Let’s make a backup of it:

sudo cp cloud.cfg cloud.cfg.bak

Next, open the file in an editor. You can use whatever editor you want, but nano is an easy choice if you don’t have a preference. I normally use vim, in case you’re curious.

sudo nano cloud.cfg

As you can see, this file is quite large. There are several very specific things that I’ll recommend updating. You can use the following document as a guide, in case you’re curious about the modules that aren’t being modified and what they’re responsible for:

https://cloudinit.readthedocs.io/en/latest/topics/modules.html

On that site, you’ll see a list of modules. Looking at your cloud.cfg file, you’ll see that many of these modules are in use. I like to remove any of them that aren’t specific to what I wan to do, or aren’t relevant in some way.

Most of these we’re going to leave alone. If in doubt about what a line does, either check the modules list in the documentation page to see if it applies to you, or simply leave that line alone. But there’s a few I want to remove because I know for sure that they’re not needed. Specifically, byobu, chef, mcollective, puppet salt-minion, etc. I don’t use those personally, but feel free to leave those alone if they have value to you.

Now, let’s look at customizing the user. At the top, we have a users section. In this case, it’s creating the default user. For example, on ubuntu systems, this will create a user named ubuntu. Debian’s default user is just called debian. This will vary from one distribution to another. If you leave this configuration here, a default user will be created when the config is run. I don’t like this, I prefer to have a user with my name. I just comment out that particular section:

Before:

users
- default

After:

users
# – default

Scroll down a bit, because there’s an entire section dedicated to the default user. Comment out that entire section.

Before:

default_user:
name: ubuntu
lock_passwd: True
gecos: Ubuntu
groups: [adm, audio, cdrom, dialout…]
sudo: ["ALL=(ALL) NOPASSWD:ALL"]
shell: /bin/bash

After:

#default_user:
# name: ubuntu
# lock_passwd: True
# gecos: Ubuntu
# groups: [adm, audio, cdrom, dialout…]
# sudo: ["ALL=(ALL) NOPASSWD:ALL"]
# shell: /bin/bash

Save the file for now, and exit. Don’t reboot, we’ll need to add additional tweaks to the cloud.cfg file. For now, we’ll make a quick detour and generate a password hash for a user we’re going to be creating. We can use the mkpasswd command for that purpose. First, check to see if you have the mkpasswd command available:

which mkpasswd

If you don’t see any output, you may need to install the whois package, at least on Debian and Ubuntu. The name of that package might be different if you’re using a different distribution.

sudo apt install whois

With the mkpasswd command available, we can generate our password hash:

mkpasswd -m sha-512

At the prompt, type the password that you want your user to have. That will generate the hash. Copy the password hash, and we’ll return to editing our file.

sudo vim cloud.cfg

At the top of the file, we’ll add the following content:

- name: jay
lock_passwd: False
passwd: $6$/m/gYOKAVX$TMxrEeUpTmvLPkEbXpRSL8pZ1ZnFgediRjDVQrpnWuM0
gecos: Jay
ssh_authorized_keys:
- ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAVfgmEwwQo426MUYcnIP2x1/edoweERB3Ysy6vpxBzo jay
groups: [adm, audio, cdrom, dialout, dip, floppy, lxd, netdev, plugdev, sudo, video]
sudo: ["ALL=(ALL) NOPASSWD:ALL"]
shell: /bin/bash

Note: Don’t copy and paste the example password hash above into your file, instead use the password hash that you generated earlier.

Note 2: For the list of groups, make sure to check to see if those groups actually exist before you type them in. You can check the contents of /etc/group to see a list of groups on your system. Basically, just make sure you’re not referencing a group on that line that doesn’t actually exist on your system.

The next thing that I recommend you change in your file, is adding your time zone. There should already be a line that references timezone, but it won’t designate a specific timezone. You can append your time zone to the end of that line.

Before:

- timezone

After:

timezone "America/Detroit"

If don’t know what your time zone is, you can find it from this page (just choose the timezone that best matches your location).

At the end of the config file, add the following line if you’d like:

bootcmd:
- date > /etc/birth_certificate

That line will cause the current date to be placed into the /etc/birth_certificate file as soon as the instance comes online. Optional, but fun.

Perhaps more useful, we can add configuration such as the following to ensure specific packages are installed automatically when cloud-init runs:

packages:
- git
- tmux
- vim-nox

That’s it! Save the file, and exit. We have another file that we need to edit, and this one won’t actually exist yet.

sudo nano /etc/cloud/cloud.cfg.d/99-fake_cloud.cfg

The contents of this file should match the following:


# configure cloud-init for NoCloud
datasource_list: [ NoCloud, None ]
datasource:
NoCloud:
fs_label: system-boot

Save the file, and exit.

Before we run cloud-init, let’s clean it to essentially “reset” it:

sudo cloud-init clean

We can run cloud-init now, but before we do, there’s one more customization that I recommend you consider adding. We can have cloud-init automate setting your server’s hostname as well. To do that, let’s open the cloud.cfg file again:

sudo nano /etc/cloud/cloud.cfg

And add the following configuration underneath preserve_hostname:


hostname: myhostname.mydomain.com
manage_etc_hosts: true

Feel free to change the hostname to whatever you want it to be. Finally, save the file, that should be it when it comes to config files.

On my end, there’s a symbolic link that (for some reason) prevents cloud-init from running if it’s present. I’m not sure yet why this file is present, but from what I’ve seen from Googling, the file will need to be removed or otherwise it can potentially stop cloud-init from working:

sudo rm /etc/systemd/network/99-default.link

That should be it! At this point, we can reboot the server, and cloud-init should run. But rather than reboot it, we can use the following command to test it in-place so we can make sure that the config actually works:

sudo cloud-init init

If all goes well, then you should be able to clean cloud-init again, and then capture an image of your server to use as a distribution image for future deployments.