Tiernan's Comms Closet

Geek, Programmer, Photographer, network egineer…

Currently Viewing Posts Tagged kubernetes

Building Cloud Images for Proxmox: 2026 Update

An update to my Proxmox cloud image post for 2026: Ubuntu 26.04, Debian 13 and AlmaLinux 10 (which needs an x86-64-v3 CPU type), a fix for clones sharing a machine ID, and a dhcpcd correction for Proxmox 9. Drafted by Claude, reviewed by me.

This post was drafted by Claude and reviewed by me. Claude checked the commands, the image links, and the libguestfs, systemd and Proxmox source code behind the machine ID fix; I checked it against my own setup.

Back in 2023 I wrote about building cloud images for Proxmox, and updated it in 2025 for Ubuntu 24.04. It’s time for another round: Ubuntu 26.04 is out, Proxmox 9 changed a few things, and I’ve finally worked out the machine ID problem from the bottom of the old post. This time I’ve added Debian 13 and AlmaLinux 10 too.

What’s changed since the old post

  • New images: Ubuntu 26.04 LTS (Resolute Raccoon), Debian 13 (Trixie) and AlmaLinux 10.
  • AlmaLinux 10 needs a newer CPU type. Like RHEL 10, it’s built for the x86-64-v3 level (AVX2). On Proxmox’s default CPU types it kernel-panics at boot, so the template needs --cpu x86-64-v3 or --cpu host. If your CPU has no AVX2 (some Pentiums and Celerons), Alma publishes an x86_64_v2 build as well.
  • Correction: install dhcpcd-base, not dhcpcd. The old note for Proxmox 9 said dhcpcd. The full package also runs a DHCP client service on the host, which grabs leases on bridges and guest firewall interfaces. One of my nodes ended up with three default routes because of it.
  • The machine ID problem is fixed in the image, with no need to boot each VM and patch it. The details are below, including why it happened.
  • Updated template commands: an explicit CPU type, import-from instead of qm importdisk, --boot order=scsi0 instead of the deprecated boot options, and DHCP set on the template.

On the Proxmox host

You need libguestfs-tools. On Proxmox 9 (Debian 13 underneath) you also need a DHCP client binary: virt-customize boots a small appliance to install packages, and without one it fails with dhcpcd: command not found and has no network.

apt update
apt install libguestfs-tools dhcpcd-base

If you followed my old note and installed dhcpcd, check ip route. If there’s more than one default route, fix it (with a console handy):

systemctl disable --now dhcpcd
apt remove dhcpcd        # keeps dhcpcd-base
ip route

Stopping it doesn’t remove the addresses and routes it added. Delete them with ip addr del, or reboot.

Pick a distro

Set these once; the rest of the commands are the same for all three.

Your storage name will be different. local-zfs is what my nodes use (the default on a ZFS install). Yours might be local-lvm or a name you picked: pvesm status on the host lists them, as does Datacenter → Storage in the web UI.

Ubuntu 26.04 LTS

VMID=9000
NAME=ubuntu-2604-cloudinit-template
IMG=resolute-server-cloudimg-amd64.img
wget https://cloud-images.ubuntu.com/resolute/current/$IMG

Debian 13

VMID=9001
NAME=debian-13-cloudinit-template
IMG=debian-13-genericcloud-amd64.qcow2
wget https://cloud.debian.org/images/cloud/trixie/latest/$IMG

AlmaLinux 10

VMID=9002
NAME=almalinux-10-cloudinit-template
IMG=AlmaLinux-10-GenericCloud-latest.x86_64.qcow2
wget https://repo.almalinux.org/almalinux/10/cloud/x86_64/images/$IMG

For the v2 build, replace x86_64 with x86_64_v2 in both the folder and the file name.

All three:

STORAGE=local-zfs                  # change to your storage
PROXY=http://10.244.71.183:3142    # change to your proxy's IP and port

Customise the image

I point apt at my apt-cacher-ng proxy and install the QEMU guest agent. If you run a proxy, set PROXY to your own proxy’s IP and port: 10.244.71.183 is mine and won’t exist on your network. If you don’t run one, drop the --append-line.

Ubuntu and Debian:

virt-customize -a $IMG \
  --install qemu-guest-agent \
  --append-line "/etc/apt/apt.conf.d/00proxy:Acquire::http { Proxy \"$PROXY\"; };"

