-
Notifications
You must be signed in to change notification settings - Fork 705
[WIP]: Support for libkrun
using krunkit
#4137
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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()) | ||
} |
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 | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
"os" | ||
"os/exec" | ||
"path/filepath" | ||
"strconv" | ||
|
||
"github.com/docker/go-units" | ||
"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{ | ||
"--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)), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The restful service is not very useful in krunkit:
For these reasons in minikube
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)), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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)), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. Remove the offloating=true parameter to make the network work. I tested it with socket_vment here: |
||
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) | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
} |
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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yes |
||
Instance *limatype.Instance | ||
SSHLocalPort int | ||
|
||
krunCmd *exec.Cmd | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) { | ||
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) | ||
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 { | ||
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 { | ||
if l.krunCmd == nil { | ||
return nil | ||
} | ||
|
||
if err := l.krunCmd.Process.Signal(os.Interrupt); err != nil { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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() | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 { | ||
return nil | ||
} | ||
|
||
func (l *LimaKrunDriver) InspectStatus(ctx context.Context, inst *limatype.Instance) string { | ||
return "" | ||
} | ||
|
||
func (l *LimaKrunDriver) RunGUI() error { | ||
return nil | ||
} | ||
|
||
func (l *LimaKrunDriver) ChangeDisplayPassword(ctx context.Context, password string) error { | ||
return fmt.Errorf("display password change not supported by krun driver") | ||
} | ||
|
||
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 | ||
} |
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
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
} |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this is correct.