Initial commit: MAAS-deployable Proxmox VE images with cluster automation
Builds a Proxmox VE image that MAAS can deploy to bare metal, plus first-boot
automation that configures the node and joins it to a Proxmox cluster with no
manual steps.
The image starts from the official Debian cloud image and installs proxmox-ve
on top of it, rather than capturing a raw disk from the Proxmox ISO. That keeps
MAAS in control of partitioning, networking, SSH keys and cloud-init, and makes
moving between Proxmox releases a variable change instead of a rewrite.
Contents:
* Makefile driving the whole flow: build, verify, preseed, upload
* customize-proxmox.sh, run inside the Packer build VM, which layers Proxmox
onto the Debian cloud image and resets the pmxcfs node identity so one image
can produce many nodes
* pve-maas-init, a first-boot state machine covering /etc/hosts, node-unique
identifiers, the root password, vmbr0 conversion, cluster create/join and
the local-lvm thin pool; each stage is resumable across reboots
* curtin-hooks, which stops curtin installing a kernel over APT and pins
interface names by MAC so they match what MAAS recorded at commissioning
* a MAAS curtin preseed template and cloud-init examples
* deploy-cluster.sh, which builds a whole cluster through the MAAS API
* verify-image.sh, 22 static checks on the produced tarball
Cluster identity lives entirely in deploy-time cloud-init user-data, so a single
image and preseed can build any number of independent clusters.
Verified end to end against MAAS 3.7.2: proxmox-ve 9.2.0 / pve-manager 9.2.11 /
kernel 7.0.14-15-pve, deployed to two machines that formed a quorate cluster with
local-lvm on both, with no manual intervention.
The README documents four failure modes found along the way that all fail
silently: curtin rejecting "kernel: null", pvenetcommit overwriting the network
configuration at boot, interface renaming leaving the link down, and a systemd
ordering cycle that made systemd delete the service's start job.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
114
overlay/curtin/curtin-hooks
Executable file
114
overlay/curtin/curtin-hooks
Executable file
@@ -0,0 +1,114 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# curtin-hooks - MAAS/curtin kurulum kancasi (Proxmox VE imaji icin)
|
||||
#
|
||||
# Curtin, hedef sistemde /curtin/curtin-hooks varsa dahili curthooks yerine
|
||||
# bu script'i calistirir (curtin.util.run_hook_if_exists).
|
||||
#
|
||||
# Amac: curtin'in APT uzerinden cekirdek kurmaya calismasini engellemek.
|
||||
# Proxmox cekirdegi (proxmox-default-kernel) imajin icinde hazir gelir;
|
||||
# curtin'in onu yeniden kurmasi gereksizdir ve dugumun deploy sirasinda
|
||||
# download.proxmox.com'a erisebilmesini zorunlu kilar (izole aglarda calismaz).
|
||||
#
|
||||
# Not: Bunu preseed'de "kernel: null" ile yapmak mumkun degil - MAAS ile
|
||||
# gelen curtin surumlerinin bir kismi bu degeri desteklemiyor ve
|
||||
# install_kernel icinde AttributeError ile patliyor. Kancayi kullanmak
|
||||
# curtin surumunden bagimsiz olarak calisir.
|
||||
#
|
||||
import shutil
|
||||
import os
|
||||
|
||||
from curtin.commands import curthooks
|
||||
from curtin.config import load_command_config
|
||||
from curtin.util import load_command_environment
|
||||
|
||||
|
||||
def disable_kernel_install():
|
||||
"""curtin'in cekirdek kurulum adimini etkisiz hale getir.
|
||||
|
||||
Fonksiyon adi curtin surumune gore degisiyor (install_kernel ->
|
||||
curthook_install_kernel), ikisini de yakaliyoruz.
|
||||
"""
|
||||
disabled = []
|
||||
for name in ("install_kernel", "curthook_install_kernel"):
|
||||
if hasattr(curthooks, name):
|
||||
setattr(curthooks, name, lambda *args, **kwargs: None)
|
||||
disabled.append(name)
|
||||
print("curtin-hooks: cekirdek kurulumu devre disi: %s" % (disabled or "hicbiri bulunamadi"))
|
||||
|
||||
|
||||
def pin_interface_names(config, target):
|
||||
"""MAAS'in bildigi arayuz adlarini hedef sistemde MAC uzerinden sabitle.
|
||||
|
||||
MAAS commissioning'i Ubuntu ephemeral ortaminda yapiyor ve arayuzu orada
|
||||
gordugu adla (or. enp6s18) kaydediyor. Deploy edilen Debian 13 / Proxmox
|
||||
ise udev'in farkli isimlendirme semasi yuzunden ayni karti ens18 diye
|
||||
adlandirabiliyor. Bu durumda cloud-init acilista arayuzu yeniden
|
||||
adlandirmaya calisiyor, su hatayi aliyor:
|
||||
|
||||
Failed to rename devices: [busy] Error renaming
|
||||
mac=... from ens18 to enp6s18
|
||||
|
||||
...ve arayuzu KAPALI birakiyor; dugum agini tamamen kaybediyor.
|
||||
|
||||
Cozum: MAC -> ad eslemesini systemd .link dosyasi olarak yaziyoruz, boylece
|
||||
udev karti en bastan MAAS'in bekledigi adla olusturuyor ve yeniden
|
||||
adlandirmaya hic gerek kalmiyor.
|
||||
"""
|
||||
net = config.get("network") or {}
|
||||
pairs = []
|
||||
|
||||
# netplan / v2 bicimi
|
||||
for name, cfg in (net.get("ethernets") or {}).items():
|
||||
cfg = cfg or {}
|
||||
mac = (cfg.get("match") or {}).get("macaddress")
|
||||
if mac:
|
||||
pairs.append((cfg.get("set-name") or name, mac))
|
||||
|
||||
# curtin v1 bicimi
|
||||
for entry in net.get("config") or []:
|
||||
if not isinstance(entry, dict) or entry.get("type") != "physical":
|
||||
continue
|
||||
if entry.get("name") and entry.get("mac_address"):
|
||||
pairs.append((entry["name"], entry["mac_address"]))
|
||||
|
||||
if not pairs:
|
||||
print("curtin-hooks: arayuz adi sabitlenmedi (ag yapilandirmasi bos)")
|
||||
return
|
||||
|
||||
link_dir = os.path.join(target, "etc/systemd/network")
|
||||
os.makedirs(link_dir, exist_ok=True)
|
||||
for name, mac in pairs:
|
||||
path = os.path.join(link_dir, "10-maas-%s.link" % name)
|
||||
with open(path, "w") as fh:
|
||||
fh.write(
|
||||
"# MAAS tarafindan beklenen arayuz adi - pve-maas imaji\n"
|
||||
"# curtin-hooks tarafindan olusturuldu.\n"
|
||||
"[Match]\n"
|
||||
"MACAddress=%s\n"
|
||||
"\n"
|
||||
"[Link]\n"
|
||||
"Name=%s\n" % (mac.lower(), name)
|
||||
)
|
||||
print("curtin-hooks: arayuz adi sabitlendi %s -> %s" % (mac, name))
|
||||
|
||||
|
||||
def cleanup():
|
||||
"""Kancayi hedef sistemden kaldir - iz birakma."""
|
||||
curtin_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
shutil.rmtree(curtin_dir, ignore_errors=True)
|
||||
|
||||
|
||||
def main():
|
||||
state = load_command_environment()
|
||||
config = load_command_config(None, state)
|
||||
target = state["target"]
|
||||
|
||||
disable_kernel_install()
|
||||
curthooks.builtin_curthooks(config, target, state)
|
||||
pin_interface_names(config, target)
|
||||
cleanup()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user