From 5295883f4c4a050abb153256037d7a256a4fab49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Bj=C3=B6rklund?= Date: Tue, 3 Sep 2024 18:54:03 +0200 Subject: [PATCH] Add new macOS vfkit driver, like hyperkit and qemu (#19423) * Add new macOS vfkit driver, like hyperkit and qemu It uses the new Virtualization.framework from macOS 11, instead of the older Hypervisor.framework (hvf) in QEMU. * Start vfkit in a new process group * vfkit: Add firewall workaround from minikube * vfkit: Add HostIP implementation for gateway * vfkit: Add driver to extra-disks supported * Add documentation for vfkit driver * vfkit: Add usual integration tests * Update site/content/en/docs/drivers/vfkit.md Co-authored-by: Steven Powell <44844360+spowelljr@users.noreply.github.com> --------- Co-authored-by: Nir Soffer Co-authored-by: Medya Ghazizadeh Co-authored-by: Steven Powell <44844360+spowelljr@users.noreply.github.com> --- cmd/minikube/cmd/start_flags.go | 2 +- hack/jenkins/common.sh | 3 + hack/jenkins/minikube_set_pending.sh | 1 + hack/jenkins/osx_integration_tests_vfkit.sh | 39 ++ pkg/drivers/vfkit/vfkit.go | 533 ++++++++++++++++++++ pkg/minikube/cluster/ip.go | 4 + pkg/minikube/driver/driver.go | 2 + pkg/minikube/driver/driver_darwin.go | 2 + pkg/minikube/registry/drvs/init.go | 1 + pkg/minikube/registry/drvs/vfkit/doc.go | 17 + pkg/minikube/registry/drvs/vfkit/vfkit.go | 92 ++++ site/content/en/docs/drivers/_index.md | 1 + site/content/en/docs/drivers/vfkit.md | 33 ++ 13 files changed, 729 insertions(+), 1 deletion(-) create mode 100755 hack/jenkins/osx_integration_tests_vfkit.sh create mode 100644 pkg/drivers/vfkit/vfkit.go create mode 100644 pkg/minikube/registry/drvs/vfkit/doc.go create mode 100644 pkg/minikube/registry/drvs/vfkit/vfkit.go create mode 100644 site/content/en/docs/drivers/vfkit.md diff --git a/cmd/minikube/cmd/start_flags.go b/cmd/minikube/cmd/start_flags.go index 4f9359bb25b5..5f286db46ea3 100644 --- a/cmd/minikube/cmd/start_flags.go +++ b/cmd/minikube/cmd/start_flags.go @@ -968,7 +968,7 @@ func interpretWaitFlag(cmd cobra.Command) map[string]bool { } func checkExtraDiskOptions(cmd *cobra.Command, driverName string) { - supportedDrivers := []string{driver.HyperKit, driver.KVM2, driver.QEMU2} + supportedDrivers := []string{driver.HyperKit, driver.KVM2, driver.QEMU2, driver.VFKit} if cmd.Flags().Changed(extraDisks) { supported := false diff --git a/hack/jenkins/common.sh b/hack/jenkins/common.sh index 442d84f80414..1a50eea43099 100755 --- a/hack/jenkins/common.sh +++ b/hack/jenkins/common.sh @@ -141,6 +141,9 @@ case "${DRIVER}" in virtualbox) echo "vbox: $(vboxmanage --version)" ;; + vfkit) + echo "vfkit: $(vfkit --version)" + ;; esac echo "" diff --git a/hack/jenkins/minikube_set_pending.sh b/hack/jenkins/minikube_set_pending.sh index dd38c91a3001..70871457f54c 100755 --- a/hack/jenkins/minikube_set_pending.sh +++ b/hack/jenkins/minikube_set_pending.sh @@ -43,6 +43,7 @@ jobs=( 'Docker_Windows' 'Docker_Cloud_Shell' 'QEMU_macOS' + 'VFkit_macOS' ) STARTED_LIST_REMOTE="gs://minikube-builds/logs/${ghprbPullId}/${BUILD_NUMBER}/started_environments.txt" diff --git a/hack/jenkins/osx_integration_tests_vfkit.sh b/hack/jenkins/osx_integration_tests_vfkit.sh new file mode 100755 index 000000000000..4226e27c9eaa --- /dev/null +++ b/hack/jenkins/osx_integration_tests_vfkit.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +# Copyright 2024 The Kubernetes Authors All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +# This script runs the integration tests on an OSX machine for the Hyperkit Driver + +# The script expects the following env variables: +# MINIKUBE_LOCATION: GIT_COMMIT from upstream build. +# COMMIT: Actual commit ID from upstream build +# EXTRA_BUILD_ARGS (optional): Extra args to be passed into the minikube integrations tests +# access_token: The GitHub API access token. Injected by the Jenkins credential provider. + + +set -ex + +ARCH="arm64" +OS="darwin" +DRIVER="vfkit" +JOB_NAME="VFkit_macOS" +EXTRA_TEST_ARGS="" +EXTERNAL="yes" + +brew tap cfergeau/crc +brew install vfkit + +source common.sh diff --git a/pkg/drivers/vfkit/vfkit.go b/pkg/drivers/vfkit/vfkit.go new file mode 100644 index 000000000000..7f3bfd36e9be --- /dev/null +++ b/pkg/drivers/vfkit/vfkit.go @@ -0,0 +1,533 @@ +//go:build darwin + +/* +Copyright 2024 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package vfkit + +import ( + "archive/tar" + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net" + "net/http" + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" + "syscall" + "time" + + "github.com/docker/machine/libmachine/drivers" + "github.com/docker/machine/libmachine/log" + "github.com/docker/machine/libmachine/mcnutils" + "github.com/docker/machine/libmachine/ssh" + "github.com/docker/machine/libmachine/state" + "github.com/pkg/errors" + + "k8s.io/klog/v2" + pkgdrivers "k8s.io/minikube/pkg/drivers" + "k8s.io/minikube/pkg/minikube/exit" + "k8s.io/minikube/pkg/minikube/firewall" + "k8s.io/minikube/pkg/minikube/out" + "k8s.io/minikube/pkg/minikube/reason" + "k8s.io/minikube/pkg/minikube/style" +) + +const ( + isoFilename = "boot2docker.iso" + pidFileName = "vfkit.pid" + sockFilename = "vfkit.sock" + defaultSSHUser = "docker" +) + +// Driver is the machine driver for vfkit (Virtualization.framework) +type Driver struct { + *drivers.BaseDriver + *pkgdrivers.CommonDriver + Boot2DockerURL string + DiskSize int + CPU int + Memory int + Cmdline string + MACAddress string + ExtraDisks int +} + +func NewDriver(hostName, storePath string) drivers.Driver { + return &Driver{ + BaseDriver: &drivers.BaseDriver{ + SSHUser: defaultSSHUser, + MachineName: hostName, + StorePath: storePath, + }, + CommonDriver: &pkgdrivers.CommonDriver{}, + } +} + +func (d *Driver) PreCreateCheck() error { + return nil +} + +func (d *Driver) GetMachineName() string { + return d.MachineName +} + +func (d *Driver) DriverName() string { + return "vfkit" +} + +func (d *Driver) GetSSHHostname() (string, error) { + return d.IPAddress, nil +} + +func (d *Driver) GetSSHKeyPath() string { + return d.ResolveStorePath("id_rsa") +} + +func (d *Driver) GetSSHPort() (int, error) { + if d.SSHPort == 0 { + d.SSHPort = 22 + } + return d.SSHPort, nil +} + +func (d *Driver) GetSSHUsername() string { + if d.SSHUser == "" { + d.SSHUser = defaultSSHUser + } + + return d.SSHUser +} + +func (d *Driver) GetURL() (string, error) { + if _, err := os.Stat(d.pidfilePath()); err != nil { + return "", nil + } + ip, err := d.GetIP() + if err != nil { + log.Warnf("Failed to get IP: %v", err) + return "", err + } + if ip == "" { + return "", nil + } + return fmt.Sprintf("tcp://%s:2376", ip), nil +} + +func (d *Driver) GetIP() (string, error) { + return d.IPAddress, nil +} + +func checkPid(pid int) error { + process, err := os.FindProcess(pid) + if err != nil { + return err + } + return process.Signal(syscall.Signal(0)) +} + +func (d *Driver) GetState() (state.State, error) { + if _, err := os.Stat(d.pidfilePath()); err != nil { + return state.Stopped, nil + } + p, err := os.ReadFile(d.pidfilePath()) + if err != nil { + return state.Error, err + } + pid, err := strconv.Atoi(strings.TrimSpace(string(p))) + if err != nil { + return state.Error, err + } + if err := checkPid(pid); err != nil { + // No pid, remove pidfile + os.Remove(d.pidfilePath()) + return state.Stopped, nil + } + ret, err := d.GetVFKitState() + if err != nil { + return state.Error, err + } + switch ret { + case "running", "VirtualMachineStateRunning": + return state.Running, nil + case "stopped", "VirtualMachineStateStopped": + return state.Stopped, nil + } + return state.None, nil +} + +func (d *Driver) Create() error { + var err error + if d.SSHPort, err = d.GetSSHPort(); err != nil { + return err + } + b2dutils := mcnutils.NewB2dUtils(d.StorePath) + if err := b2dutils.CopyIsoToMachineDir(d.Boot2DockerURL, d.MachineName); err != nil { + return err + } + isoPath := d.ResolveStorePath(isoFilename) + + log.Info("Extracting Kernel...") + if err := d.extractKernel(isoPath); err != nil { + return err + } + + log.Info("Creating SSH key...") + if err := ssh.GenerateSSHKey(d.sshKeyPath()); err != nil { + return err + } + + log.Info("Creating Disk image...") + if err := d.generateDiskImage(d.DiskSize); err != nil { + return err + } + + if d.ExtraDisks > 0 { + log.Info("Creating extra disk images...") + for i := 0; i < d.ExtraDisks; i++ { + path := pkgdrivers.ExtraDiskPath(d.BaseDriver, i) + if err := pkgdrivers.CreateRawDisk(path, d.DiskSize); err != nil { + return err + } + } + } + + log.Info("Starting vfkit VM...") + return d.Start() +} + +func (d *Driver) Start() error { + machineDir := filepath.Join(d.StorePath, "machines", d.GetMachineName()) + + var startCmd []string + + startCmd = append(startCmd, + "--memory", fmt.Sprintf("%d", d.Memory), + "--cpus", fmt.Sprintf("%d", d.CPU), + "--restful-uri", fmt.Sprintf("unix://%s", d.sockfilePath())) + var isoPath = filepath.Join(machineDir, isoFilename) + startCmd = append(startCmd, + "--device", fmt.Sprintf("virtio-blk,path=%s", isoPath)) + + var mac = d.MACAddress + startCmd = append(startCmd, + "--device", fmt.Sprintf("virtio-net,nat,mac=%s", mac)) + + startCmd = append(startCmd, + "--device", "virtio-rng") + + startCmd = append(startCmd, + "--kernel", d.ResolveStorePath("bzimage")) + startCmd = append(startCmd, + "--kernel-cmdline", d.Cmdline) + startCmd = append(startCmd, + "--initrd", d.ResolveStorePath("initrd")) + + for i := 0; i < d.ExtraDisks; i++ { + startCmd = append(startCmd, + "--device", fmt.Sprintf("virtio-blk,path=%s", pkgdrivers.ExtraDiskPath(d.BaseDriver, i))) + } + + startCmd = append(startCmd, + "--device", fmt.Sprintf("virtio-blk,path=%s", d.diskPath())) + + log.Debugf("executing: vfkit %s", strings.Join(startCmd, " ")) + os.Remove(d.sockfilePath()) + cmd := exec.Command("vfkit", startCmd...) + + // Create vfkit in a new process group, so minikube caller can use killpg + // to terminate the entire process group without harming the vfkit process. + cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} + + if err := cmd.Start(); err != nil { + return err + } + pid := cmd.Process.Pid + if err := os.WriteFile(d.pidfilePath(), []byte(fmt.Sprintf("%v", pid)), 0600); err != nil { + return err + } + + // Need to strip 0's + mac = pkgdrivers.TrimMacAddress(mac) + if err := d.setupIP(mac); err != nil { + return err + } + + log.Infof("Waiting for VM to start (ssh -p %d docker@%s)...", d.SSHPort, d.IPAddress) + + return WaitForTCPWithDelay(fmt.Sprintf("%s:%d", d.IPAddress, d.SSHPort), time.Second) +} + +func (d *Driver) setupIP(mac string) error { + var err error + getIP := func() error { + d.IPAddress, err = pkgdrivers.GetIPAddressByMACAddress(mac) + if err != nil { + return errors.Wrap(err, "failed to get IP address") + } + return nil + } + // Implement a retry loop because IP address isn't added to dhcp leases file immediately + for i := 0; i < 60; i++ { + log.Debugf("Attempt %d", i) + err = getIP() + if err == nil { + break + } + time.Sleep(2 * time.Second) + } + + if err == nil { + log.Debugf("IP: %s", d.IPAddress) + return nil + } + if !isBootpdError(err) { + return errors.Wrap(err, "IP address never found in dhcp leases file") + } + if unblockErr := firewall.UnblockBootpd(); unblockErr != nil { + klog.Errorf("failed unblocking bootpd from firewall: %v", unblockErr) + exit.Error(reason.IfBootpdFirewall, "ip not found", err) + } + out.Styled(style.Restarting, "Successfully unblocked bootpd process from firewall, retrying") + return fmt.Errorf("ip not found: %v", err) +} + +func isBootpdError(err error) bool { + return strings.Contains(err.Error(), "could not find an IP address") +} + +func (d *Driver) Stop() error { + if err := d.SetVFKitState("HardStop"); err != nil { + return err + } + return nil +} + +func (d *Driver) Remove() error { + s, err := d.GetState() + if err != nil { + return errors.Wrap(err, "get state") + } + if s == state.Running { + if err := d.Kill(); err != nil { + return errors.Wrap(err, "kill") + } + } + if s != state.Stopped { + if err := d.SetVFKitState("Stop"); err != nil { + return errors.Wrap(err, "quit") + } + } + return nil +} + +func (d *Driver) Restart() error { + s, err := d.GetState() + if err != nil { + return err + } + + if s == state.Running { + if err := d.Stop(); err != nil { + return err + } + } + return d.Start() +} + +func (d *Driver) extractKernel(isoPath string) error { + for _, f := range []struct { + pathInIso string + destPath string + }{ + {"/boot/bzimage", "bzimage"}, + {"/boot/initrd", "initrd"}, + } { + fullDestPath := d.ResolveStorePath(f.destPath) + if err := pkgdrivers.ExtractFile(isoPath, f.pathInIso, fullDestPath); err != nil { + return err + } + } + return nil +} + +func (d *Driver) Kill() error { + if err := d.SetVFKitState("HardStop"); err != nil { + return err + } + return nil +} + +func (d *Driver) StartDocker() error { + return fmt.Errorf("hosts without a driver cannot start docker") +} + +func (d *Driver) StopDocker() error { + return fmt.Errorf("hosts without a driver cannot stop docker") +} + +func (d *Driver) GetDockerConfigDir() string { + return "" +} + +func (d *Driver) Upgrade() error { + return fmt.Errorf("hosts without a driver cannot be upgraded") +} + +func (d *Driver) sshKeyPath() string { + machineDir := filepath.Join(d.StorePath, "machines", d.GetMachineName()) + return filepath.Join(machineDir, "id_rsa") +} + +func (d *Driver) publicSSHKeyPath() string { + return d.sshKeyPath() + ".pub" +} + +func (d *Driver) diskPath() string { + machineDir := filepath.Join(d.StorePath, "machines", d.GetMachineName()) + return filepath.Join(machineDir, "disk.img") +} + +func (d *Driver) sockfilePath() string { + machineDir := filepath.Join(d.StorePath, "machines", d.GetMachineName()) + return filepath.Join(machineDir, sockFilename) +} + +func (d *Driver) pidfilePath() string { + machineDir := filepath.Join(d.StorePath, "machines", d.GetMachineName()) + return filepath.Join(machineDir, pidFileName) +} + +// Make a boot2docker VM disk image. +func (d *Driver) generateDiskImage(size int) error { + log.Debugf("Creating %d MB hard disk image...", size) + + magicString := "boot2docker, please format-me" + + buf := new(bytes.Buffer) + tw := tar.NewWriter(buf) + + // magicString first so the automount script knows to format the disk + file := &tar.Header{Name: magicString, Size: int64(len(magicString))} + if err := tw.WriteHeader(file); err != nil { + return err + } + if _, err := tw.Write([]byte(magicString)); err != nil { + return err + } + // .ssh/key.pub => authorized_keys + file = &tar.Header{Name: ".ssh", Typeflag: tar.TypeDir, Mode: 0700} + if err := tw.WriteHeader(file); err != nil { + return err + } + pubKey, err := os.ReadFile(d.publicSSHKeyPath()) + if err != nil { + return err + } + file = &tar.Header{Name: ".ssh/authorized_keys", Size: int64(len(pubKey)), Mode: 0644} + if err := tw.WriteHeader(file); err != nil { + return err + } + if _, err := tw.Write(pubKey); err != nil { + return err + } + file = &tar.Header{Name: ".ssh/authorized_keys2", Size: int64(len(pubKey)), Mode: 0644} + if err := tw.WriteHeader(file); err != nil { + return err + } + if _, err := tw.Write(pubKey); err != nil { + return err + } + if err := tw.Close(); err != nil { + return err + } + rawFile := d.diskPath() + if err := os.WriteFile(rawFile, buf.Bytes(), 0644); err != nil { + return nil + } + if err := os.Truncate(rawFile, int64(size)*int64(1024*1024)); err != nil { + return nil + } + log.Debugf("DONE writing to %s and %s", rawFile, d.diskPath()) + return nil +} + +func httpUnixClient(path string) http.Client { + return http.Client{ + Transport: &http.Transport{ + DialContext: func(_ context.Context, _, _ string) (net.Conn, error) { + return net.Dial("unix", path) + }, + }, + } +} + +type VMState struct { + State string `json:"state"` +} + +func (d *Driver) GetVFKitState() (string, error) { + httpc := httpUnixClient(d.sockfilePath()) + var vmstate VMState + response, err := httpc.Get("http://_/vm/state") + if err != nil { + return "", err + } + defer response.Body.Close() + err = json.NewDecoder(response.Body).Decode(&vmstate) + if err != nil { + return "", err + } + log.Debugf("get state: %+v", vmstate) + return vmstate.State, nil +} + +func (d *Driver) SetVFKitState(state string) error { + httpc := httpUnixClient(d.sockfilePath()) + var vmstate VMState + vmstate.State = state + data, err := json.Marshal(&vmstate) + if err != nil { + return err + } + _, err = httpc.Post("http://_/vm/state", "application/json", bytes.NewReader(data)) + if err != nil { + return err + } + log.Debugf("set state: %+v", vmstate) + return nil +} + +func WaitForTCPWithDelay(addr string, duration time.Duration) error { + for { + conn, err := net.Dial("tcp", addr) + if err != nil { + continue + } + defer conn.Close() + if _, err := conn.Read(make([]byte, 1)); err != nil && err != io.EOF { + time.Sleep(duration) + continue + } + break + } + return nil +} diff --git a/pkg/minikube/cluster/ip.go b/pkg/minikube/cluster/ip.go index a9a9102c4e5a..1f28d50c2083 100644 --- a/pkg/minikube/cluster/ip.go +++ b/pkg/minikube/cluster/ip.go @@ -139,6 +139,10 @@ func HostIP(host *host.Host, clusterName string) (net.IP, error) { return []byte{}, errors.Wrap(err, "Error converting VM IP address to IPv4 address") } return net.IPv4(vmIP[0], vmIP[1], vmIP[2], byte(1)), nil + case driver.VFKit: + vmIPString, _ := host.Driver.GetIP() + gatewayIPString := vmIPString[:strings.LastIndex(vmIPString, ".")+1] + "1" + return net.ParseIP(gatewayIPString), nil case driver.None: return net.ParseIP("127.0.0.1"), nil default: diff --git a/pkg/minikube/driver/driver.go b/pkg/minikube/driver/driver.go index a24c72aeed02..7f721e5c955f 100644 --- a/pkg/minikube/driver/driver.go +++ b/pkg/minikube/driver/driver.go @@ -61,6 +61,8 @@ const ( HyperV = "hyperv" // Parallels driver Parallels = "parallels" + // VFKit driver + VFKit = "vfkit" // AliasKVM is driver name alias for kvm2 AliasKVM = "kvm" diff --git a/pkg/minikube/driver/driver_darwin.go b/pkg/minikube/driver/driver_darwin.go index cc2270797039..6bf5f20d7c5b 100644 --- a/pkg/minikube/driver/driver_darwin.go +++ b/pkg/minikube/driver/driver_darwin.go @@ -28,6 +28,7 @@ var supportedDrivers = func() []string { // on darwin/arm64 only docker and ssh are supported yet return []string{ QEMU2, + VFKit, Parallels, Docker, Podman, @@ -51,6 +52,7 @@ var supportedDrivers = func() []string { HyperKit, VMware, QEMU2, + VFKit, Docker, Podman, SSH, diff --git a/pkg/minikube/registry/drvs/init.go b/pkg/minikube/registry/drvs/init.go index 63a7f291214f..d4276d23afc3 100644 --- a/pkg/minikube/registry/drvs/init.go +++ b/pkg/minikube/registry/drvs/init.go @@ -27,6 +27,7 @@ import ( _ "k8s.io/minikube/pkg/minikube/registry/drvs/podman" _ "k8s.io/minikube/pkg/minikube/registry/drvs/qemu2" _ "k8s.io/minikube/pkg/minikube/registry/drvs/ssh" + _ "k8s.io/minikube/pkg/minikube/registry/drvs/vfkit" _ "k8s.io/minikube/pkg/minikube/registry/drvs/virtualbox" _ "k8s.io/minikube/pkg/minikube/registry/drvs/vmware" ) diff --git a/pkg/minikube/registry/drvs/vfkit/doc.go b/pkg/minikube/registry/drvs/vfkit/doc.go new file mode 100644 index 000000000000..26f1ffd65af5 --- /dev/null +++ b/pkg/minikube/registry/drvs/vfkit/doc.go @@ -0,0 +1,17 @@ +/* +Copyright 2024 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package vfkit diff --git a/pkg/minikube/registry/drvs/vfkit/vfkit.go b/pkg/minikube/registry/drvs/vfkit/vfkit.go new file mode 100644 index 000000000000..1f2bb8c094dc --- /dev/null +++ b/pkg/minikube/registry/drvs/vfkit/vfkit.go @@ -0,0 +1,92 @@ +//go:build darwin + +/* +Copyright 2024 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package vfkit + +import ( + "crypto/rand" + "fmt" + "os/exec" + + "github.com/docker/machine/libmachine/drivers" + + "k8s.io/minikube/pkg/drivers/vfkit" + "k8s.io/minikube/pkg/minikube/config" + "k8s.io/minikube/pkg/minikube/download" + "k8s.io/minikube/pkg/minikube/driver" + "k8s.io/minikube/pkg/minikube/localpath" + "k8s.io/minikube/pkg/minikube/registry" +) + +const ( + docURL = "https://minikube.sigs.k8s.io/docs/reference/drivers/vfkit/" +) + +func init() { + if err := registry.Register(registry.DriverDef{ + Name: driver.VFKit, + Init: func() drivers.Driver { return vfkit.NewDriver("", "") }, + Config: configure, + Status: status, + Default: true, + Priority: registry.Experimental, + }); err != nil { + panic(fmt.Sprintf("register failed: %v", err)) + } +} + +func configure(cfg config.ClusterConfig, n config.Node) (interface{}, error) { + mac, err := generateMACAddress() + if err != nil { + return nil, fmt.Errorf("generating MAC address: %v", err) + } + + return &vfkit.Driver{ + BaseDriver: &drivers.BaseDriver{ + MachineName: config.MachineName(cfg, n), + StorePath: localpath.MiniPath(), + SSHUser: "docker", + }, + Boot2DockerURL: download.LocalISOResource(cfg.MinikubeISO), + DiskSize: cfg.DiskSize, + Memory: cfg.Memory, + CPU: cfg.CPUs, + MACAddress: mac, + Cmdline: "", + ExtraDisks: cfg.ExtraDisks, + }, nil +} + +func status() registry.State { + _, err := exec.LookPath("vfkit") + if err != nil { + return registry.State{Error: err, Fix: "Run 'brew tap cfergeau/crc && brew install vfkit'", Doc: docURL} + } + return registry.State{Installed: true, Healthy: true, Running: true} +} + +func generateMACAddress() (string, error) { + buf := make([]byte, 6) + if _, err := rand.Read(buf); err != nil { + return "", err + } + // Set local bit, ensure unicast address + buf[0] = (buf[0] | 2) & 0xfe + mac := fmt.Sprintf("%02x:%02x:%02x:%02x:%02x:%02x", buf[0], buf[1], buf[2], buf[3], buf[4], buf[5]) + return mac, nil +} diff --git a/site/content/en/docs/drivers/_index.md b/site/content/en/docs/drivers/_index.md index 9e685323a613..84e0a30ddb48 100644 --- a/site/content/en/docs/drivers/_index.md +++ b/site/content/en/docs/drivers/_index.md @@ -32,6 +32,7 @@ To do so, we use the [Docker Machine](https://github.com/docker/machine) library * [VMware Fusion]({{}}) - VM * [QEMU]({{}}) - VM * [Podman]({{}}) - VM + Container (experimental) +* [VFkit]({{}}) - VM (experimental) * [SSH]({{}}) - remote ssh ## Windows diff --git a/site/content/en/docs/drivers/vfkit.md b/site/content/en/docs/drivers/vfkit.md new file mode 100644 index 000000000000..934db8c39471 --- /dev/null +++ b/site/content/en/docs/drivers/vfkit.md @@ -0,0 +1,33 @@ +--- +title: "vfkit" +weight: 2 +aliases: + - /docs/reference/drivers/vfkit +--- + +## Overview + +[VFKit](https://github.com/crc-org/vfkit) is an open-source program for macOS virtualization, optimized for lightweight virtual machines and container deployment. + +## Issues + +### Other + +* [Full list of open 'vfkit' driver issues](https://github.com/kubernetes/minikube/labels/co%2Fvfkit) + +## Troubleshooting + +### Run with logs + +Run `minikube start --driver vfkit --alsologtostderr -v=7` to debug crashes + +### Upgrade VFKit + +New updates to macOS often require an updated vfkit driver. To upgrade: + +* If Podman Desktop is installed, it also bundles `vfkit` +* If you have Brew Package Manager, run: `brew upgrade vfkit` +* As a final alternative, you install the latest VFKit from [GitHub](https://github.com/crc-org/vfkit/releases) +* To check your current version, run: `vfkit -v` +* If the version didn't change after upgrading verify the correct VFKit is in the path. run: `which vfkit` +