AlmaLinux:

virt-customize -a $IMG \
  --install qemu-guest-agent \
  --selinux-relabel

--selinux-relabel matters on Alma: without it, files changed by virt-customize have no SELinux labels and services can fail to start. I skip the proxy here: Alma’s mirrors are HTTPS, and apt-cacher-ng only caches plain HTTP.

Sysprep and reset the machine ID

virt-sysprep -a $IMG
virt-customize -a $IMG \
  --truncate /etc/machine-id \
  --run-command 'rm -f /var/lib/dbus/machine-id && ln -s /etc/machine-id /var/lib/dbus/machine-id'
virt-cat -a $IMG /etc/machine-id | wc -c    # should print 0

Why clones were sharing an IP. systemd-networkd builds its DHCP client ID from /etc/machine-id. Two VMs with the same machine ID look like the same client to the DHCP server, so they get the same address, even with different MAC addresses.

When systemd boots and finds /etc/machine-id empty, it picks a new ID in this order:

  1. /var/lib/dbus/machine-id, if it’s a regular file with an ID in it.
  2. On KVM, the VM’s SMBIOS UUID.
  3. A random ID.

Proxmox gives every clone a new SMBIOS UUID, so a template with an empty machine ID produces a unique ID in every clone, on first boot.

virt-sysprep already empties both files. So if clones still share an ID, one of two things happened:

  • The template was booted before qm template. One boot is enough to write an ID, and every clone inherits it.
  • Something ran after virt-sysprep. Package installs can create a machine ID, so do sysprep and the reset last.

The virt-customize line above is my old manual fix, applied to the image instead of each VM. If you did boot the template, run cloud-init clean --logs --machine-id before shutting it down.

Create the template

qm create $VMID --name $NAME --ostype l26 --memory 4096 --cores 2 \
  --cpu x86-64-v2-AES --net0 virtio,bridge=vmbr0,tag=72
qm set $VMID --scsihw virtio-scsi-single \
  --scsi0 $STORAGE:0,import-from=$PWD/$IMG,discard=on,ssd=1,iothread=1
qm set $VMID --boot order=scsi0
qm disk resize $VMID scsi0 +50G
qm set $VMID --ide2 $STORAGE:cloudinit
qm set $VMID --serial0 socket --vga serial0
qm set $VMID --agent enabled=1
qm set $VMID --ipconfig0 ip=dhcp
qm template $VMID

For AlmaLinux 10, use --cpu x86-64-v3 (or host).

  • --cpu: without it, qm create uses kvm64, a Pentium 4-level CPU. x86-64-v2-AES works on any reasonably modern host and keeps live migration working. host is fastest, but migrating to a node with a different CPU can fail.
  • import-from: replaces qm importdisk plus qm set --scsi0. It needs an absolute path, hence $PWD.
  • virtio-scsi-single with iothread=1: gives each disk its own I/O thread. discard=on,ssd=1 passes TRIM through to thin storage.
  • VLAN 72 is my Kubernetes VLAN; remove ,tag=72 if you don’t need it.
  • Disk size: the cloud images are only 2-3 GB, so add space before templating.

Clone it

qm clone 9000 2001 --name k8s-01 --full
qm set 2001 --sshkey ~/godboxv3.pub --ciuser tiernano --memory 4096
qm start 2001

Change the IDs (9000 is the template, 2001 the new VM), the VM name (k8s-01), the username (tiernano), the SSH public key path (~/godboxv3.pub) and the memory to your own.

For a fixed IP, add --ipconfig0 to the qm set line, not to qm clone: qm clone only makes the copy, and the cloud-init settings (--ipconfig0, --sshkey, --ciuser) all belong to qm set. Set it before the first qm start. It replaces the template’s ip=dhcp for that VM only:

qm set 2001 --sshkey ~/godboxv3.pub --ciuser tiernano --memory 4096 \
  --ipconfig0 ip=10.244.72.21/24,gw=10.244.72.1

Use an address, prefix and gateway from your own network.

--full makes an independent copy. Without it you get a linked clone, which is quicker and smaller, but the template can’t be deleted while linked clones exist.

Check the machine ID on the clones

cat /etc/machine-id
journalctl -b | grep -i 'machine id'

