Preface

If you read my 'about me' page before, you might now I really like to play around with all kinds of different technologies. In my opinion, the best way to play around with these things is by using Virtual Machines. Personally I use Proxmox on my Server, to create (temporary??) VM's. The only downside is that installing multiple VMs using a ISO, is a pretty boring practice.

Okay, we've all been there. Installing VM's, because we're experimenting with Linux (or Windows for the matter). This is fun and using the installer is also a good practice if you're learning Linux. E.g. using Kickstart files (post coming up! ;)) is a great way to 'kickstart' a new RHEL installation (pun intended.), or just run through the installer to learn about it.

However: what if you want to experiment with something like a High Available WebApp setup? Something like this isn't really Linux focussed, but more Application focussed. Knowing Linux is cool, but running through 6 installations is really boring.

Cloud Images

Boom. Cloud Images. Major Linux distributions nowadays include Cloud Images. But what exactly are these? Imagine this: working for a Hosting company, setting up multiple servers a week. Running through a installer is a tedious job. Why? Because all of those installation share the same steps. Yes, all VMs have a root account, network access, need updated packages etc.

Cloud Images are minimal and customizable Linux Distribution. They are designed for Cloud Deployments (or fast) and are mostly used at Cloud Providers, or even Hosting companies. Because they're customizable, you're able to set things like passwords, SSH keys, networking, packages and many more while the VM is starting for the first time. After the VM has been started, it is basically customized to your needs and preferences. Cool, right?

Some distributions and their examples:

https://cloud-images.ubuntu.com/

https://wiki.almalinux.org/cloud/Generic-cloud.html

https://wiki.rockylinux.org/rocky/image/

Using Packer, you can setup your own Cloud Images.

Cloud-Init

Cloud-Init is the technology what we use to automate installations. Cloud-init works with so called user and meta data. The metadata is used for 'general' settings on the server. Think about setting up networking, or updating and installing packages. The user data, you might already guessed it, sets up all the user-related stuff. Think about: setting password(s), adding SSH keys, creating and adding groups... and so forth.

The way Cloud-init works, is that you've to add a Cloud-Init drive to the server/VM. At some point of starting the server, it'll check if there is a Cloud-init drive available. If there is, it executes all it's commands. Those commands are configured through yaml files; one for meta, one for user data.

Proxmox and Cloud-init

For those who already have some experience with Proxmox, might have noticed the 'Cloud-init' entry in the menu for each VM. While typically greyed out, it gets enabled if there is a Cloud-init enabled VM or template available. Proxmox itself has a really nice integration with Cloud-Init, and offers to set the general 'stuff' within its GUI.

However; this is not all. While creating a Cloud-Init template, we're also able to inject it directly with our Cloud-init ISO (drive). In this ISO, we can give the VM certain instructions. Personally I like to set up a Ansible user, so I can run my Linux base Ansible role.

In short: using Cloud-Init, I'm able to setup IP-addresses, a Ansible service user and some other small configurations. When the VM is done, Ansible is immediately ready to do its own thing. This way I have a VM running within 5-7 minutes, instead of the earlier 20 ๐Ÿ˜€

Creating a Cloud-init ready VM template

To setup a Cloud-init ready VM template in Proxmox, requires some steps. In my honest opinion it's too much to do everything, every time. Thats why I created a Python script to automates this process. This script uses a settings file in YAML, where you can set up the VM as you want. Sounds cool right? Check out the repository below.

Repository: https://github.com/Larse99/ProxmoxTemplater

Setting up the environment

First of all, we have to clone the repository. Because this uses Proxmox as a dependency, we have to run the script directly on the Proxmox node. After that we have to set up a Python3 virtual environment, this is necessary so we can install all the needed dependencies within a virtual environment.

1. Clone the repository

git clone https://github.com/Larse99/ProxmoxTemplater

2. Setup a Python3 Virtual Environment (venv) and installing the dependencies

# a. This creates a virtual environment called 'venv'.
python3 -m venv venv

# b. Activate the new environment
source venv/bin/activate

# c. Install the dependencies/requirements
python3 -m pip install -r requirements.txt

# d, To deactivate later, just type
deactivate

Creating your first image

1. Download a compatible cloud image

In order to create a VM, we'll have to download a cloud-image first. In this example we'll use Ubuntu, but this should be compatible with all Cloud-init enabled images and distros.

curl -O https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img

2. Set up the settings.yaml

The settings.yaml is used for all the Proxmox specific configuration. This includes things like setting up the resources, but also customizing Cloud-Init items that are available via the Proxmox GUI. Therefore it is possible to setup SSH keys (root), memory, CPU, disk, network settings and more. Take a look at the example below.

# Global settings - These are global settings and will be expanded in the future.
global_settings:
  qm_bin: "/usr/sbin/qm"

