Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions cmd/lima-driver-krun/main.go
Original file line number Diff line number Diff line change
@@ -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/krun"
)

// To be used as an external driver for Lima.
func main() {
server.Serve(context.Background(), krun.New())

Check failure on line 15 in cmd/lima-driver-krun/main.go

View workflow job for this annotation

GitHub Actions / Vulncheck

undefined: krun.New
}
121 changes: 121 additions & 0 deletions pkg/driver/krun/krun_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// SPDX-FileCopyrightText: Copyright The Lima Authors
// SPDX-License-Identifier: Apache-2.0

package krun
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about the name. The program we use here is named krunkit, and the library is called libkrun. In minikube the driver is called krunkit similar to other drivers (vfkit, qemu). In lima we call the qemu driver qemu, so this driver should call krunkit for consistency.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "Kit" suffix is carried over from Docker, where it was HyperKit and LinuxKit and BuildKit and so on

Copy link
Member

@afbjorklund afbjorklund Oct 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I am not mistaken, "krun" is the name of the container runtime? Like "crun", but using VMs instead.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I am not mistaken, "krun" is the name of the container runtime? Like "crun", but using VMs instead.

Yes, this is correct.


import (
"context"
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"strconv"

"github.com/docker/go-units"

Check failure on line 15 in pkg/driver/krun/krun_darwin.go

View workflow job for this annotation

GitHub Actions / Lint Go (macos-26)

File is not properly formatted (gci)
"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"
)

const (
restfulURI = "krun-restful.sock" // This is for setting/checking the state of the VM
logLevelInfo = "3"
KrunEfi = "krun-efi" // efi variable store
)

// Cmdline constructs the command line arguments for krunkit based on the instance configuration.
func Cmdline(inst *limatype.Instance) (*exec.Cmd, error) {
var args = []string{

Check failure on line 32 in pkg/driver/krun/krun_darwin.go

View workflow job for this annotation

GitHub Actions / Lint Go (macos-26)

File is not properly formatted (gofumpt)
"--memory", strconv.Itoa(2048),
"--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", fmt.Sprintf("unix://%s", restfulSocketPath(inst)),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The restful service is not very useful in krunkit:

  • It always return "running"
  • It may fail to respond if krunkit is terminating
  • You cannot depend on it for stopping the vm since the vm may ignore the ACPI request
  • Sending Stop request send an async event to libkrun and return immediately without waiting for ack from the library

For these reasons in minikube

  • we use --restfu-uri none:// (which is also the default in latest krunkit, which is required to for using --device virtio-net,type=unixstream,path=...).
  • the vm state is the process state if the process exist the vm is running, if the process does not exist the vm is stopped
  • we terminate the vm with SIGTERM

When the service will improved, it can make sense to use it for stopping the service gracefully, and terminate krunkit after a timeout if it failed to stop within a timeout.

Checking the status will always be the existence of the process. If the process exist you cannot start another instance, and if you want to stop it you must terminate the process anyway.

"--bootloader", fmt.Sprintf("efi,variable-store=%s,create", filepath.Join(inst.Dir, KrunEfi)),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not needed - krunkit ignore this parameter. It exists for compatibility with vfkit.

"--device", fmt.Sprintf("virtio-blk,path=%s,format=raw", filepath.Join(inst.Dir, filenames.DiffDisk)),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worth a comment that this is the boot disk. The order of the device matters.

"--device", fmt.Sprintf("virtio-blk,path=%s", filepath.Join(inst.Dir, filenames.CIDataISO)),
}

// TODO: socket_vmnet and ssh not working
networkArgs, err := buildNetworkArgs(inst)
if err != nil {
return nil, fmt.Errorf("failed to build network arguments: %w", err)
}
args = append(args, networkArgs...)

return exec.CommandContext(context.Background(), "krunkit", args...), nil
}

func buildNetworkArgs(inst *limatype.Instance) ([]string, error) {
var args []string
nwCfg, err := networks.LoadConfig()
if err != nil {
return nil, err
}
socketVMNetOk, err := nwCfg.IsDaemonInstalled(networks.SocketVMNet)
if err != nil {
return nil, err
}
if socketVMNetOk {
sock, err := networks.Sock("shared")
if err != nil {
return nil, err
}
networkArg := fmt.Sprintf("virtio-net,type=unixstream,path=%s,mac=%s,offloading=true",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

offloading=true cannot not work with socket_vment since it does not enable offloading in the vment interface, and we really don't want to enable offloading in vment since it lead to horrible performance.
See https://github.com/containers/krunkit/blob/main/docs/usage.md#offloading-performance-implications

Remove the offloating=true parameter to make the network work. I tested it with socket_vment here:
containers/krunkit#63 (comment)

sock,
limayaml.MACAddress(inst.Dir),
)
args = append(args, "--device", networkArg)

return args, nil
}

return args, errors.New("socket_vmnet is not installed")
}

