-
Notifications
You must be signed in to change notification settings - Fork 712
[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/krunkit" | ||
| ) | ||
|
|
||
| // To be used as an external driver for Lima. | ||
| func main() { | ||
| server.Serve(context.Background(), krunkit.New()) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,210 @@ | ||
| // 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/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/sirupsen/logrus" | ||
| ) | ||
|
|
||
| const logLevelInfo = "3" | ||
|
|
||
| // 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", "none://", | ||
|
|
||
| // First virtio-blk device is the boot disk | ||
| "--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)), | ||
| } | ||
|
|
||
| networkArgs, err := buildNetworkArgs(inst) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to build network arguments: %w", err) | ||
| } | ||
|
|
||
| if *inst.Config.MountType == limatype.VIRTIOFS { | ||
| for i, mount := range inst.Config.Mounts { | ||
| logrus.Infof("Mount: %+v", mount) | ||
| 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(), "krunkit", 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, fmt.Errorf("no socket_vment 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) | ||
| } | ||
|
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 | ||
| } | ||
|
|
||
| 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 | ||
| } | ||
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.
This file needs to be called "main_darwin.go" (not main.go), since krunkit is only implemented for darwin