# Template settings - These are the template related settings: how should the template look like?
template_settings:
  # VM settings
  vm_id: "9000" # VM ID
  vm_name: "Ubuntu-2404-NobleNumbat" # Name of the to-be-template
  vm_inject: "cloudConfigs/ubuntu2404.yaml" # The path to your Cloud Init 'injection' file (user/meta data yaml)
  vm_image: "images/noble-server-cloudimg-amd64.img" # The path to your downloaded image
  vm_user: "root" # User of the VM. Default: root
  vm_user_ssh_key: "ssh.key" # SSH key to place under 'vm_user'.
  vm_agent: "1" # -> Qemu guest agent. Make sure this is installed within the VM as well.

  # Networking
  vm_bridge: "vmbr1" # Network adapter
  vm_tag: "30" # VLAN tag. If no VLAN tag is needed, you can leave this empty

  # VM Resources
  vm_cores: "2"
  vm_memory: "1024"
  vm_size: "10G"

  # Proxmox settings
  storage_pool: "local"

I think most of the settings should be self explanatory, but some noticable values are:

  • vm_inject: The templater takes a cloud-init configuration file as well. Where you can define things like updating/installing packages etc. A example will be below
  • vm_user: This is the main user of the VM. This is also the user where the key wil be added. Leaving this as root is fine.
  • vm_user_ssh_key: The key file, mentioned above.
  • storage_pool: Where should the template live? For me, it's default 'local'. This can be changed to e.g. "VMStorage".

3. Set up SSH key(s)

For this, you just need to create a ssh.key file. You can call this anything you want, aslong the path in the settings.yaml is correct. The content of the file should look like below, if you need more keys you can just pate them on seperate lines.

ecdsa-sha2-nistp256 AAAAE2VjZ .... foo@bar.local
ecdsa-sha2-nistp256 AAAAE2VjZHNh ... laptop@apparaat

4. Set up the Cloud-init configuration file ('vm_inject')

The best way to get started, is to read the documentation about the desired distribution you want to use. For example, the documentation I like to use is the one below.

https://cloudinit.readthedocs.io/

You can change the values to whatever you want and these should translate to every OS you want to use. A example of a configuration file I use for Ubuntu, is below. This configuration sets up a Ansible service account as well as installing some packages.

#cloud-config

# User Management
users:
    - name: ansible
      gecos: Ansible User
      groups: wheel
      sudo: ALL=(ALL) NOPASSWD:ALL
      shell: /bin/bash
      lock_passwd: true
      ssh_authorized_keys:
        - "ecdsa-sha2-nistp256 AAAAE2VjZHN..... ansible@srv.local"

# Run commands
runcmd:
  - [ sh, -c, echo "You can run commands like this :)" ]
  - reboot

# Install base packages
package_update: true
packages:
  - qemu-guest-agent
  - pwgen
  - curl
  - htop
  - vim
  - net-tools
  - lldpad
  - ncdu
  - git
  - btop

5. Creating it!

Everything setup? cool! Let's create our image. You can create the image by running the command below.

# Activate the virtual environment, if not done yet.
source venv/bin/activate

# Run the command, where settings.yaml is your settings file.
python3 main.py -c settings.yaml

Output:

โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Welcome โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
โ”‚                                                                             โ”‚
โ”‚  ProxmoxTemplater v2.1                                                      โ”‚
โ”‚  (C) 2024 Lars Eissink / Larse99                                            โ”‚
โ”‚                                                                             โ”‚
โ”‚  GitHub: https://github.com/Larse99/ProxmoxTemplater/                       โ”‚
โ”‚  Guide: https://github.com/Larse99/ProxmoxTemplater//blob/main/README.md    โ”‚
โ”‚                                                                             โ”‚
โ”‚  Thanks for using the ProxmoxTemplater! Let's build some amazing images :)  โ”‚
โ”‚                                                                             โ”‚
โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ ProxmoxTemplater โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ

[WARNING] This script doesn't factcheck your settings yaml configuration. Make sure everything has been setup correctly!
[INFO] Initializing...
[INFO] Running prerequisites check
[OK] Settings
[OK] /usr/sbin/qm
[OK] images/noble-server-cloudimg-amd64.img
[OK] cloudConfigs/ubuntu2404.yaml
[OK] ssh.key
[OK] /var/lib/vz/snippets exists
[OK] Prerequisites check completed.

[INFO] Using settings:
       Template ID: 9008
       Template Name: Ubuntu-2404-NobleNumbat
       Template Storage: local
       Cloud-Init Settings: cloudConfigs/ubuntu2404.yaml
       Default user: root
       SSH Key File: ssh.key

       Base image: images/noble-server-cloudimg-amd64.img
       Image Size: 10G

       Using debug: False
[OK] Image Resized
[OK] Temporary VM created
[OK] Imported disk
[ERROR] Use of uninitialized value $dev in hash element at /usr/share/perl5/PVE/QemuServer/Drive.pm line 555.
[INFO] Injecting Base Cloud-init data
[OK] Setting up CloudInit
[OK] Setting Bootorder
[INFO] Converting VM to Template
[!! SUCCESS !!] Template created successfully!

Your first image has been created! Awesome! Now let's head to Proxmox and clone your Template to create your first VM. See how you are able to use the cloud-init menu item? Cool, right?!

Conclusion

The script in this writeup helped me enormously with setting up new VMs with ease. Whenever I want to try something with multiple VM's, I'm able to set them up in no time. Being able to create a Ansible service account and setting up networking, makes it possible for me to integrate this flawlessy into my Ansible workflow. Therefore I'm able to spin up a new VM in about 5 minutes!

With my Terraform and Ansible integration (writeup soonโ„ข๏ธ) I can automate this process too!