From 0856c25a5b70c0a492b209596a7d466f67911a9c Mon Sep 17 00:00:00 2001 From: Ansuman Sahoo Date: Wed, 10 Sep 2025 12:30:38 +0530 Subject: [PATCH] support for libkrun using krunkit Signed-off-by: Ansuman Sahoo --- cmd/lima-driver-krunkit/main_darwin_arm64.go | 16 + pkg/driver/krunkit/errors_darwin_arm64.go | 8 + pkg/driver/krunkit/hack/install-vulkan-gpu.sh | 33 ++ pkg/driver/krunkit/krunkit_darwin_arm64.go | 243 ++++++++++++ .../krunkit/krunkit_driver_darwin_arm64.go | 355 ++++++++++++++++++ templates/experimental/krunkit.yaml | 21 ++ .../content/en/docs/config/vmtype/krunkit.md | 88 +++++ 7 files changed, 764 insertions(+) create mode 100644 cmd/lima-driver-krunkit/main_darwin_arm64.go create mode 100644 pkg/driver/krunkit/errors_darwin_arm64.go create mode 100644 pkg/driver/krunkit/hack/install-vulkan-gpu.sh create mode 100644 pkg/driver/krunkit/krunkit_darwin_arm64.go create mode 100644 pkg/driver/krunkit/krunkit_driver_darwin_arm64.go create mode 100644 templates/experimental/krunkit.yaml create mode 100644 website/content/en/docs/config/vmtype/krunkit.md diff --git a/cmd/lima-driver-krunkit/main_darwin_arm64.go b/cmd/lima-driver-krunkit/main_darwin_arm64.go new file mode 100644 index 00000000000..1fa75ba64b8 --- /dev/null +++ b/cmd/lima-driver-krunkit/main_darwin_arm64.go @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: Copyright The Lima Authors +// SPDX-License-Identifier: Apache-2.0 + +package main + +import ( + "context" + + "github.com/lima-vm/lima/v2/pkg/driver/external/server" + "github.com/lima-vm/lima/v2/pkg/driver/krunkit" +) + +// To be used as an external driver for Lima. +func main() { + server.Serve(context.Background(), krunkit.New()) +} diff --git a/pkg/driver/krunkit/errors_darwin_arm64.go b/pkg/driver/krunkit/errors_darwin_arm64.go new file mode 100644 index 00000000000..0f8a8d9af2d --- /dev/null +++ b/pkg/driver/krunkit/errors_darwin_arm64.go @@ -0,0 +1,8 @@ +// SPDX-FileCopyrightText: Copyright The Lima Authors +// SPDX-License-Identifier: Apache-2.0 + +package krunkit + +import "errors" + +var errUnimplemented = errors.New("unimplemented by the krunkit driver") diff --git a/pkg/driver/krunkit/hack/install-vulkan-gpu.sh b/pkg/driver/krunkit/hack/install-vulkan-gpu.sh new file mode 100644 index 00000000000..d3f60d43fad --- /dev/null +++ b/pkg/driver/krunkit/hack/install-vulkan-gpu.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +# SPDX-FileCopyrightText: Copyright The Lima Authors +# SPDX-License-Identifier: Apache-2.0 + +set -eux -o pipefail + +# Install required packages +dnf install -y dnf-plugins-core dnf-plugin-versionlock llvm18-libs + +# Install Vulkan and Mesa base packages +dnf install -y \ + mesa-vulkan-drivers \ + vulkan-loader-devel \ + vulkan-headers \ + vulkan-tools \ + vulkan-loader \ + glslc + +# Enable COPR repo with patched Mesa for Venus support +dnf copr enable -y slp/mesa-krunkit fedora-40-aarch64 + +# Downgrade to patched Mesa version from COPR +dnf downgrade -y mesa-vulkan-drivers.aarch64 \ + --repo=copr:copr.fedorainfracloud.org:slp:mesa-krunkit + +# Lock Mesa version to prevent automatic upgrades +dnf versionlock add mesa-vulkan-drivers + +# Clean up +dnf clean all + +echo "Krunkit GPU(Venus) setup complete. Verify Vulkan installation by running 'vulkaninfo --summary'." diff --git a/pkg/driver/krunkit/krunkit_darwin_arm64.go b/pkg/driver/krunkit/krunkit_darwin_arm64.go new file mode 100644 index 00000000000..448886b64fe --- /dev/null +++ b/pkg/driver/krunkit/krunkit_darwin_arm64.go @@ -0,0 +1,243 @@ +// SPDX-FileCopyrightText: Copyright The Lima Authors +// SPDX-License-Identifier: Apache-2.0 + +package krunkit + +import ( + "context" + "errors" + "fmt" + "net" + "os" + "os/exec" + "path/filepath" + "strconv" + + "github.com/docker/go-units" + "github.com/sirupsen/logrus" + + "github.com/lima-vm/lima/v2/pkg/driver/vz" + "github.com/lima-vm/lima/v2/pkg/imgutil/proxyimgutil" + "github.com/lima-vm/lima/v2/pkg/iso9660util" + "github.com/lima-vm/lima/v2/pkg/limatype" + "github.com/lima-vm/lima/v2/pkg/limatype/filenames" + "github.com/lima-vm/lima/v2/pkg/limayaml" + "github.com/lima-vm/lima/v2/pkg/networks" + "github.com/lima-vm/lima/v2/pkg/networks/usernet" + "github.com/lima-vm/lima/v2/pkg/store" +) + +const logLevelInfo = "3" + +// Cmdline constructs the command line arguments for krunkit based on the instance configuration. +func Cmdline(inst *limatype.Instance) (*exec.Cmd, error) { + memBytes, err := units.RAMInBytes(*inst.Config.Memory) + if err != nil { + return nil, err + } + + args := []string{ + // Memory in MiB + "--memory", strconv.FormatInt(memBytes/units.MiB, 10), + "--cpus", fmt.Sprintf("%d", *inst.Config.CPUs), + "--device", fmt.Sprintf("virtio-serial,logFilePath=%s", filepath.Join(inst.Dir, filenames.SerialLog)), + "--krun-log-level", logLevelInfo, + "--restful-uri", "none://", + + // First virtio-blk device is the boot disk + "--device", fmt.Sprintf("virtio-blk,path=%s,format=raw", filepath.Join(inst.Dir, filenames.DiffDisk)), + "--device", fmt.Sprintf("virtio-blk,path=%s", filepath.Join(inst.Dir, filenames.CIDataISO)), + } + + // Add additional disks + if len(inst.Config.AdditionalDisks) > 0 { + ctx := context.Background() + diskUtil := proxyimgutil.NewDiskUtil(ctx) + for _, d := range inst.Config.AdditionalDisks { + disk, derr := store.InspectDisk(d.Name) + if derr != nil { + return nil, fmt.Errorf("failed to load disk %q: %w", d.Name, derr) + } + if disk.Instance != "" { + return nil, fmt.Errorf("failed to run attach disk %q, in use by instance %q", disk.Name, disk.Instance) + } + if lerr := disk.Lock(inst.Dir); lerr != nil { + return nil, fmt.Errorf("failed to lock disk %q: %w", d.Name, lerr) + } + extraDiskPath := filepath.Join(disk.Dir, filenames.DataDisk) + logrus.Infof("Mounting disk %q on %q", disk.Name, disk.MountPoint) + if cerr := diskUtil.ConvertToRaw(ctx, extraDiskPath, extraDiskPath, nil, true); cerr != nil { + return nil, fmt.Errorf("failed to convert extra disk %q to raw: %w", extraDiskPath, cerr) + } + args = append(args, "--device", fmt.Sprintf("virtio-blk,path=%s,format=raw", extraDiskPath)) + } + } + + // Network commands + networkArgs, err := buildNetworkArgs(inst) + if err != nil { + return nil, fmt.Errorf("failed to build network arguments: %w", err) + } + + // File sharing commands + if *inst.Config.MountType == limatype.VIRTIOFS { + for i, mount := range inst.Config.Mounts { + if _, err := os.Stat(mount.Location); errors.Is(err, os.ErrNotExist) { + if err := os.MkdirAll(mount.Location, 0o750); err != nil { + return nil, err + } + } + tag := fmt.Sprintf("mount%d", i) + mountArg := fmt.Sprintf("virtio-fs,sharedDir=%s,mountTag=%s", mount.Location, tag) + args = append(args, "--device", mountArg) + } + } + + args = append(args, networkArgs...) + cmd := exec.CommandContext(context.Background(), vmType, args...) + + return cmd, nil +} + +func buildNetworkArgs(inst *limatype.Instance) ([]string, error) { + var args []string + + // Configure default usernetwork with limayaml.MACAddress(inst.Dir) for eth0 interface + firstUsernetIndex := limayaml.FirstUsernetIndex(inst.Config) + if firstUsernetIndex == -1 { + // slirp network using gvisor netstack + krunkitSock, err := usernet.SockWithDirectory(inst.Dir, "", usernet.FDSock) + if err != nil { + return nil, err + } + client, err := vz.PassFDToUnix(krunkitSock) + if err != nil { + return nil, err + } + + args = append(args, "--device", fmt.Sprintf("virtio-net,type=unixgram,fd=%d,mac=%s", client.Fd(), limayaml.MACAddress(inst.Dir))) + } + + for _, nw := range inst.Networks { + var sock string + var mac string + if nw.Lima != "" { + nwCfg, err := networks.LoadConfig() + if err != nil { + return nil, err + } + switch nw.Lima { + case networks.ModeUserV2: + sock, err = usernet.Sock(nw.Lima, usernet.QEMUSock) + if err != nil { + return nil, err + } + mac = limayaml.MACAddress(inst.Dir) + case networks.ModeShared, networks.ModeBridged: + socketVMNetInstalled, err := nwCfg.IsDaemonInstalled(networks.SocketVMNet) + if err != nil { + return nil, err + } + if !socketVMNetInstalled { + return nil, errors.New("socket_vmnet is not installed") + } + sock, err = networks.Sock(nw.Lima) + if err != nil { + return nil, err + } + mac = nw.MACAddress + default: + return nil, fmt.Errorf("invalid network spec %+v", nw) + } + } else if nw.Socket != "" { + sock = nw.Socket + mac = nw.MACAddress + } else { + return nil, fmt.Errorf("invalid network spec %+v", nw) + } + + device := fmt.Sprintf("virtio-net,type=unixstream,path=%s,mac=%s", sock, mac) + args = append(args, "--device", device) + } + + if len(args) == 0 { + return args, errors.New("no socket_vmnet networks defined") + } + + return args, nil +} + +func EnsureDisk(ctx context.Context, inst *limatype.Instance) error { + diffDisk := filepath.Join(inst.Dir, filenames.DiffDisk) + if _, err := os.Stat(diffDisk); err == nil || !errors.Is(err, os.ErrNotExist) { + // disk is already ensured + return err + } + + diskUtil := proxyimgutil.NewDiskUtil(ctx) + + baseDisk := filepath.Join(inst.Dir, filenames.BaseDisk) + + diskSize, _ := units.RAMInBytes(*inst.Config.Disk) + if diskSize == 0 { + return nil + } + isBaseDiskISO, err := iso9660util.IsISO9660(baseDisk) + if err != nil { + return err + } + if isBaseDiskISO { + // Create an empty data volume (sparse) + diffDiskF, err := os.Create(diffDisk) + if err != nil { + return err + } + + err = diskUtil.MakeSparse(ctx, diffDiskF, 0) + if err != nil { + diffDiskF.Close() + return fmt.Errorf("failed to create sparse diff disk %q: %w", diffDisk, err) + } + return diffDiskF.Close() + } + + // Krunkit also supports qcow2 disks but raw is faster to create and use. + if err = diskUtil.ConvertToRaw(ctx, baseDisk, diffDisk, &diskSize, false); err != nil { + return fmt.Errorf("failed to convert %q to a raw disk %q: %w", baseDisk, diffDisk, err) + } + return err +} + +func startUsernet(ctx context.Context, inst *limatype.Instance) (*usernet.Client, context.CancelFunc, error) { + if firstUsernetIndex := limayaml.FirstUsernetIndex(inst.Config); firstUsernetIndex != -1 { + return usernet.NewClientByName(inst.Config.Networks[firstUsernetIndex].Lima), nil, nil + } + // Start a in-process gvisor-tap-vsock + endpointSock, err := usernet.SockWithDirectory(inst.Dir, "", usernet.EndpointSock) + if err != nil { + return nil, nil, err + } + krunkitSock, err := usernet.SockWithDirectory(inst.Dir, "", usernet.FDSock) + if err != nil { + return nil, nil, err + } + os.RemoveAll(endpointSock) + os.RemoveAll(krunkitSock) + ctx, cancel := context.WithCancel(ctx) + err = usernet.StartGVisorNetstack(ctx, &usernet.GVisorNetstackOpts{ + MTU: 1500, + Endpoint: endpointSock, + FdSocket: krunkitSock, + Async: true, + DefaultLeases: map[string]string{ + networks.SlirpIPAddress: limayaml.MACAddress(inst.Dir), + }, + Subnet: networks.SlirpNetwork, + }) + if err != nil { + defer cancel() + return nil, nil, err + } + subnetIP, _, err := net.ParseCIDR(networks.SlirpNetwork) + return usernet.NewClient(endpointSock, subnetIP), cancel, err +} diff --git a/pkg/driver/krunkit/krunkit_driver_darwin_arm64.go b/pkg/driver/krunkit/krunkit_driver_darwin_arm64.go new file mode 100644 index 00000000000..9a712220da2 --- /dev/null +++ b/pkg/driver/krunkit/krunkit_driver_darwin_arm64.go @@ -0,0 +1,355 @@ +// SPDX-FileCopyrightText: Copyright The Lima Authors +// SPDX-License-Identifier: Apache-2.0 + +package krunkit + +import ( + "context" + _ "embed" + "errors" + "fmt" + "net" + "os" + "os/exec" + "path/filepath" + "strings" + "syscall" + "time" + + "github.com/coreos/go-semver/semver" + "github.com/sirupsen/logrus" + + "github.com/lima-vm/lima/v2/pkg/driver" + "github.com/lima-vm/lima/v2/pkg/executil" + "github.com/lima-vm/lima/v2/pkg/limatype" + "github.com/lima-vm/lima/v2/pkg/limatype/filenames" + "github.com/lima-vm/lima/v2/pkg/limayaml" + "github.com/lima-vm/lima/v2/pkg/networks/usernet" + "github.com/lima-vm/lima/v2/pkg/osutil" + "github.com/lima-vm/lima/v2/pkg/ptr" +) + +type LimaKrunkitDriver struct { + Instance *limatype.Instance + SSHLocalPort int + + usernetClient *usernet.Client + stopUsernet context.CancelFunc + krunkitCmd *exec.Cmd + krunkitWaitCh chan error +} + +type KrunkitOpts struct { + GPUAccel *bool `yaml:"gpuAccel,omitempty"` +} + +func NewKrunkitOpts(cfg *limatype.LimaYAML) (*KrunkitOpts, error) { + var krunkitOpts KrunkitOpts + if err := limayaml.Convert(cfg.VMOpts[vmType], &krunkitOpts, "vmOpts.krunkit"); err != nil { + return nil, err + } + + return &krunkitOpts, nil +} + +var ( + _ driver.Driver = (*LimaKrunkitDriver)(nil) + vmType limatype.VMType = "krunkit" + + //go:embed hack/install-vulkan-gpu.sh + gpuProvisionScript string +) + +func New() *LimaKrunkitDriver { + return &LimaKrunkitDriver{} +} + +func (l *LimaKrunkitDriver) Configure(inst *limatype.Instance) *driver.ConfiguredDriver { + l.Instance = inst + l.SSHLocalPort = inst.SSHLocalPort + + return &driver.ConfiguredDriver{ + Driver: l, + } +} + +func (l *LimaKrunkitDriver) CreateDisk(ctx context.Context) error { + return EnsureDisk(ctx, l.Instance) +} + +func (l *LimaKrunkitDriver) Start(ctx context.Context) (chan error, error) { + var err error + l.usernetClient, l.stopUsernet, err = startUsernet(ctx, l.Instance) + if err != nil { + return nil, fmt.Errorf("failed to start usernet: %w", err) + } + + krunkitCmd, err := Cmdline(l.Instance) + if err != nil { + return nil, fmt.Errorf("failed to construct krunkit command line: %w", err) + } + // Detach krunkit process from parent Lima process + krunkitCmd.SysProcAttr = executil.BackgroundSysProcAttr + + logPath := filepath.Join(l.Instance.Dir, "krunkit.log") + logfile, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644) + if err != nil { + return nil, fmt.Errorf("failed to open krunkit logfile: %w", err) + } + krunkitCmd.Stderr = logfile + + logrus.Infof("Starting krun VM (hint: to watch the progress, see %q)", logPath) + logrus.Infof("krunkitCmd.Args: %v", krunkitCmd.Args) + + if err := krunkitCmd.Start(); err != nil { + logfile.Close() + return nil, errors.New("failed to start krunkitCmd") + } + + pidPath := filepath.Join(l.Instance.Dir, filenames.PIDFile(*l.Instance.Config.VMType)) + if err := os.WriteFile(pidPath, []byte(fmt.Sprintf("%d\n", krunkitCmd.Process.Pid)), 0o644); err != nil { + logrus.WithError(err).Warn("Failed to write PID file") + } + + l.krunkitCmd = krunkitCmd + l.krunkitWaitCh = make(chan error, 1) + go func() { + defer func() { + logfile.Close() + os.RemoveAll(pidPath) + close(l.krunkitWaitCh) + }() + l.krunkitWaitCh <- krunkitCmd.Wait() + }() + + err = l.usernetClient.ConfigureDriver(ctx, l.Instance, l.SSHLocalPort) + if err != nil { + l.krunkitWaitCh <- fmt.Errorf("failed to configure usernet: %w", err) + } + + return l.krunkitWaitCh, nil +} + +func (l *LimaKrunkitDriver) Stop(_ context.Context) error { + if l.krunkitCmd == nil { + return nil + } + + if err := l.krunkitCmd.Process.Signal(syscall.SIGTERM); err != nil { + logrus.WithError(err).Warn("Failed to send interrupt signal") + } + + go func() { + if l.usernetClient != nil { + _ = l.usernetClient.UnExposeSSH(l.Instance.SSHLocalPort) + } + if l.stopUsernet != nil { + l.stopUsernet() + } + }() + + timeout := time.After(30 * time.Second) + select { + case <-l.krunkitWaitCh: + return nil + case <-timeout: + if err := l.krunkitCmd.Process.Kill(); err != nil { + return err + } + + <-l.krunkitWaitCh + return nil + } +} + +func (l *LimaKrunkitDriver) Validate(_ context.Context) error { + return validateConfig(l.Instance.Config) +} + +func validateConfig(cfg *limatype.LimaYAML) error { + if cfg == nil { + return errors.New("configuration is nil") + } + macOSProductVersion, err := osutil.ProductVersion() + if err != nil { + return err + } + if macOSProductVersion.LessThan(*semver.New("13.0.0")) { + return errors.New("krunkit driver requires macOS 13 or higher to run") + } + if cfg.Arch != nil && !limayaml.IsNativeArch(*cfg.Arch) { + return fmt.Errorf("unsupported arch: %q (krunkit requires native arch)", *cfg.Arch) + } + if _, err := exec.LookPath(vmType); err != nil { + return errors.New("krunkit CLI not found in PATH. Install it via:\nbrew tap slp/krunkit\nbrew install krunkit") + } + + if cfg.MountType != nil && (*cfg.MountType != limatype.VIRTIOFS && *cfg.MountType != limatype.REVSSHFS) { + return fmt.Errorf("field `mountType` must be %q or %q for krunkit driver, got %q", limatype.VIRTIOFS, limatype.REVSSHFS, *cfg.MountType) + } + + // If GPU acceleration is requested, ensure Fedora image/template is used + krunkitOpts, err := NewKrunkitOpts(cfg) + if err != nil { + return err + } + if krunkitOpts.GPUAccel != nil && *krunkitOpts.GPUAccel { + if !isFedoraConfigured(cfg) { + logrus.Warn("gpuAccel: true requires a Fedora image (use a Fedora base template or image)") + } + } + + return nil +} + +func isFedoraConfigured(cfg *limatype.LimaYAML) bool { + for _, b := range cfg.Base { + if strings.Contains(strings.ToLower(b.URL), "fedora") { + return true + } + } + for _, img := range cfg.Images { + if strings.Contains(strings.ToLower(img.Location), "fedora") { + return true + } + } + return false +} + +func (l *LimaKrunkitDriver) FillConfig(_ context.Context, cfg *limatype.LimaYAML, _ string) error { + if cfg.MountType == nil { + cfg.MountType = ptr.Of(limatype.VIRTIOFS) + } else { + *cfg.MountType = limatype.VIRTIOFS + } + + if cfg.Arch == nil { + cfg.Arch = ptr.Of(limatype.AARCH64) + } else { + *cfg.Arch = limatype.AARCH64 + } + + cfg.VMType = ptr.Of(vmType) + + krunkitOpts, err := NewKrunkitOpts(cfg) + if err != nil { + return err + } + + if krunkitOpts.GPUAccel == nil { + krunkitOpts.GPUAccel = ptr.Of(false) + } + + if *krunkitOpts.GPUAccel { + gpuInstallScript := limatype.Provision{ + Mode: limatype.ProvisionModeData, + Script: ptr.Of(gpuProvisionScript), + ProvisionData: limatype.ProvisionData{ + Content: ptr.Of(gpuProvisionScript), + Path: ptr.Of("/usr/local/bin/install-vulkan-gpu.sh"), + Permissions: ptr.Of("0755"), + Overwrite: ptr.Of(false), + Owner: cfg.User.Name, + }, + } + cfg.Provision = append(cfg.Provision, gpuInstallScript) + cfg.Message = "To enable GPU support for krunkit, run the following command inside the VM:\n\033[32msudo install-vulkan-gpu.sh\033[0m\n" + } + + return validateConfig(cfg) +} + +func (l *LimaKrunkitDriver) BootScripts() (map[string][]byte, error) { + // Override default reboot-if-required with a no-op because Fedora does not support this well and + // takes a long time to start up. + krunkitOpts, err := NewKrunkitOpts(l.Instance.Config) + if err != nil { + return nil, err + } + if krunkitOpts.GPUAccel == nil || !*krunkitOpts.GPUAccel { + return nil, nil + } + scripts := map[string][]byte{ + "00-reboot-if-required.sh": []byte(`#!/bin/sh +set -eu +# Disabled by krunkit driver +exit 0 +`), + } + return scripts, nil +} + +func (l *LimaKrunkitDriver) Create(_ context.Context) error { + return nil +} + +func (l *LimaKrunkitDriver) Info() driver.Info { + var info driver.Info + info.Name = vmType + if l.Instance != nil && l.Instance.Dir != "" { + info.InstanceDir = l.Instance.Dir + } + + info.Features = driver.DriverFeatures{ + DynamicSSHAddress: false, + SkipSocketForwarding: false, + CanRunGUI: false, + } + return info +} + +func (l *LimaKrunkitDriver) SSHAddress(_ context.Context) (string, error) { + return "127.0.0.1", nil +} + +func (l *LimaKrunkitDriver) ForwardGuestAgent() bool { + return true +} + +func (l *LimaKrunkitDriver) Delete(_ context.Context) error { + return nil +} + +func (l *LimaKrunkitDriver) InspectStatus(_ context.Context, _ *limatype.Instance) string { + return "" +} + +func (l *LimaKrunkitDriver) RunGUI() error { + return nil +} + +func (l *LimaKrunkitDriver) ChangeDisplayPassword(_ context.Context, _ string) error { + return errUnimplemented +} + +func (l *LimaKrunkitDriver) DisplayConnection(_ context.Context) (string, error) { + return "", errUnimplemented +} + +func (l *LimaKrunkitDriver) CreateSnapshot(_ context.Context, _ string) error { + return errUnimplemented +} + +func (l *LimaKrunkitDriver) ApplySnapshot(_ context.Context, _ string) error { + return errUnimplemented +} + +func (l *LimaKrunkitDriver) DeleteSnapshot(_ context.Context, _ string) error { + return errUnimplemented +} + +func (l *LimaKrunkitDriver) ListSnapshots(_ context.Context) (string, error) { + return "", errUnimplemented +} + +func (l *LimaKrunkitDriver) Register(_ context.Context) error { + return nil +} + +func (l *LimaKrunkitDriver) Unregister(_ context.Context) error { + return nil +} + +func (l *LimaKrunkitDriver) GuestAgentConn(_ context.Context) (net.Conn, string, error) { + return nil, "unix", nil +} diff --git a/templates/experimental/krunkit.yaml b/templates/experimental/krunkit.yaml new file mode 100644 index 00000000000..aa05f89e1bb --- /dev/null +++ b/templates/experimental/krunkit.yaml @@ -0,0 +1,21 @@ +# Use this template only if you want to use GPU(Venus) inside the krunkit VM +# or else Krunkit can also work with the default Lima template. + +vmType: krunkit + +# For AI workloads, at least 4GiB memory and 4 CPUs are recommended. +memory: 4GiB +cpus: 4 +arch: aarch64 + +# Fedora 40+ is preferred because of better support for MESA & Vulkan, +# which is good for GPU acceleration and AI workloads +base: +- template://_images/fedora +- template://_default/mounts + +mountType: virtiofs + +vmOpts: + krunkit: + gpuAccel: true diff --git a/website/content/en/docs/config/vmtype/krunkit.md b/website/content/en/docs/config/vmtype/krunkit.md new file mode 100644 index 00000000000..6d0f48ce5e2 --- /dev/null +++ b/website/content/en/docs/config/vmtype/krunkit.md @@ -0,0 +1,88 @@ +--- +title: Krunkit +weight: 4 +--- + +> Warning +> "krunkit" is experimental + +| ⚡ Requirement | Lima >= 2.0, macOS >= 13 (Ventura+), Apple Silicon (arm64) | +| ------------- | ----------------------------------------------------------- | + +Krunkit runs super‑light VMs on macOS/ARM64 with a focus on GPU access. It builds on [libkrun](https://github.com/containers/libkrun), a library that embeds a VMM so apps can launch processes in a hardware‑isolated VM (HVF on macOS, KVM on Linux). The standout feature is GPU support in the guest via Mesa’s Venus Vulkan driver ([venus](https://docs.mesa3d.org/drivers/venus.html)), enabling Vulkan workloads inside the VM. See the project: [containers/krunkit](https://github.com/containers/krunkit). + +## Install krunkit (host) +```bash +brew tap slp/krunkit +brew install krunkit +``` +For reference: https://github.com/slp/homebrew-krun + + +## Using the driver with Lima +Build the driver binary and point Lima to it. See also [Virtual Machine Drivers](../../dev/drivers). + +```bash +git clone https://github.com/lima-vm/lima && cd lima + +# From the Lima source tree +# is your installation prefix. With Homebrew, use: $(brew --prefix) +go build -o /libexec/lima/lima-driver-krunkit ./cmd/lima-driver-krunkit/main_darwin_arm64.go +limactl info # "vmTypes" should include "krunkit" +``` + + +## Quick start + +- Non‑GPU (general workloads) +```bash +limactl start default --vm-type=krunkit +``` + +- GPU (Vulkan via Venus) + - Recommended distro: Fedora 40+ (smoothest Mesa/Vulkan setup; uses COPR “slp/mesa-krunkit” for patched mesa-vulkan-drivers). + - Start from the krunkit template and follow the logs to complete GPU setup. + +{{< tabpane text=true >}} +{{% tab header="CLI" %}} +```bash +# GPU (Vulkan via Venus on Fedora) +limactl start template:experimental/krunkit +``` +{{% /tab %}} +{{% tab header="YAML" %}} +```yaml +vmType: krunkit + +# For AI workloads, at least 4GiB memory and 4 CPUs are recommended. +memory: 4GiB +cpus: 4 +arch: aarch64 + +# Fedora 40+ is preferred for Mesa & Vulkan (Venus) support +base: +- template://_images/fedora + +mounts: +- location: "~" + writable: true + +mountType: virtiofs + +vmOpts: + krunkit: + gpuAccel: true +``` +{{% /tab %}} +{{< /tabpane >}} + +After the VM is READY, inside the VM: +```bash +sudo install-vulkan-gpu.sh +``` + +## Notes and caveats +- macOS Ventura or later on Apple Silicon is required. +- GPU mode requires a Fedora image/template; Fedora 40+ recommended for Mesa/Vulkan (Venus). +- To verify GPU/Vulkan in the guest, use tools like `vulkaninfo` after running the install script. +- Driver architecture details: see [Virtual Machine Drivers](../../dev/drivers). \ No newline at end of file