Merge pull request #1191 from anderbubble/dracut-initramfs-boot

Dracut initramfs boot
This commit is contained in:
Jonathon Anderson
2024-06-06 14:41:49 -06:00
committed by GitHub
17 changed files with 461 additions and 32 deletions

View File

@@ -38,11 +38,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Added ### Added
- Add examples for building overlays in parallel to documentation - Add examples for building overlays in parallel to documentation
- Add `stage=initramfs` to warewulfd provision to serve initramfs from container image. #1115
- Add `warewulf-dracut` package to support building Warewulf-compatible initramfs images with dracut. #1115
- Add iPXE template `dracut.ipxe` to boot a dracut initramfs. #1115
- Add dracut menuentry to `grub.cfg.ww` to boot a dracut initramfs. #1115
- Add `.NetDevs` variable to iPXE and GRUB templates, similar to overlay templates. #1115
- Add `.Tags` variable to iPXE and GRUB templates, similar to overlay templates. #1115
### Changed ### Changed
- Replace reference to docusaurus with Sphinx - Replace reference to docusaurus with Sphinx
- `wwctl container import` now only runs syncuser if explicitly requested. #1212 - `wwctl container import` now only runs syncuser if explicitly requested. #1212
- wwinit now configures NetworkManager to not retain configurations from dracut. #1115
- Improved detection of SELinux capable root fs #1093
### Fixed ### Fixed

View File

