cloud-init example being edited by a salmon

Why cloud-init?

Last year I deployed thirty physical servers for use in an Openstack cluster, using Canonical’s MAAS. This was my first exposure to a technology that has been around for years now, cloud-init.

It’s not that I hadn’t heard the name before, I even had a vague idea about what it could do, but I had never used it and had no idea exactly how powerful it could be and how much time it could save on repeatable tasks.

Below I’ve included a few examples of scripts I have used to make my life easy.

Example 1 – Setup Extra Users & Install Packages

#cloud-config
---
ssh_pwauth: false
users:
-   gecos: additional-user
    groups: adm,cdrom,dip,lxd,plugdev,sudo
    lock_passwd: false
    sudo: ['ALL=(ALL) NOPASSWD:ALL']
    name: additional-user
    passwd: $6$kahwie4Faiyeiloh$m0N8nQF91Nf5a6P7mUSRtmf2iQLgrjyNAtlUv2nEye2HCsYKiq37fK4OCfJ4s.6yRJei9xj4xy3uOTePNpTx90
    shell: /bin/bash
    ssh-authorized-keys:
      - ssh-rsa YourKeyHere 
-   gecos: ansible
    groups: adm,cdrom,dip,lxd,plugdev,sudo
    lock_passwd: false
    sudo: ['ALL=(ALL) NOPASSWD:ALL']
    name: ansible
    passwd: $6$aiQui1aeloo4che0$atHuXsRnrVcVXnmyC0UwrVwKnSkxRovIq/ehmEajILX4EzfSjH6Sa8HR0OmcCuQ/sNk1cgItRoB1oiAlVVLTE1
    shell: /bin/bash
    ssh-authorized-keys:
      - ssh-rsa YourKeyHere

package_upgrade: true
packages:
  - ca-certificates
  - snmpd
  - unzip
  - net-tools
  - git
  - make
  - gcc

...

So what exactly are we doing?

  • Disable password auth for SSH
  • Create two new users, additional-user and ansible
    • Both users have passwordless sudo
    • Set authorized_keys for both
  • Upgrade all packages on first boot
  • Install the additional listed packages

As an aside, the passwords included in a cloud-init document are generated as follows

mkpasswd -m sha-512

Example 2 – CA Certificates & bash Scripts

#cloud-config
---
package_upgrade: true
packages:
  - ca-certificates

ca_certs:
  trusted:
  - |
   -----BEGIN CERTIFICATE-----
   YourCertificateHere
   -----END CERTIFICATE-----

runcmd:
 - [ wget, "https://example.com/scripts/script.sh" ]
 - [ chmod, +x, script.sh ]
 - [ /bin/bash, script.sh ]
...

In this example we:

  • Upgrade all packages on boot
  • Install the ca-certificates package
  • Add a local CA as trusted
  • Download a bash script and run it

This is a great example, as the runcmd module allows us to do pretty much anything that is not part of an existing cloud-init module

Example 3 – Early Boot Commands, Write Files and Advanced Run Commands

#cloud-config
---
bootcmd:
 - [ install, -m, "0755", -d, /etc/apt/keyrings ]
 - [ curl, -fsSL, "https://download.docker.com/linux/ubuntu/gpg", -o, /etc/apt/keyrings/docker.asc ]
 - [ chmod, a+r, /etc/apt/keyrings/docker.asc ]
 - [ sh, -c, 'echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null' ]
 - [ wget, -q, "https://packages.microsoft.com/config/ubuntu/22.04/packages-microsoft-prod.deb", -O, /tmp/microsoft.deb ]
 - [ dpkg, -i, /tmp/microsoft.deb ]

package_upgrade: true
packages:
  - wget
  - jq
  - apt-transport-https
  - software-properties-common
  - powershell
  - docker-ce
  - docker-ce-cli
  - containerd.io
  - docker-buildx-plugin
  - docker-compose-plugin

write_files:
  - path: /root/.bashrc
    content: |
      export OS_AUTH_URL=https://openstack.example.com
      export OS_USER_DOMAIN_NAME="default"
      export OS_PROJECT_DOMAIN_ID="projectdomainid"
      export OS_USERNAME="admin-user"
      export OS_PASSWORD="Password"
      export OS_REGION_NAME="RegionOne"
      export OS_INTERFACE=public
      export OS_IDENTITY_API_VERSION=3
      export OS_DOMAIN_NAME="default"
    append: true
disable_root: false
users:
-   name: root
    lock_passwd: false
    name: root
    passwd: $6$Nv.R3ddl9EkAOJY2$9654rBbm9/gvbIxy0nXJdHkndFFKNbqTGfhIubMP2RswSzQ/GqNpgvhhBmNM8tXkFQabCjtEQsvVFnKq1Z4Gl0
    shell: /bin/bash
    ssh-authorized-keys:
      - ssh-rsa authorizedkey1
      - ssh-rsa authorizedkey2

runcmd:
 - [ cd, /usr/lib64 ]
 - [ wget, "https://example.com/downloads/VMware-vix-disklib.tar.gz" ]
 - [ tar, xzvf, VMware-vix-disklib.tar.gz ]
 - [ mv, vmware-vix-disklib-distrib/, vmware-vix-disklib/ ]
 - [ rm, VMware-vix-disklib.tar.gz ]
 - [ mkdir, -p, /mnt/config ]
 - [ mount, /dev/sr0, /mnt/config ]
 - [ sh, -c, 'projid=$(cat /mnt/config/openstack/latest/meta_data.json | jq -r .project_id) && echo "export OS_PROJECT_ID=${projid}" >> /root/.bashrc' ]
 - [ umount, /mnt/config ]
 - [ docker, pull, registry.atmosphere.dev/library/migratekit ]
 - [ pwsh, -Command, "Set-PSRepository -Name PSGallery -InstallationPolicy Trusted" ]
 - [ pwsh, -Command, "$ProgressPreference = 'Continue'; Write-Host 'Installing VMware.PowerCLI module - this may take several minutes...'; Install-Module -Name VMware.PowerCLI -Force -Scope AllUsers -Verbose" ]
 - [ rm, -f, /tmp/microsoft.deb ]
 - [ reboot ]
...

This is a very specific example to show the power of what cloud-init can achieve. The above script I used to set up migration environments to move VMs from VMWare to Openstack.

It does the following:

  • Early in the boot process
    • Adds the Docker and Microsoft repos
  • Updates all packages
  • Installs listed packages, including docker and powershell
  • Writes Openstack details to bashrc for root
  • Sets the root user to enabled, sets it’s password and authorized keys
  • Downloads the VMWare virtual disk development kit and unpacks it
  • Mounts the Openstack ConfigDrive and retrieves the parent projects ID
  • Adds said ID to bashrc as well
  • Pulls Vexxhost’s MigrateKit docker container
  • Installs PowerCLI
  • Reboots the machine

I had to set up quite a few of these VMs and using a cloud-init script like the above saved me a TON of time.

I want more what now?

You can check out the cloud-init documentation for dozens more examples, and a complete list of all included modules. There is so much more than I covered here and you could be reading for days

Alternatively for those so inclined, I have found asking LLMs to write these for you can have pretty good results as well.