Every clone should show a different ID, and the log should say Initializing machine ID from SMBIOS/DMI UUID. If two match, the template has an ID baked in: rebuild it from a fresh image.

Building Cloud Images for Proxmox

To create an Ubuntu VM for a Kubernetes cluster using Proxmox, follow these steps: download and tweak the base image, sysprep it, create a template with specified configurations, and clone the VM. Adjust settings such as memory, storage, and IP configurations. Fix shared IP issues by resetting the machine ID.

[Updated] Updating this for 2025, including Noble as the image (24.04) and adding some other missing bits.

I needed to create a few Ubuntu VMs for a Kubernetes cluster for testing, and I wanted to make the process as simple as possible using Proxmox and some minimal automation. Here’s what I’ve done:

First, download the base image:

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

To be able to run the commands below, you need to install libguestfs-tools on Proxmox. An apt update and apt install libguestfs-tools is all that is needed.

Then, tweak the image. Since I’m using my apt-cacher-ng proxy here, I’ve set the proxy for all VMs. You can remove it or adjust it as needed. If you want to remove it, simply remove the append-line option. Additionally, I’m installing qemu-guest-agent here. You can add any additional items at this point if desired.

virt-customize -a noble-server-cloudimg-amd64.img --install qemu-guest-agent --append-line '/etc/apt/apt.conf.d/00proxy:Acquire::http { Proxy "http://10.244.71.182:3142"; };'

That command does the following:

– installs the QEMU Guest Agent
– adds the proxy script to apt config

NOTE: If you are running Proxmox 9, you will need to install dhcpcd first. So, apt update and apt install dhcpcd, then run the above commands.

Sysprepping the image resets it to the default state. If you don’t perform this step, and you clone the machine multiple times, all the clones will have the same machine ID and IP address. [Note: This isn’t working fully for me. See below for the changes I made to the machine ID.]

virt-sysprep -a noble-server-cloudimg-amd64.img

Create the template. I used ID 9000 and assigned a name. You can modify this. Additionally, I’ve tagged mine with VLAN 72 (my Kubernetes VLAN). Feel free to change or remove this tag as needed. Furthermore, I set the disk size to add 50GB. Please replace any references to “godboxv2-tank” with your storage name.

qm create 9000 --name "ubuntu-2404-cloudinit-template" --memory 4096 --cores 2 --net0 virtio,bridge=vmbr0,tag=72
qm importdisk 9000 noble-server-cloudimg-amd64.img godboxv2-tank
qm set 9000 --scsihw virtio-scsi-pci --scsi0 godboxv2-tank:vm-9000-disk-0
qm set 9000 --boot c --bootdisk scsi0
qm disk resize 9000 scsi0 +50G
qm set 9000 --ide2 godboxv2-tank:cloudinit
qm set 9000 --serial0 socket --vga serial0
qm set 9000 --agent enabled=1
qm template 9000

This does the following:

– creates a new VM with id 9000 and sets the name, memory and core count. It also sets the network card, and in my case the VLAN (remove ,tag=72 if you do not need a VLAN)
– imports the disk to the VM and puts it in the storage path. You will need to change your path here.
– sets the VM to use that disk as a SCSI drive
– sets the disk to be bootable
– sets the size of the disk to +50GB of what it was originally… important as the disk is only a couple of GB and you will run out of space quite quickly…
– sets up cloud init for the VM as an IDE CD drive.
– sets serial output for the VM
– tells Proxmox the Agent is installed
– sets the VM as a template.

Next, clone the template into a new VM.

qm clone 9000 2001 --name k8s-01
qm set 2001 --sshkey godboxv3.pub
qm set 2001 --memory 4096
qm set 2001 --ciuser tiernano
qm set 2001 --ipconfig0 ip=dhcp

Here we are creating a new VM with ID 2001, setting the SSH key, setting the memory and user, and telling the machine to get its IP from DHCP. 

Change tiernano and godboxv3.pub to your settings. Modify the names and memory as necessary.

As mentioned earlier, I’m still encountering the issue of IP addresses being shared. To resolve this, log into the boxes and execute the following commands:

echo -n > /etc/machine-id
rm /var/lib/dbus/machine-id
ln -s /etc/machine-id /var/lib/dbus/machine-id

Reboot the VM, and the problem should be resolved.