@@ -138,6 +138,8 @@ install: build docs
install -m 0644 etc/bash_completion.d/wwctl $(DESTDIR)$(BASHCOMPDIR)/wwctl install -m 0644 etc/bash_completion.d/wwctl $(DESTDIR)$(BASHCOMPDIR)/wwctl
for f in docs/man/man1/*.1.gz; do install -m 0644 $$f $(DESTDIR)$(MANDIR)/man1/; done for f in docs/man/man1/*.1.gz; do install -m 0644 $$f $(DESTDIR)$(MANDIR)/man1/; done
for f in docs/man/man5/*.5.gz; do install -m 0644 $$f $(DESTDIR)$(MANDIR)/man5/; done for f in docs/man/man5/*.5.gz; do install -m 0644 $$f $(DESTDIR)$(MANDIR)/man5/; done
install -pd -m 0755 $(DESTDIR)$(DRACUTMODDIR)/90wwinit
install -m 0644 dracut/modules.d/90wwinit/*.sh $(DESTDIR)$(DRACUTMODDIR)/90wwinit
.PHONY: installapi .PHONY: installapi
installapi: installapi:

View File

@@ -44,10 +44,11 @@ else
endif endif
# OS-Specific Service Locations # OS-Specific Service Locations
VARLIST += TFTPDIR FIREWALLDDIR SYSTEMDDIR BASHCOMPDIR VARLIST += TFTPDIR FIREWALLDDIR SYSTEMDDIR BASHCOMPDIR DRACUTMODDIR
SYSTEMDDIR ?= /usr/lib/systemd/system SYSTEMDDIR ?= /usr/lib/systemd/system
BASHCOMPDIR ?= /etc/bash_completion.d BASHCOMPDIR ?= /etc/bash_completion.d
FIREWALLDDIR ?= /usr/lib/firewalld/services FIREWALLDDIR ?= /usr/lib/firewalld/services
DRACUTMODDIR ?= /usr/lib/dracut/modules.d
ifeq ($(OS),suse) ifeq ($(OS),suse)
TFTPDIR ?= /srv/tftpboot TFTPDIR ?= /srv/tftpboot
endif endif

View File

@@ -0,0 +1,13 @@
#!/bin/bash
info "Mounting tmpfs at $NEWROOT"
mount -t tmpfs ${wwinit_tmpfs_size_option} tmpfs "$NEWROOT"
for archive in "${wwinit_container}" "${wwinit_kmods}" "${wwinit_system}" "${wwinit_runtime}"
do
if [ -n "${archive}" ]
then
info "Loading ${archive}"
(curl --silent -L "${archive}" | gzip -d | cpio -im --directory="${NEWROOT}") || die "Unable to load ${archive}"
fi
done

View File

@@ -0,0 +1,20 @@
#!/bin/bash
check() {
# Don't include in hostonly mode
[[ $hostonly ]] && return 1
# Don't include by default
return 255
}
depends() {
echo network
return 0
}
install() {
inst_multiple cpio curl
inst_hook cmdline 30 "$moddir/parse-wwinit.sh"
inst_hook pre-mount 30 "$moddir/load-wwinit.sh"
}

View File

@@ -0,0 +1,28 @@
#!/bin/sh
# root=wwinit
[ -z "$root" ] && root=$(getarg root=)
if [ "${root}" = "wwinit" ]
then
info "root=${root}"
export wwinit_container=$(getarg wwinit.container=); info "wwinit.container=${wwinit_container}"
export wwinit_system=$(getarg wwinit.system=); info "wwinit.system=${wwinit_system}"
export wwinit_runtime=$(getarg wwinit.runtime=); info "wwinit.runtime=${wwinit_runtime}"
export wwinit_kmods=$(getarg wwinit.kmods=); info "wwinit.kmods=${wwinit_kmods}"
wwinit_tmpfs_size=$(getarg wwinit.tmpfs.size=)
if [ -n "$wwinit_tmpfs_size" ]
then
info "wwinit.tmpfs.size=${wwinit_tmpfs_size}"
export wwinit_tmpfs_size_option="-o size=${wwinit_tmpfs_size}"
fi
if [ -n "${wwinit_container}" ]
then
info "Found root=${root} and a Warewulf container image. Will boot from Warewulf."
rootok=1
else
die "Found root=${root} but no container image. Cannot boot from Warewulf."
fi
fi

View File

@@ -6,13 +6,16 @@ echo "Warewulf Controller: {{.Ipaddr}}"
echo echo
sleep 1 sleep 1
smbios --type1 --get-string 8 --set assetkey smbios --type1 --get-string 8 --set assetkey
uri="(http,{{.Ipaddr}}:{{.Port}})/provision/${net_default_mac}?assetkey=${assetkey}" uri="(http,{{.Ipaddr}}:{{.Port}})/provision/${net_default_mac}?assetkey=${assetkey}"
kernel="${uri}&stage=kernel" kernel="${uri}&stage=kernel"
container="${uri}&stage=container&compress=gz" container="${uri}&stage=container&compress=gz"
system="${uri}&stage=system&compress=gz" system="${uri}&stage=system&compress=gz"
runtime="${uri}&stage=runtime&compress=gz" runtime="${uri}&stage=runtime&compress=gz"
set default=ww4
set default={{ or .Tags.GrubMenuEntry "ww4" }}
set timeout=5 set timeout=5
menuentry "Network boot node: {{.Id}}" --id ww4 { menuentry "Network boot node: {{.Id}}" --id ww4 {
{{if .KernelOverride }} {{if .KernelOverride }}
echo "Kernel: {{.KernelOverride}}" echo "Kernel: {{.KernelOverride}}"
@@ -34,17 +37,54 @@ menuentry "Network boot node: {{.Id}}" --id ww4 {
reboot reboot
fi fi
} }
menuentry "Network boot node with dracut: {{.Id}}" --id dracut {
initramfs="${uri}&stage=initramfs"
uri="http://{{.Ipaddr}}:{{.Port}}/provision/${net_default_mac}?assetkey=${assetkey}"
container="${uri}&stage=container&compress=gz"
system="${uri}&stage=system&compress=gz"
runtime="${uri}&stage=runtime&compress=gz"
{{if .KernelOverride }}
echo "Kernel: {{.KernelOverride}}"
{{else}}
echo "Kernel: {{.ContainerName}} (container default)"
{{end}}
echo "KernelArgs: {{.KernelArgs}}"
net_args="rd.neednet=1 {{range $devname, $netdev := .NetDevs}}{{if and $netdev.Hwaddr $netdev.Device}} ifname={{$netdev.Device}}:{{$netdev.Hwaddr}} {{end}}{{end}}"
wwinit_args="root=wwinit wwinit.container=${container} wwinit.system=${system} wwinit.runtime=${runtime} init=/init"
linux $kernel wwid=${net_default_mac} {{.KernelArgs}} $net_args $wwinit_args
if [ x$? = x0 ] ; then
echo "Loading Container: {{.ContainerName}}"
initrd $initramfs
boot
else
echo "MESSAGE: This node seems to be unconfigured. Please have your system administrator add a"
echo " configuration for this node with HW address: ${net_default_mac}"
echo ""
echo "Rebooting in 1 minute..."
sleep 60
reboot
fi
}
menuentry "Chainload specific configfile" { menuentry "Chainload specific configfile" {
conf="(http,{{.Ipaddr}}:{{.Port}})/efiboot/grub.cfg" conf="(http,{{.Ipaddr}}:{{.Port}})/efiboot/grub.cfg"
configfile $conf configfile $conf
} }
menuentry "UEFI Firmware Settings" --id "uefi-firmware" { menuentry "UEFI Firmware Settings" --id "uefi-firmware" {
fwsetup fwsetup
} }
menuentry "System restart" { menuentry "System restart" {
echo "System rebooting..." echo "System rebooting..."
reboot reboot
} }
menuentry "System shutdown" { menuentry "System shutdown" {
echo "System shutting down..." echo "System shutting down..."
halt halt

46
etc/ipxe/dracut.ipxe Normal file
View File

@@ -0,0 +1,46 @@
#!ipxe
echo
echo ================================================================================
echo Warewulf v4 now booting via dracut: {{.Fqdn}} ({{.Hwaddr}})
echo
echo Container: {{.ContainerName}}
{{if .KernelOverride }}
echo Kernel: {{.KernelOverride}}
{{else}}
echo Kernel: {{.ContainerName}} (container default)
{{end}}
echo KernelArgs: {{.KernelArgs}}
echo
set uri http://{{.Ipaddr}}:{{.Port}}/provision/{{.Hwaddr}}?assetkey=${asset}&uuid=${uuid}
echo Warewulf Controller: {{.Ipaddr}}
echo Downloading Kernel Image:
kernel --name kernel ${uri}&stage=kernel || goto reboot
{{if ne .KernelOverride ""}}
echo Downloading Kernel Modules:
imgextract --name kmods ${uri}&stage=kmods&compress=gz || initrd --name kmods ${uri}&stage=kmods || goto reboot
set kernel_mods initrd=kmods
{{end}}
echo Downloading initramfs
initrd --name initramfs ${uri}&stage=initramfs || goto reboot
set dracut_net rd.neednet=1 {{range $devname, $netdev := .NetDevs}}{{if and $netdev.Hwaddr $netdev.Device}} ifname={{$netdev.Device}}:{{$netdev.Hwaddr}} {{end}}{{end}}
set dracut_wwinit root=wwinit wwinit.container=${uri}&stage=container&compress=gz wwinit.system=${uri}&stage=system&compress=gz wwinit.runtime=${uri}&stage=runtime&compress=gz {{if ne .KernelOverride ""}}wwinit.kmods=${uri}&stage=kmods&compress=gz{{end}} init=/init
echo Booting initramfs
#echo Network KernelArgs: ${dracut_net}
#echo Dracut wwinit KernelArgs: ${dracut_wwinit}
#sleep 15
boot kernel initrd=initramfs ${kernel_mods} ${dracut_net} ${dracut_wwinit} wwid={{.Hwaddr}} {{.KernelArgs}}
:reboot
echo
echo There was an error, rebooting in 15s...
echo
sleep 15
reboot

View File

@@ -1,11 +1,25 @@
package container package container
import ( import (
"fmt"
"path" "path"
"github.com/warewulf/warewulf/internal/pkg/util"
"github.com/warewulf/warewulf/internal/pkg/wwlog"
warewulfconf "github.com/warewulf/warewulf/internal/pkg/config" warewulfconf "github.com/warewulf/warewulf/internal/pkg/config"
) )
var (
initramfsSearchPaths = []string{
// This is a printf format where the %s will be the kernel version
"boot/initramfs-%s",
"boot/initramfs-%s.img",
"boot/initrd-%s",
"boot/initrd-%s.img",
}
)
func SourceParentDir() string { func SourceParentDir() string {
conf := warewulfconf.Get() conf := warewulfconf.Get()
return conf.Paths.WWChrootdir return conf.Paths.WWChrootdir
@@ -27,3 +41,16 @@ func ImageParentDir() string {
func ImageFile(name string) string { func ImageFile(name string) string {
return path.Join(ImageParentDir(), name+".img") return path.Join(ImageParentDir(), name+".img")
} }
// InitramfsBootPath returns the dracut built initramfs path, as dracut built initramfs inside container
// the function returns host path of the built file
func InitramfsBootPath(image, kver string) (string, error) {
for _, searchPath := range initramfsSearchPaths {
initramfs_path := path.Join(RootFsDir(image), fmt.Sprintf(searchPath, kver))
wwlog.Debug("Looking for initramfs at: %s", initramfs_path)
if util.IsFile(initramfs_path) {
return initramfs_path, nil
}
}
return "", fmt.Errorf("Failed to find a target kernel version initramfs")
}

View File

@@ -0,0 +1,88 @@
package container
import (
"fmt"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
warewulfconf "github.com/warewulf/warewulf/internal/pkg/config"
)
func TestInitramfsBootPath(t *testing.T) {
conf := warewulfconf.Get()
temp, err := os.MkdirTemp(os.TempDir(), "ww-conf-*")
assert.NoError(t, err)
defer os.RemoveAll(temp)
conf.Paths.WWChrootdir = temp
assert.NoError(t, os.MkdirAll(filepath.Join(RootFsDir("image"), "boot"), 0700))
tests := []struct {
name string
initramfs []string
ver string
err error
retName string
}{
{
name: "ok case 1",
initramfs: []string{"initramfs-1.1.1.aarch64.img"},
ver: "1.1.1.aarch64",
err: nil,
},
{
name: "ok case 2",
initramfs: []string{"initrd-1.1.1.aarch64"},
ver: "1.1.1.aarch64",
err: nil,
},
{
name: "ok case 3",
initramfs: []string{"initramfs-1.1.1.aarch64"},
ver: "1.1.1.aarch64",
err: nil,
},
{
name: "ok case 4",
initramfs: []string{"initrd-1.1.1.aarch64.img"},
ver: "1.1.1.aarch64",
err: nil,
},
{
name: "error case, wrong init name",
initramfs: []string{"initrr-1.1.1.aarch64.img"},
ver: "1.1.1.aarch64",
err: fmt.Errorf("Failed to find a target kernel version initramfs"),
},
{
name: "error case, wrong ver",
initramfs: []string{"initrr-1.1.1.aarch64.img"},
ver: "1.1.2.aarch64",
err: fmt.Errorf("Failed to find a target kernel version initramfs"),
},
}
for _, tt := range tests {
t.Logf("running test: %s", tt.name)
for _, init := range tt.initramfs {
assert.NoError(t, os.WriteFile(filepath.Join(RootFsDir("image"), "boot", init), []byte(""), 0600))
}
initPath, err := InitramfsBootPath("image", tt.ver)
assert.Equal(t, tt.err, err)
if err == nil {
assert.NotEmpty(t, initPath)
} else {
assert.Empty(t, initPath)
}
if tt.retName != "" {
assert.Equal(t, filepath.Base(initPath), tt.retName)
}
// remove the file
for _, init := range tt.initramfs {
assert.NoError(t, os.Remove(filepath.Join(RootFsDir("image"), "boot", init)))
}
}
}

View File

@@ -73,6 +73,8 @@ func parseReq(req *http.Request) (parserInfo, error) {
ret.stage = "runtime" ret.stage = "runtime"
} else if stage == "efiboot" { } else if stage == "efiboot" {
ret.stage = "efiboot" ret.stage = "efiboot"
} else if stage == "initramfs" {
ret.stage = "initramfs"
} }
} }

View File

@@ -15,6 +15,7 @@ import (
warewulfconf "github.com/warewulf/warewulf/internal/pkg/config" warewulfconf "github.com/warewulf/warewulf/internal/pkg/config"
"github.com/warewulf/warewulf/internal/pkg/container" "github.com/warewulf/warewulf/internal/pkg/container"
"github.com/warewulf/warewulf/internal/pkg/kernel" "github.com/warewulf/warewulf/internal/pkg/kernel"
"github.com/warewulf/warewulf/internal/pkg/node"
"github.com/warewulf/warewulf/internal/pkg/overlay" "github.com/warewulf/warewulf/internal/pkg/overlay"
"github.com/warewulf/warewulf/internal/pkg/util" "github.com/warewulf/warewulf/internal/pkg/util"
"github.com/warewulf/warewulf/internal/pkg/wwlog" "github.com/warewulf/warewulf/internal/pkg/wwlog"
@@ -33,6 +34,8 @@ type templateVars struct {
Port string Port string
KernelArgs string KernelArgs string
KernelOverride string KernelOverride string
Tags map[string]string
NetDevs map[string]*node.NetDevs
} }
func ProvisionSend(w http.ResponseWriter, req *http.Request) { func ProvisionSend(w http.ResponseWriter, req *http.Request) {
@@ -56,12 +59,13 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) {
} }
status_stages := map[string]string{ status_stages := map[string]string{
"efiboot": "EFI", "efiboot": "EFI",
"ipxe": "IPXE", "ipxe": "IPXE",
"kernel": "KERNEL", "kernel": "KERNEL",
"kmods": "KMODS_OVERLAY", "kmods": "KMODS_OVERLAY",
"system": "SYSTEM_OVERLAY", "system": "SYSTEM_OVERLAY",
"runtime": "RUNTIME_OVERLAY"} "runtime": "RUNTIME_OVERLAY",
"initramfs": "INITRAMFS"}
status_stage := status_stages[rinfo.stage] status_stage := status_stages[rinfo.stage]
var stage_file string var stage_file string
@@ -93,6 +97,7 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) {
} else if rinfo.stage == "ipxe" { } else if rinfo.stage == "ipxe" {
stage_file = path.Join(conf.Paths.Sysconfdir, "warewulf/ipxe/"+node.Ipxe.Get()+".ipxe") stage_file = path.Join(conf.Paths.Sysconfdir, "warewulf/ipxe/"+node.Ipxe.Get()+".ipxe")
tstruct := overlay.InitStruct(&node)
tmpl_data = templateVars{ tmpl_data = templateVars{
Id: node.Id.Get(), Id: node.Id.Get(),
Cluster: node.ClusterName.Get(), Cluster: node.ClusterName.Get(),
@@ -103,7 +108,9 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) {
Hwaddr: rinfo.hwaddr, Hwaddr: rinfo.hwaddr,
ContainerName: node.ContainerName.Get(), ContainerName: node.ContainerName.Get(),
KernelArgs: node.Kernel.Args.Get(), KernelArgs: node.Kernel.Args.Get(),
KernelOverride: node.Kernel.Override.Get()} KernelOverride: node.Kernel.Override.Get(),
NetDevs: tstruct.NetDevs,
Tags: tstruct.Tags}
} else if rinfo.stage == "kernel" { } else if rinfo.stage == "kernel" {
if node.Kernel.Override.Defined() { if node.Kernel.Override.Defined() {
stage_file = kernel.KernelImage(node.Kernel.Override.Get()) stage_file = kernel.KernelImage(node.Kernel.Override.Get())
@@ -175,6 +182,7 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) {
} }
case "grub.cfg": case "grub.cfg":
stage_file = path.Join(conf.Paths.Sysconfdir, "warewulf/grub/grub.cfg.ww") stage_file = path.Join(conf.Paths.Sysconfdir, "warewulf/grub/grub.cfg.ww")
tstruct := overlay.InitStruct(&node)
tmpl_data = templateVars{ tmpl_data = templateVars{
Id: node.Id.Get(), Id: node.Id.Get(),
Cluster: node.ClusterName.Get(), Cluster: node.ClusterName.Get(),
@@ -185,7 +193,9 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) {
Hwaddr: rinfo.hwaddr, Hwaddr: rinfo.hwaddr,
ContainerName: node.ContainerName.Get(), ContainerName: node.ContainerName.Get(),
KernelArgs: node.Kernel.Args.Get(), KernelArgs: node.Kernel.Args.Get(),
KernelOverride: node.Kernel.Override.Get()} KernelOverride: node.Kernel.Override.Get(),
NetDevs: tstruct.NetDevs,
Tags: tstruct.Tags}
if stage_file == "" { if stage_file == "" {
wwlog.ErrorExc(fmt.Errorf("could't find grub.cfg template"), containerName) wwlog.ErrorExc(fmt.Errorf("could't find grub.cfg template"), containerName)
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)
@@ -213,6 +223,19 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) {
} else { } else {
wwlog.Warn("No conainer set for node %s", node.Id.Get()) wwlog.Warn("No conainer set for node %s", node.Id.Get())
} }
} else if rinfo.stage == "initramfs" {
if node.ContainerName.Defined() {
_, kver, err := kernel.FindKernel(container.RootFsDir(node.ContainerName.Get()))
if err != nil {
wwlog.Error("No kernel found for initramfs for container %s: %s", node.ContainerName.Get(), err)
}
stage_file, err = container.InitramfsBootPath(node.ContainerName.Get(), kver)
if err != nil {
wwlog.Error("No initramfs found for container %s: %s", node.ContainerName.Get(), err)
}
} else {
wwlog.Warn("No container set for node %s", node.Id.Get())
}
} }
wwlog.Serv("stage_file '%s'", stage_file) wwlog.Serv("stage_file '%s'", stage_file)

View File

@@ -30,6 +30,8 @@ var provisionSendTests = []struct {
{"find shim", "/efiboot/shim.efi", "", 404, "10.10.10.11:9873"}, {"find shim", "/efiboot/shim.efi", "", 404, "10.10.10.11:9873"},
{"find grub", "/efiboot/grub.efi", "", 200, "10.10.10.10:9873"}, {"find grub", "/efiboot/grub.efi", "", 200, "10.10.10.10:9873"},
{"find grub", "/efiboot/grub.efi", "", 404, "10.10.10.11:9873"}, {"find grub", "/efiboot/grub.efi", "", 404, "10.10.10.11:9873"},
{"find initramfs", "/provision/00:00:00:ff:ff:ff?stage=initramfs", "", 200, "10.10.10.10:9873"},
{"ipxe test with NetDevs and KernelOverrides", "/provision/00:00:00:00:00:ff?stage=ipxe", "1.1.1 ifname=net:00:00:00:00:00:ff ", 200, "10.10.10.12:9873"},
} }
func Test_ProvisionSend(t *testing.T) { func Test_ProvisionSend(t *testing.T) {
@@ -50,7 +52,15 @@ nodes:
network devices: network devices:
default: default:
hwaddr: 00:00:00:00:ff:ff hwaddr: 00:00:00:00:ff:ff
container name: none`) container name: none
n3:
network devices:
default:
hwaddr: 00:00:00:00:00:ff
device: net
ipxe template: test
kernel:
override: 1.1.1`)
assert.NoError(t, err) assert.NoError(t, err)
} }
assert.NoError(t, conf_file.Sync()) assert.NoError(t, conf_file.Sync())
@@ -63,7 +73,8 @@ nodes:
{ {
_, err := arp_file.WriteString(`IP address HW type Flags HW address Mask Device _, err := arp_file.WriteString(`IP address HW type Flags HW address Mask Device
10.10.10.10 0x1 0x2 00:00:00:ff:ff:ff * dummy 10.10.10.10 0x1 0x2 00:00:00:ff:ff:ff * dummy
10.10.10.11 0x1 0x2 00:00:00:00:ff:ff * dummy`) 10.10.10.11 0x1 0x2 00:00:00:00:ff:ff * dummy
10.10.10.12 0x1 0x2 00:00:00:00:00:ff * dummy`)
assert.NoError(t, err) assert.NoError(t, err)
} }
assert.NoError(t, arp_file.Sync()) assert.NoError(t, arp_file.Sync())
@@ -74,6 +85,12 @@ nodes:
assert.NoError(t, imageDirErr) assert.NoError(t, imageDirErr)
defer os.RemoveAll(containerDir) defer os.RemoveAll(containerDir)
conf.Paths.WWChrootdir = containerDir conf.Paths.WWChrootdir = containerDir
sysConfDir, sysConfDirErr := os.MkdirTemp(os.TempDir(), "ww-test-sysconf-*")
assert.NoError(t, sysConfDirErr)
defer os.RemoveAll(sysConfDir)
conf.Paths.Sysconfdir = sysConfDir
assert.NoError(t, os.MkdirAll(path.Join(containerDir, "suse/rootfs/usr/lib64/efi"), 0700)) assert.NoError(t, os.MkdirAll(path.Join(containerDir, "suse/rootfs/usr/lib64/efi"), 0700))
{ {
_, err := os.Create(path.Join(containerDir, "suse/rootfs/usr/lib64/efi", "shim.efi")) _, err := os.Create(path.Join(containerDir, "suse/rootfs/usr/lib64/efi", "shim.efi"))
@@ -84,6 +101,15 @@ nodes:
_, err := os.Create(path.Join(containerDir, "suse/rootfs/usr/share/efi/x86_64/", "grub.efi")) _, err := os.Create(path.Join(containerDir, "suse/rootfs/usr/share/efi/x86_64/", "grub.efi"))
assert.NoError(t, err) assert.NoError(t, err)
} }
assert.NoError(t, os.MkdirAll(path.Join(containerDir, "suse/rootfs/boot"), 0700))
{
_, err := os.Create(path.Join(containerDir, "suse/rootfs/boot", "initramfs-.img"))
assert.NoError(t, err)
}
assert.NoError(t, os.MkdirAll(path.Join(conf.Paths.Sysconfdir, "warewulf/ipxe"), 0700))
{
assert.NoError(t, os.WriteFile(path.Join(conf.Paths.Sysconfdir, "warewulf/ipxe", "test.ipxe"), []byte("{{.KernelOverride}}{{range $devname, $netdev := .NetDevs}}{{if and $netdev.Hwaddr $netdev.Device}} ifname={{$netdev.Device}}:{{$netdev.Hwaddr}} {{end}}{{end}}"), 0600))
}
dbErr := LoadNodeDB() dbErr := LoadNodeDB()
assert.NoError(t, dbErr) assert.NoError(t, dbErr)

View File

@@ -0,0 +1,2 @@
[device]
keep-configuration=no

View File

@@ -2,11 +2,6 @@
. /warewulf/config . /warewulf/config
if test -z "$WWROOT"; then
echo "Skipping SELinux configuration: Warewulf Root device not set"
exit
fi
if test -f "/etc/sysconfig/selinux"; then if test -f "/etc/sysconfig/selinux"; then
. /etc/sysconfig/selinux . /etc/sysconfig/selinux
else else
@@ -14,22 +9,25 @@ else
exit exit
fi fi
if test "$WWROOT" == "initramfs"; then if test "$SELINUX" == "disabled"; then
echo "Skipping SELinux configuration: 'Root=initramfs'" echo "Skipping SELinux setup per /etc/sysconfig/selinux"
if test "$SELINUX" != "disabled"; then
echo "WARNING: SELinux prep is being skipped, but SELinux is enabled on host! This may"
echo "WARNING: cause the system to not work properly. Try setting 'Root=tmpfs'"
sleep 5
fi
exit exit
fi fi
if test "$SELINUX" == "disabled"; then if grep -q "selinux=0" /proc/cmdline; then
echo "Skipping SELinux setup per /etc/sysconfig/selinux"
elif grep -q "selinux=0" /proc/cmdline; then
echo "Skipping SELinux setup per kernel command line" echo "Skipping SELinux setup per kernel command line"
else exit
echo "Setting up SELinux"
/sbin/load_policy -i
/sbin/restorecon -r /
fi fi
if [ $(findmnt / --noheadings --output SOURCE) == 'rootfs' ]; then
echo "Skipping SELinux configuration: rootfs does not support SELinux contexts"
echo
echo "WARNING: SELinux prep is being skipped, but SELinux is enabled on host! This may"
echo "WARNING: cause the system to not work properly. Try setting 'Root=tmpfs'"
sleep 5
exit
fi
echo "Setting up SELinux"
/sbin/load_policy -i
/sbin/restorecon -e /sys -r /

View File

@@ -5,6 +5,9 @@ Boot Management
Warewulf uses iPXE to for network boot by default. As a tech preview, support Warewulf uses iPXE to for network boot by default. As a tech preview, support
for GRUB is also available, which adds support for secure boot. for GRUB is also available, which adds support for secure boot.
Also as a tech preview, Warewulf may also use iPXE to boot a dracut
initramfs as an initial stage before loading the container image.
Booting with iPXE Booting with iPXE
================= =================
@@ -29,6 +32,81 @@ Booting with iPXE
ipxe_cfg->kernel[ltail=cluster0,label="http"]; ipxe_cfg->kernel[ltail=cluster0,label="http"];
} }
Booting with dracut
-------------------
Some systems, typically due to limitations in their BIOS or EFI
firmware, are unable to load container image of a certain size
directly with a traditional bootloader, either iPXE or GRUB. As a
workaround for such systems, Warewulf can be configured to load a
dracut initramfs from the container and to use that initramfs to load
the full container image.
Warewulf provides a dracut module to configure the dracut initramfs to
load the container image. This module is available in the
``warewulf-dracut`` subpackage, which must be installed in the
container image.
With the ``warewulf-dracut`` package installed, you can build an
initramfs inside the container.
.. code-block:: shell
dnf -y install warewulf-dracut
dracut --force --no-hostonly --add wwinit --kver $(ls /lib/modules | head -n1)
Set the node's iPXE template to ``dracut`` to direct iPXE to fetch the
node's initramfs image and boot with dracut semantics, rather than
booting the node image directly.
.. note::
Warewulf iPXE templates are located at ``/etc/warewulf/ipxe/`` when
Warewulf is installed via official packages. You can learn more
about how dracut booting works by inspecting its iPXE template at
``/etc/warewulf/ipxe/dracut.ipxe``.
.. code-block:: shell
wwctl node set wwnode1 --ipxe dracut
.. note::
The iPXE template may be set at the node or profile level.
Alternatively, to direct GRUB to fetch the node's initramfs image and boot with
dracut semantics, set a ``GrubMenuEntry`` tag for the node.
.. note::
Warewulf configures GRUB with a template located at
``/etc/warewulf/grub/grub.cfg.ww``. Inspect the template to learn more about
the dracut booting process.
.. code-block:: shell
wwctl node set wwnode1 --tagadd GrubMenuEntry=dracut
.. note::
The ``GrubMenuEntry`` variable may be set at the node or profile level.
During boot, ``warewulfd`` will detect and dynamically serve an
initramfs from a node's container image in much the same way that it
can serve a kernel from a container image. This image is loaded by
iPXE (or GRUB) which directs dracut to fetch the node's container image
during boot.
The wwinit module provisions to tmpfs. By default, tmpfs is permitted
to use up to 50% of physical memory. This size limit may be adjustd
using the kernel argument `wwinit.tmpfs.size`. (This parameter is
passed to the `size` option during tmpfs mount. See ``tmpfs(5)`` for
more details.)
.. warning::
Kernel overrides are not currently fully supported during dracut initramfs boot.
Booting with GRUB Booting with GRUB
================= =================

View File

@@ -82,6 +82,23 @@ BuildRequires: libassuan-devel gpgme-devel
Warewulf is a stateless and diskless container operating system provisioning Warewulf is a stateless and diskless container operating system provisioning
system for large clusters of bare metal and/or virtual systems. system for large clusters of bare metal and/or virtual systems.
%package dracut
Summary: dracut module for loading a Warewulf container image
BuildArch: noarch
Requires: dracut
%if 0%{?suse_version}
%else
Requires: dracut-network
%endif
%description dracut
Warewulf is a stateless and diskless container operating system provisioning
system for large clusters of bare metal and/or virtual systems.
This subpackage contains a dracut module that can be used to generate
an initramfs that can fetch and boot a Warewulf container image from a
Warewulf server.
%prep %prep
%setup -q -n %{name}-%{version} -b0 %if %{?with_offline:-a2} %setup -q -n %{name}-%{version} -b0 %if %{?with_offline:-a2}
@@ -105,7 +122,8 @@ make defaults \
BASHCOMPDIR=/etc/bash_completion.d/ \ BASHCOMPDIR=/etc/bash_completion.d/ \
FIREWALLDDIR=/usr/lib/firewalld/services \ FIREWALLDDIR=/usr/lib/firewalld/services \
WWCLIENTDIR=/warewulf \ WWCLIENTDIR=/warewulf \
IPXESOURCE=/usr/share/ipxe IPXESOURCE=/usr/share/ipxe \
DRACUTMODDIR=/usr/lib/dracut/modules.d
make make
%if %{api} %if %{api}
make api make api
@@ -185,7 +203,16 @@ getent group %{wwgroup} >/dev/null || groupadd -r %{wwgroup}
%endif %endif
%files dracut
%defattr(-, root, root)
%dir %{_prefix}/lib/dracut/modules.d/90wwinit
%{_prefix}/lib/dracut/modules.d/90wwinit/*.sh
%changelog %changelog
* Thu Apr 18 2024 Jonathon Anderson <janderson@ciq.com>
- New warewulf-dracut subpackage
* Wed Apr 17 2024 Jonathon Anderson <janderson@ciq.com> * Wed Apr 17 2024 Jonathon Anderson <janderson@ciq.com>
- Don't build the API on EL7 - Don't build the API on EL7