func restfulSocketPath(inst *limatype.Instance) string {
return filepath.Join(inst.Dir, restfulURI)
}

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()
}
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)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

krunkit support qcow2 disks but using raw disk is likely to perform better. We can add a comment here.

return err
}
203 changes: 203 additions & 0 deletions pkg/driver/krun/krun_driver_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
// SPDX-FileCopyrightText: Copyright The Lima Authors
// SPDX-License-Identifier: Apache-2.0

package krun

import (
"context"
"fmt"
"net"
"os"
"os/exec"
"path/filepath"
"time"

"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/ptr"
)

type LimaKrunDriver struct {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need the Lima prefix? Do we use LimaQemuDriver for the qemu driver?

Copy link
Contributor Author

@unsuman unsuman Oct 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we use LimaQemuDriver for the qemu driver?

Yes

Instance *limatype.Instance
SSHLocalPort int

krunCmd *exec.Cmd
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

krunCmd is not a good name since the command is krunkit. This is internal detail not visible to users, but it makes the code more confusing.

krunWaitCh chan error
}

var _ driver.Driver = (*LimaKrunDriver)(nil)

func New() *LimaKrunDriver {
return &LimaKrunDriver{}
}

func (l *LimaKrunDriver) Configure(inst *limatype.Instance) *driver.ConfiguredDriver {
l.Instance = inst
l.SSHLocalPort = inst.SSHLocalPort

return &driver.ConfiguredDriver{
Driver: l,
}
}

func (l *LimaKrunDriver) CreateDisk(ctx context.Context) error {
return EnsureDisk(ctx, l.Instance)
}

func (l *LimaKrunDriver) Start(ctx context.Context) (chan error, error) {

Check failure on line 51 in pkg/driver/krun/krun_driver_darwin.go

View workflow job for this annotation

GitHub Actions / Lint Go (macos-26)

unused-parameter: parameter 'ctx' seems to be unused, consider removing or renaming it as _ (revive)
krunCmd, err := Cmdline(l.Instance)
if err != nil {
return nil, fmt.Errorf("failed to construct krunkit command line: %w", err)
}
krunCmd.SysProcAttr = executil.BackgroundSysProcAttr

logPath := filepath.Join(l.Instance.Dir, "krun.log")
logfile, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)

Check failure on line 59 in pkg/driver/krun/krun_driver_darwin.go

View workflow job for this annotation

GitHub Actions / Lint Go (macos-26)

octalLiteral: use new octal literal style, 0o644 (gocritic)
if err != nil {
return nil, fmt.Errorf("failed to open krunkit logfile: %w", err)
}
krunCmd.Stderr = logfile

logrus.Infof("Starting krun VM (hint: to watch the progress, see %q)", logPath)
logrus.Debugf("krunCmd.Args: %v", krunCmd.Args)

if err := krunCmd.Start(); err != nil {
logfile.Close()
return nil, err
}

pidPath := filepath.Join(l.Instance.Dir, filenames.PIDFile(*l.Instance.Config.VMType))
if err := os.WriteFile(pidPath, []byte(fmt.Sprintf("%d\n", krunCmd.Process.Pid)), 0644); err != nil {

Check failure on line 74 in pkg/driver/krun/krun_driver_darwin.go

View workflow job for this annotation

GitHub Actions / Lint Go (macos-26)

octalLiteral: use new octal literal style, 0o644 (gocritic)
logrus.WithError(err).Warn("Failed to write PID file")
}

l.krunCmd = krunCmd
l.krunWaitCh = make(chan error, 1)
go func() {
defer func() {
logfile.Close()
os.RemoveAll(pidPath)
close(l.krunWaitCh)
}()
l.krunWaitCh <- krunCmd.Wait()
}()

return l.krunWaitCh, nil
}

func (l *LimaKrunDriver) Stop(ctx context.Context) error {

Check failure on line 92 in pkg/driver/krun/krun_driver_darwin.go

View workflow job for this annotation

GitHub Actions / Lint Go (macos-26)

unused-parameter: parameter 'ctx' seems to be unused, consider removing or renaming it as _ (revive)
if l.krunCmd == nil {
return nil
}

if err := l.krunCmd.Process.Signal(os.Interrupt); err != nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use SIGTERM for terminating a process

logrus.WithError(err).Warn("Failed to send interrupt signal")
}

timeout := time.After(30 * time.Second)
select {
case <-l.krunWaitCh:
return nil
case <-timeout:
return l.krunCmd.Process.Kill()
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to wait for the process after killing it. Otherwise it will remain a zombie until this process terminates.

}