day 10 of #100daysofhomelab

Day 10 of #100daysofhomelab and its mostly updates and monitoring.

I think my next plan for the Kubernetes cluster is to rebuild the VMs and start from scratch. Currently, they ranged from 2-4 cores and 4-8Gb RAM. They also had a single disk on them and used an Ubuntu 22.04 cloud image. I think the plan going forward is to make sure each has similar RAM and Cores, none are going on the smaller VM Hosts I have, and I will be adding a new disk just for storage. Looks like Minio might work for me… More testing and reading are required though.

Day 9 of #100daysofhomelab

Well, day 9 of #100daysofhomelab is about Disaster Recovery… Well, at least the disaster part… Recovery not so much… My Kubernetes cluster, how do I put this… shat the bed… It’s been up and down all day and then the Longhorn storage failed and took my WordPress install with it… I lost yesterday’s post (which isn’t the end of the world) but it’s a pain in the ass… I ended up using the old docker copy of WordPress, so at least that’s online.

So, going to shut down the full cluster and start again… Might be looking at something other than Longhorn for storage… but giving up for the day… I will be back tomorrow.

day 7 of #100daysofhomelab

Day 7 of #100daysofhomelab and just a quick update for today: this site is now running on my Kubernetes Cluster! I am using Cloudflare tunnels for the ingress controller (more on that later) and so far, so good… Most of this was done yesterday, and it was a swap over of the DNS stuff today… been sick most of the day, so that’s all I got in me for day 7…

day 6 of #100daysofhomelab

Day 6 of #100daysofhomelab and I have some progress on my Kubernetes cluster!

Then after a few min, it comes back online…

Also, I got WordPress installed in Kubernetes! Now to migrate this blog over… Hopefully before my next update tomorrow…

day 5 of #100daysofhomelab

Day 5 of #100daysofhomelab and its mostly reading… the daddy was in the hospital for the last 2 weeks, including over Christmas day, so tomorrow is Christmas day for us… Turkey, ham and all the usual stuff… So, been busy with that. But have been reading a couple of docs, so some links for today:

That’s about it for today… I’ll be back tomorrow… hopefully…

day 3 of #100daysofhomelab

Day 3 of #100daysofhomelab and more Kubernetes messing today. Haven’t got it working, but messing with it is a start. Some links and notes are below:

I am planning on moving my WordPress install over from my Docker host to Kubernetes in the next few days, so running through the docks from Bharathiraja above, but I keep getting errors related to MySQL… More digging is required. I use Cloudflare Tunnels to secure my WordPress install, so the docs on how to use Cloudflare Tunnels with Kubernetes are important…

Day 2 of #100daysofhomelab

Day 2 of #100daysofhomelab and more messing with Kubernetes… So far, I have built, torn down, rebuilt and torn down a second time… and now building for a third time! Techno Tims Ansible scripts for the Win! A couple of notes for today:

  • the script uses K3s version 1.24.8-k3s1. at some stage yesterday I tried changing this to 1.26.0-k3s1, the latest version from the K3s GitHub page… This was a bad idea. Rancher does not like this, and, well, I don’t know what I am doing, so I want to see what Rancher does…
  • ideally, you would have multiple master nodes, but, me being the lazy git that I am, only set up 1… but it does look like it could be changeable later on…
  • I have a total of 6 VMs running my K3S cluster: 3 are 4 Cores with 8GB RAM running on GodBoxV2, which is now running Proxmox. The other 3 are each running on my HP Micro Server, the Quad 2.5Gb Celeron Box and an 8th Gen Intel NUC… each is given 2 cores and 4GB RAM. That gives me a total of (roughly) 18 cores and 36GB RAM. Each VM has around 50 GB of storage and using Longhorn, I have around 250GB of space (master does not seem to contribute space). Replicas are set to 3, so not quite a full 250GB.
  • Why Kubernetes? Well, I have 2 VMs currently running my fleet of docker containers. I have lost count of how many I actually have. So, my plan is to use Kubernetes to move all them from those single docker boxes, and have them more distributed and more HA. This will allow me to move stuff around easier, or at least I think it will… At the very least, I get to play with new tech! :)

More work on the cluster is required. This blog is hosted in-house on one of the docker instances… Hopefully, at some stage, it will be moved to the K3s cluster! That would be the first major move!