Skip to content

MaaS-managed KubeVirt VMs

Create a virtual machine in Kubernetes. A few minutes later it appears in MaaS as a commissioned, Ready machine — with working power control, the right name, and filed under its tenant's resource pool.

Nobody typed a MAC address, created a BMC, or clicked Commission.

Everything here is on GitHub — manifests, both scripts, the exported cluster profiles and this site.

That matters because MaaS is built to manage physical servers. It expects a machine to PXE boot, to have a BMC it can power-cycle over Redfish or IPMI, and to keep the same identity across reboots. A KubeVirt VM has none of those things by default. This project gives it all of them.


The flow

graph TD
    A["<b>VM created</b><br/>in an opted-in namespace"] --> B["<b>KubeMacPool</b><br/>assigns a persistent MAC"]
    A --> C["<b>Kyverno mutate</b><br/>stamps VM identity into SMBIOS"]
    B --> D["<b>Kyverno generate</b><br/>Secret + VirtualMachineBMC + Ingress"]
    C --> D
    D --> E["<b>kubevirtBMC</b><br/>serves Redfish for the VM"]
    E --> F["<b>VM PXE boots</b><br/>on the provisioning VLAN"]
    F --> G["<b>MaaS enlists it</b><br/>machine appears as New"]
    G --> H["<b>Commissioning script</b><br/>writes Redfish power config"]
    H --> I["<b>Reconciler CronJob</b><br/>renames, pools, commissions"]
    I --> J["<b>Ready machine</b><br/>MaaS owns power and boot order"]

    style A fill:#1F7A78,stroke:#043736,color:#fff
    style J fill:#9EB277,stroke:#043736,color:#012121
    style E fill:#F0BE65,stroke:#043736,color:#012121
    style H fill:#F0BE65,stroke:#043736,color:#012121
Step 1
Identity
A persistent MAC, and the VM's name.namespace written into the SMBIOS serial so it can identify itself later.
Step 2
A BMC
A Redfish endpoint per VM, published over TLS, so MaaS can power it on and set its boot device.
Step 3
Enlistment
The VM network-boots, MaaS discovers it, and a commissioning script hands MaaS the BMC details.
Step 4
Ready
A reconciler names the machine after the VM, files it in a tenant pool, and commissions it.

What each piece does

Component Runs where Job
KubeMacPool Cluster Assigns a MAC that survives VM restarts. Without it the MAC changes every power cycle and MaaS loses the machine.
Kyverno Cluster One mutate rule stamps identity into SMBIOS; three generate rules create the BMC credential, the VirtualMachineBMC, and the Redfish Ingress.
kubevirtBMC Cluster Turns a VirtualMachineBMC into a real Redfish service that can power the VM on/off and set its boot device.
Commissioning script MaaS Runs inside the machine during enlistment, works out which VM it is, and writes the Redfish power configuration back to MaaS.
Reconciler CronJob Cluster Renames the machine to match the VM, creates and assigns a per-namespace resource pool, and commissions anything still sitting in New.

Why it is built this way

Why the SMBIOS serial, and not the hostname?

The Redfish endpoint is per-VM (<vm>.<namespace>.redfish.example.com), so the commissioning script has to know which VM it is running on. During commissioning the hostname is a MaaS-generated name like famous-marten — useless.

KubeVirt's firmware.serial is a free-form string that reaches SMBIOS, so a Kyverno mutate rule writes <vm>.<namespace> there. Inside the machine that reads back as:

$ dmidecode -s system-serial-number
web1.team-a

Using <vm>.<namespace> rather than just the VM name means two tenants can both run a VM called web1 without colliding.

Why a commissioning script, and not an API call from Kubernetes?

MaaS needs power configuration in order to power a machine on for commissioning — but the thing that supplies that configuration only runs during commissioning. The only place to break that circle is the commissioning that happens automatically at enlistment, when the machine has PXE-booted itself and is already running.

MaaS supports exactly this: any script tagged bmc-config gets a BMC_CONFIG_PATH to write to, and MaaS applies the result as the machine's power configuration. That is the same mechanism MaaS's own IPMI detection uses.

Why a polling reconciler, and not an event hook?

MaaS has no outbound webhooks — the API exposes events (read-only, pollable) and notifications (UI banners), and neither can call out.

Enlistment is also asynchronous: a VM might PXE boot seconds or hours after it is created, so there is no point in the Kubernetes flow where you can synchronously say "now go rename the MaaS machine". A small converging loop is the honest design.

Why does the VM not power itself on?

The PXE template ships runStrategy: Manual, so a new VM is created powered-off — exactly like a physical server arriving in a rack. Boot it once and the rest is automatic.

runStrategy is only a starting value in any case: kubevirtBMC drives power by writing that field directly (ResetType=On → Always, ForceOff → Halted), so MaaS takes ownership of it from the first power operation onward.


Get started