func (l *LimaKrunDriver) Delete(ctx context.Context) error {

Check failure on line 110 in pkg/driver/krun/krun_driver_darwin.go

View workflow job for this annotation

GitHub Actions / Lint Go (macos-26)

unused-parameter: parameter 'ctx' seems to be unused, consider removing or renaming it as _ (revive)
return nil
}

func (l *LimaKrunDriver) InspectStatus(ctx context.Context, inst *limatype.Instance) string {

Check failure on line 114 in pkg/driver/krun/krun_driver_darwin.go

View workflow job for this annotation

GitHub Actions / Lint Go (macos-26)

unused-parameter: parameter 'ctx' seems to be unused, consider removing or renaming it as _ (revive)
return ""
}

func (l *LimaKrunDriver) RunGUI() error {
return nil
}

func (l *LimaKrunDriver) ChangeDisplayPassword(ctx context.Context, password string) error {

Check failure on line 122 in pkg/driver/krun/krun_driver_darwin.go

View workflow job for this annotation

GitHub Actions / Lint Go (macos-26)

unused-parameter: parameter 'ctx' seems to be unused, consider removing or renaming it as _ (revive)
return fmt.Errorf("display password change not supported by krun driver")

Check failure on line 123 in pkg/driver/krun/krun_driver_darwin.go

View workflow job for this annotation

GitHub Actions / Lint Go (macos-26)

unnecessary-format: unnecessary use of formatting function "fmt.Errorf", you can replace it with "errors.New" (revive)
}

func (l *LimaKrunDriver) DisplayConnection(ctx context.Context) (string, error) {
return "", fmt.Errorf("display connection not supported by krun driver")
}

func (l *LimaKrunDriver) CreateSnapshot(ctx context.Context, tag string) error {
return fmt.Errorf("snapshots not supported by krun driver")
}

func (l *LimaKrunDriver) ApplySnapshot(ctx context.Context, tag string) error {
return fmt.Errorf("snapshots not supported by krun driver")
}

func (l *LimaKrunDriver) DeleteSnapshot(ctx context.Context, tag string) error {
return fmt.Errorf("snapshots not supported by krun driver")
}

func (l *LimaKrunDriver) ListSnapshots(ctx context.Context) (string, error) {
return "", fmt.Errorf("snapshots not supported by krun driver")
}

func (l *LimaKrunDriver) Register(ctx context.Context) error {
return nil
}

func (l *LimaKrunDriver) Unregister(ctx context.Context) error {
return nil
}

func (l *LimaKrunDriver) ForwardGuestAgent() bool {
return true
}

func (l *LimaKrunDriver) GuestAgentConn(ctx context.Context) (net.Conn, string, error) {
return nil, "", fmt.Errorf("guest agent connection not implemented for krun driver")
}

func (l *LimaKrunDriver) Validate(ctx context.Context) error {
return nil
}

func (l *LimaKrunDriver) FillConfig(ctx context.Context, cfg *limatype.LimaYAML, filePath string) error {
if cfg.MountType == nil {
cfg.MountType = ptr.Of(limatype.VIRTIOFS)
} else {
*cfg.MountType = limatype.VIRTIOFS
}

cfg.VMType = ptr.Of("krun")

return nil
}

func (l *LimaKrunDriver) BootScripts() (map[string][]byte, error) {
return nil, nil
}

func (l *LimaKrunDriver) Create(ctx context.Context) error {
return nil
}

func (l *LimaKrunDriver) Info() driver.Info {
var info driver.Info
info.Name = "krun"
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 *LimaKrunDriver) SSHAddress(ctx context.Context) (string, error) {
return "127.0.0.1", nil
}
10 changes: 10 additions & 0 deletions pkg/driver/krun/register.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// SPDX-FileCopyrightText: Copyright The Lima Authors
// SPDX-License-Identifier: Apache-2.0

package krun

import "github.com/lima-vm/lima/v2/pkg/registry"

func init() {
registry.Register(New())

Check failure on line 9 in pkg/driver/krun/register.go

View workflow job for this annotation

GitHub Actions / Vulncheck

undefined: New

Check failure on line 9 in pkg/driver/krun/register.go

View workflow job for this annotation

GitHub Actions / Cross-compile (NetBSD, DragonFlyBSD)

undefined: New

Check failure on line 9 in pkg/driver/krun/register.go

View workflow job for this annotation

GitHub Actions / Unit tests (1.24.x)

undefined: New

Check failure on line 9 in pkg/driver/krun/register.go

View workflow job for this annotation

GitHub Actions / Unit tests (1.25.x)

undefined: New

Check failure on line 9 in pkg/driver/krun/register.go

View workflow job for this annotation

GitHub Actions / Windows tests (WSL2)

undefined: New
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only needed for built-in internal drivers, it is not needed for external drivers.

}
Loading