diff --git a/cmd/minikube/cmd/delete.go b/cmd/minikube/cmd/delete.go index 6afb85924b79..dbc4bd47fd97 100644 --- a/cmd/minikube/cmd/delete.go +++ b/cmd/minikube/cmd/delete.go @@ -309,7 +309,7 @@ func deleteProfile(profile *config.Profile) error { return DeletionError{Err: delErr, Errtype: MissingProfile} } - if err == nil && driver.BareMetal(cc.Driver) { + if err == nil && (driver.BareMetal(cc.Driver) || driver.IsSSH(cc.Driver)) { if err := uninstallKubernetes(api, *cc, cc.Nodes[0], viper.GetString(cmdcfg.Bootstrapper)); err != nil { deletionError, ok := err.(DeletionError) if ok { diff --git a/cmd/minikube/cmd/start.go b/cmd/minikube/cmd/start.go index c6fba77971f2..9fd0bb4408c2 100644 --- a/cmd/minikube/cmd/start.go +++ b/cmd/minikube/cmd/start.go @@ -306,7 +306,7 @@ func provisionWithDriver(cmd *cobra.Command, ds registry.DriverState, existing * os.Exit(0) } - if driver.IsVM(driverName) { + if driver.IsVM(driverName) && !driver.IsSSH(driverName) { url, err := download.ISO(viper.GetStringSlice(isoURL), cmd.Flags().Changed(isoURL)) if err != nil { return node.Starter{}, errors.Wrap(err, "Failed to cache ISO") @@ -851,7 +851,7 @@ func validateUser(drvName string) { // memoryLimits returns the amount of memory allocated to the system and hypervisor, the return value is in MiB func memoryLimits(drvName string) (int, int, error) { - info, cpuErr, memErr, diskErr := machine.CachedHostInfo() + info, cpuErr, memErr, diskErr := machine.LocalHostInfo() if cpuErr != nil { klog.Warningf("could not get system cpu info while verifying memory limits, which might be okay: %v", cpuErr) } diff --git a/cmd/minikube/cmd/start_flags.go b/cmd/minikube/cmd/start_flags.go index 8cd3fdc6acc0..4893ab671852 100644 --- a/cmd/minikube/cmd/start_flags.go +++ b/cmd/minikube/cmd/start_flags.go @@ -110,6 +110,12 @@ const ( network = "network" startNamespace = "namespace" trace = "trace" + sshIPAddress = "ssh-ip-address" + sshSSHUser = "ssh-user" + sshSSHKey = "ssh-key" + sshSSHPort = "ssh-port" + defaultSSHUser = "root" + defaultSSHPort = 22 ) var ( @@ -221,6 +227,12 @@ func initNetworkingFlags() { startCmd.Flags().String(serviceCIDR, constants.DefaultServiceCIDR, "The CIDR to be used for service cluster IPs.") startCmd.Flags().StringArrayVar(&config.DockerEnv, "docker-env", nil, "Environment variables to pass to the Docker daemon. (format: key=value)") startCmd.Flags().StringArrayVar(&config.DockerOpt, "docker-opt", nil, "Specify arbitrary flags to pass to the Docker daemon. (format: key=value)") + + // ssh + startCmd.Flags().String(sshIPAddress, "", "IP address (ssh driver only)") + startCmd.Flags().String(sshSSHUser, defaultSSHUser, "SSH user (ssh driver only)") + startCmd.Flags().String(sshSSHKey, "", "SSH key (ssh driver only)") + startCmd.Flags().Int(sshSSHPort, defaultSSHPort, "SSH port (ssh driver only)") } // ClusterFlagValue returns the current cluster name based on flags @@ -335,6 +347,10 @@ func generateClusterConfig(cmd *cobra.Command, existing *config.ClusterConfig, k NatNicType: viper.GetString(natNicType), StartHostTimeout: viper.GetDuration(waitTimeout), ExposedPorts: viper.GetStringSlice(ports), + SSHIPAddress: viper.GetString(sshIPAddress), + SSHUser: viper.GetString(sshSSHUser), + SSHKey: viper.GetString(sshSSHKey), + SSHPort: viper.GetInt(sshSSHPort), KubernetesConfig: config.KubernetesConfig{ KubernetesVersion: k8sVersion, ClusterName: ClusterFlagValue(), diff --git a/pkg/drivers/ssh/ssh.go b/pkg/drivers/ssh/ssh.go new file mode 100644 index 000000000000..d36fd8b073cf --- /dev/null +++ b/pkg/drivers/ssh/ssh.go @@ -0,0 +1,241 @@ +/* +Copyright 2019 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 ssh + +import ( + "fmt" + "net" + "os" + "os/exec" + "path" + "strconv" + "time" + + "github.com/docker/machine/libmachine/drivers" + "github.com/docker/machine/libmachine/engine" + "github.com/docker/machine/libmachine/log" + "github.com/docker/machine/libmachine/mcnutils" + "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/command" + "k8s.io/minikube/pkg/minikube/cruntime" + "k8s.io/minikube/pkg/minikube/sysinit" +) + +// Driver is a driver designed to run kubeadm w/o VM management. +// https://minikube.sigs.k8s.io/docs/reference/drivers/ssh/ +type Driver struct { + *drivers.BaseDriver + *pkgdrivers.CommonDriver + EnginePort int + SSHKey string + runtime cruntime.Manager + exec command.Runner +} + +// Config is configuration for the SSH driver +type Config struct { + MachineName string + StorePath string + ContainerRuntime string +} + +const ( + defaultTimeout = 15 * time.Second +) + +// NewDriver creates and returns a new instance of the driver +func NewDriver(c Config) *Driver { + d := &Driver{ + EnginePort: engine.DefaultPort, + BaseDriver: &drivers.BaseDriver{ + MachineName: c.MachineName, + StorePath: c.StorePath, + }, + } + runner := command.NewSSHRunner(d) + runtime, err := cruntime.New(cruntime.Config{Type: c.ContainerRuntime, Runner: runner}) + // Libraries shouldn't panic, but there is no way for drivers to return error :( + if err != nil { + klog.Fatalf("unable to create container runtime: %v", err) + } + d.runtime = runtime + d.exec = runner + return d +} + +// DriverName returns the name of the driver +func (d *Driver) DriverName() string { + return "ssh" +} + +func (d *Driver) GetSSHHostname() (string, error) { + return d.GetIP() +} + +func (d *Driver) GetSSHUsername() string { + return d.SSHUser +} + +func (d *Driver) GetSSHKeyPath() string { + return d.SSHKeyPath +} + +func (d *Driver) PreCreateCheck() error { + if d.SSHKey != "" { + if _, err := os.Stat(d.SSHKey); os.IsNotExist(err) { + return fmt.Errorf("SSH key does not exist: %q", d.SSHKey) + } + } + + return nil +} + +func (d *Driver) Create() error { + if d.SSHKey == "" { + log.Info("No SSH key specified. Assuming an existing key at the default location.") + } else { + log.Info("Importing SSH key...") + + d.SSHKeyPath = d.ResolveStorePath(path.Base(d.SSHKey)) + if err := copySSHKey(d.SSHKey, d.SSHKeyPath); err != nil { + return err + } + + if err := copySSHKey(d.SSHKey+".pub", d.SSHKeyPath+".pub"); err != nil { + log.Infof("Couldn't copy SSH public key : %s", err) + } + } + + if d.runtime.Name() == "Docker" { + if _, err := d.exec.RunCmd(exec.Command("sudo", "usermod", "-aG", "docker", d.GetSSHUsername())); err != nil { + return errors.Wrap(err, "usermod") + } + } + + log.Debugf("IP: %s", d.IPAddress) + + return nil +} + +func (d *Driver) GetURL() (string, error) { + if err := drivers.MustBeRunning(d); err != nil { + return "", err + } + + ip, err := d.GetIP() + if err != nil { + return "", err + } + + return fmt.Sprintf("tcp://%s", net.JoinHostPort(ip, strconv.Itoa(d.EnginePort))), nil +} + +func (d *Driver) GetState() (state.State, error) { + address := net.JoinHostPort(d.IPAddress, strconv.Itoa(d.SSHPort)) + + _, err := net.DialTimeout("tcp", address, defaultTimeout) + if err != nil { + return state.Stopped, nil + } + + return state.Running, nil +} + +// Start a host +func (d *Driver) Start() error { + return nil +} + +// Stop a host gracefully, including any containers that we are managing. +func (d *Driver) Stop() error { + if err := sysinit.New(d.exec).Stop("kubelet"); err != nil { + klog.Warningf("couldn't stop kubelet. will continue with stop anyways: %v", err) + if err := sysinit.New(d.exec).ForceStop("kubelet"); err != nil { + klog.Warningf("couldn't force stop kubelet. will continue with stop anyways: %v", err) + } + } + containers, err := d.runtime.ListContainers(cruntime.ListOptions{}) + if err != nil { + return errors.Wrap(err, "containers") + } + if len(containers) > 0 { + if err := d.runtime.StopContainers(containers); err != nil { + return errors.Wrap(err, "stop containers") + } + } + klog.Infof("ssh driver is stopped!") + return nil +} + +// Restart a host +func (d *Driver) Restart() error { + if err := sysinit.New(d.exec).Restart("kubelet"); err != nil { + return err + } + return nil +} + +// Kill stops a host forcefully, including any containers that we are managing. +func (d *Driver) Kill() error { + if err := sysinit.New(d.exec).ForceStop("kubelet"); err != nil { + klog.Warningf("couldn't force stop kubelet. will continue with kill anyways: %v", err) + } + + // First try to gracefully stop containers + containers, err := d.runtime.ListContainers(cruntime.ListOptions{}) + if err != nil { + return errors.Wrap(err, "containers") + } + if len(containers) == 0 { + return nil + } + // Try to be graceful before sending SIGKILL everywhere. + if err := d.runtime.StopContainers(containers); err != nil { + return errors.Wrap(err, "stop") + } + + containers, err = d.runtime.ListContainers(cruntime.ListOptions{}) + if err != nil { + return errors.Wrap(err, "containers") + } + if len(containers) == 0 { + return nil + } + if err := d.runtime.KillContainers(containers); err != nil { + return errors.Wrap(err, "kill") + } + return nil +} + +func (d *Driver) Remove() error { + return nil +} + +func copySSHKey(src, dst string) error { + if err := mcnutils.CopyFile(src, dst); err != nil { + return fmt.Errorf("unable to copy ssh key: %s", err) + } + + if err := os.Chmod(dst, 0600); err != nil { + return fmt.Errorf("unable to set permissions on the ssh key: %s", err) + } + + return nil +} diff --git a/pkg/minikube/cluster/ip.go b/pkg/minikube/cluster/ip.go index 891705e60c73..54a715c3bd75 100644 --- a/pkg/minikube/cluster/ip.go +++ b/pkg/minikube/cluster/ip.go @@ -40,6 +40,12 @@ func HostIP(host *host.Host, clusterName string) (net.IP, error) { return oci.RoutableHostIPFromInside(oci.Docker, clusterName, host.Name) case driver.Podman: return oci.RoutableHostIPFromInside(oci.Podman, clusterName, host.Name) + case driver.SSH: + ip, err := host.Driver.GetIP() + if err != nil { + return []byte{}, errors.Wrap(err, "Error getting VM/Host IP address") + } + return net.ParseIP(ip), nil case driver.KVM2: return net.ParseIP("192.168.39.1"), nil case driver.HyperV: diff --git a/pkg/minikube/config/types.go b/pkg/minikube/config/types.go index 00cc1b54d4d3..fe533bf0a7e9 100644 --- a/pkg/minikube/config/types.go +++ b/pkg/minikube/config/types.go @@ -66,6 +66,10 @@ type ClusterConfig struct { HostDNSResolver bool // Only used by virtualbox HostOnlyNicType string // Only used by virtualbox NatNicType string // Only used by virtualbox + SSHIPAddress string // Only used by ssh driver + SSHUser string // Only used by ssh driver + SSHKey string // Only used by ssh driver + SSHPort int // Only used by ssh driver KubernetesConfig KubernetesConfig Nodes []Node Addons map[string]bool diff --git a/pkg/minikube/driver/driver.go b/pkg/minikube/driver/driver.go index 261f8be5db38..4053a9dfef3a 100644 --- a/pkg/minikube/driver/driver.go +++ b/pkg/minikube/driver/driver.go @@ -38,6 +38,8 @@ const ( Mock = "mock" // None driver None = "none" + // SSH driver + SSH = "ssh" // KVM2 driver KVM2 = "kvm2" // VirtualBox driver @@ -55,6 +57,8 @@ const ( // AliasKVM is driver name alias for kvm2 AliasKVM = "kvm" + // AliasSSH is driver name alias for ssh + AliasSSH = "generic" ) var ( @@ -96,6 +100,10 @@ func MachineType(name string) string { return "container" } + if IsSSH(name) { + return "bare metal machine" + } + if IsVM(name) { return "VM" } @@ -143,6 +151,11 @@ func BareMetal(name string) bool { return name == None || name == Mock } +// IsSSH checks if the driver is ssh +func IsSSH(name string) bool { + return name == SSH +} + // NeedsPortForward returns true if driver is unable provide direct IP connectivity func NeedsPortForward(name string) bool { if !IsKIC(name) { diff --git a/pkg/minikube/driver/driver_darwin.go b/pkg/minikube/driver/driver_darwin.go index f951177677c2..1eef33635ddc 100644 --- a/pkg/minikube/driver/driver_darwin.go +++ b/pkg/minikube/driver/driver_darwin.go @@ -26,7 +26,7 @@ var supportedDrivers = []string{ HyperKit, VMware, Docker, - Podman, + SSH, } func VBoxManagePath() string { diff --git a/pkg/minikube/driver/driver_linux.go b/pkg/minikube/driver/driver_linux.go index 16e7b5e706de..a428a9a2bfe0 100644 --- a/pkg/minikube/driver/driver_linux.go +++ b/pkg/minikube/driver/driver_linux.go @@ -29,6 +29,7 @@ var supportedDrivers = []string{ None, Docker, Podman, + SSH, } // VBoxManagePath returns the path to the VBoxManage command diff --git a/pkg/minikube/driver/driver_test.go b/pkg/minikube/driver/driver_test.go index b18fc36cab01..b4fac014eb52 100644 --- a/pkg/minikube/driver/driver_test.go +++ b/pkg/minikube/driver/driver_test.go @@ -65,6 +65,7 @@ func TestMachineType(t *testing.T) { Docker: "container", Mock: "bare metal machine", None: "bare metal machine", + SSH: "bare metal machine", KVM2: "VM", VirtualBox: "VM", HyperKit: "VM", diff --git a/pkg/minikube/driver/driver_windows.go b/pkg/minikube/driver/driver_windows.go index 80ff83b825b2..1ebc9d5f1d62 100644 --- a/pkg/minikube/driver/driver_windows.go +++ b/pkg/minikube/driver/driver_windows.go @@ -33,6 +33,7 @@ var supportedDrivers = []string{ HyperV, VMware, Docker, + SSH, } // TODO: medyagh add same check for kic docker diff --git a/pkg/minikube/machine/info.go b/pkg/minikube/machine/info.go index bc507d9d0bbf..05361182a8e8 100644 --- a/pkg/minikube/machine/info.go +++ b/pkg/minikube/machine/info.go @@ -17,8 +17,11 @@ limitations under the License. package machine import ( + "errors" "io/ioutil" "os/exec" + "strconv" + "strings" "github.com/docker/machine/libmachine/provision" "github.com/shirou/gopsutil/v3/cpu" @@ -39,8 +42,8 @@ type HostInfo struct { DiskSize int64 } -// CachedHostInfo returns system information such as memory,CPU, DiskSize -func CachedHostInfo() (*HostInfo, error, error, error) { +// LocalHostInfo returns system information such as memory,CPU, DiskSize +func LocalHostInfo() (*HostInfo, error, error, error) { var cpuErr, memErr, diskErr error i, cpuErr := cachedCPUInfo() if cpuErr != nil { @@ -63,6 +66,43 @@ func CachedHostInfo() (*HostInfo, error, error, error) { return &info, cpuErr, memErr, diskErr } +// RemoteHostInfo returns system information such as memory,CPU, DiskSize +func RemoteHostInfo(r command.Runner) (*HostInfo, error, error, error) { + rr, cpuErr := r.RunCmd(exec.Command("nproc")) + if cpuErr != nil { + klog.Warningf("Unable to get CPU info: %v", cpuErr) + } + nproc := rr.Stdout.String() + ncpus, err := strconv.Atoi(strings.TrimSpace(nproc)) + if err != nil { + klog.Warningf("Failed to parse CPU info: %v", err) + } + rr, memErr := r.RunCmd(exec.Command("free", "-m")) + if memErr != nil { + klog.Warningf("Unable to get mem info: %v", memErr) + } + free := rr.Stdout.String() + memory, err := parseMemFree(free) + if err != nil { + klog.Warningf("Unable to parse mem info: %v", err) + } + rr, diskErr := r.RunCmd(exec.Command("df", "-m")) + if diskErr != nil { + klog.Warningf("Unable to get disk info: %v", diskErr) + } + df := rr.Stdout.String() + disksize, err := parseDiskFree(df) + if err != nil { + klog.Warningf("Unable to parse disk info: %v", err) + } + + var info HostInfo + info.CPUs = ncpus + info.Memory = memory + info.DiskSize = disksize + return &info, cpuErr, memErr, diskErr +} + // showLocalOsRelease shows systemd information about the current linux distribution, on the local host func showLocalOsRelease() { osReleaseOut, err := ioutil.ReadFile("/etc/os-release") @@ -150,3 +190,50 @@ func cachedCPUInfo() ([]cpu.InfoStat, error) { } return *cachedCPU, *cachedCPUErr } + +// ParseMemFree parses the output of the `free -m` command +func parseMemFree(out string) (int64, error) { + // total used free shared buff/cache available + //Mem: 1987 706 194 1 1086 1173 + //Swap: 0 0 0 + outlines := strings.Split(out, "\n") + l := len(outlines) + for _, line := range outlines[1 : l-1] { + parsedLine := strings.Fields(line) + if len(parsedLine) < 7 { + continue + } + t, err := strconv.ParseInt(parsedLine[1], 10, 64) + if err != nil { + return 0, err + } + m := strings.Trim(parsedLine[0], ":") + if m == "Mem" { + return t, nil + } + } + return 0, errors.New("no matching data found") +} + +// ParseDiskFree parses the output of the `df -m` command +func parseDiskFree(out string) (int64, error) { + // Filesystem 1M-blocks Used Available Use% Mounted on + // /dev/sda1 39643 3705 35922 10% / + outlines := strings.Split(out, "\n") + l := len(outlines) + for _, line := range outlines[1 : l-1] { + parsedLine := strings.Fields(line) + if len(parsedLine) < 6 { + continue + } + t, err := strconv.ParseInt(parsedLine[1], 10, 64) + if err != nil { + return 0, err + } + m := parsedLine[5] + if m == "/" { + return t, nil + } + } + return 0, errors.New("no matching data found") +} diff --git a/pkg/minikube/machine/machine.go b/pkg/minikube/machine/machine.go index d382041f6a04..a465694e0bdb 100644 --- a/pkg/minikube/machine/machine.go +++ b/pkg/minikube/machine/machine.go @@ -104,7 +104,7 @@ func fastDetectProvisioner(h *host.Host) (libprovision.Provisioner, error) { switch { case driver.IsKIC(d): return provision.NewUbuntuProvisioner(h.Driver), nil - case driver.BareMetal(d): + case driver.BareMetal(d), driver.IsSSH(d): return libprovision.DetectProvisioner(h.Driver) default: return provision.NewBuildrootProvisioner(h.Driver), nil diff --git a/pkg/minikube/machine/start.go b/pkg/minikube/machine/start.go index 301286257bf8..2fa43e58b6f4 100644 --- a/pkg/minikube/machine/start.go +++ b/pkg/minikube/machine/start.go @@ -129,7 +129,10 @@ func createHost(api libmachine.API, cfg *config.ClusterConfig, n *config.Node) ( klog.Infof("duration metric: createHost completed in %s", time.Since(start)) }() - showHostInfo(*cfg) + if cfg.Driver != driver.SSH { + showHostInfo(nil, *cfg) + } + def := registry.Driver(cfg.Driver) if def.Empty() { return nil, fmt.Errorf("unsupported/missing driver: %s", cfg.Driver) @@ -163,6 +166,9 @@ func createHost(api libmachine.API, cfg *config.ClusterConfig, n *config.Node) ( return nil, errors.Wrap(err, "creating host") } klog.Infof("duration metric: libmachine.API.Create for %q took %s", cfg.Name, time.Since(cstart)) + if cfg.Driver == driver.SSH { + showHostInfo(h, *cfg) + } if err := postStartSetup(h, *cfg); err != nil { return h, errors.Wrap(err, "post-start") @@ -283,7 +289,7 @@ func postStartSetup(h *host.Host, mc config.ClusterConfig) error { if driver.BareMetal(mc.Driver) { showLocalOsRelease() } - if driver.IsVM(mc.Driver) || driver.IsKIC(mc.Driver) { + if driver.IsVM(mc.Driver) || driver.IsKIC(mc.Driver) || driver.IsSSH(mc.Driver) { logRemoteOsRelease(r) } return syncLocalAssets(r) @@ -314,16 +320,29 @@ func acquireMachinesLock(name string, drv string) (mutex.Releaser, error) { } // showHostInfo shows host information -func showHostInfo(cfg config.ClusterConfig) { +func showHostInfo(h *host.Host, cfg config.ClusterConfig) { machineType := driver.MachineType(cfg.Driver) if driver.BareMetal(cfg.Driver) { - info, cpuErr, memErr, DiskErr := CachedHostInfo() + info, cpuErr, memErr, DiskErr := LocalHostInfo() if cpuErr == nil && memErr == nil && DiskErr == nil { register.Reg.SetStep(register.RunningLocalhost) out.Step(style.StartingNone, "Running on localhost (CPUs={{.number_of_cpus}}, Memory={{.memory_size}}MB, Disk={{.disk_size}}MB) ...", out.V{"number_of_cpus": info.CPUs, "memory_size": info.Memory, "disk_size": info.DiskSize}) } return } + if driver.IsSSH(cfg.Driver) { + r, err := CommandRunner(h) + if err != nil { + klog.Warningf("error getting command runner: %v", err) + return + } + info, cpuErr, memErr, DiskErr := RemoteHostInfo(r) + if cpuErr == nil && memErr == nil && DiskErr == nil { + register.Reg.SetStep(register.RunningRemotely) + out.Step(style.StartingSSH, "Running remotely (CPUs={{.number_of_cpus}}, Memory={{.memory_size}}MB, Disk={{.disk_size}}MB) ...", out.V{"number_of_cpus": info.CPUs, "memory_size": info.Memory, "disk_size": info.DiskSize}) + } + return + } if driver.IsKIC(cfg.Driver) { // TODO:medyagh add free disk space on docker machine register.Reg.SetStep(register.CreatingContainer) out.Step(style.StartingVM, "Creating {{.driver_name}} {{.machine_type}} (CPUs={{.number_of_cpus}}, Memory={{.memory_size}}MB) ...", out.V{"driver_name": cfg.Driver, "number_of_cpus": cfg.CPUs, "memory_size": cfg.Memory, "machine_type": machineType}) diff --git a/pkg/minikube/out/register/register.go b/pkg/minikube/out/register/register.go index ebb6c13a4cd7..6b810b0e3059 100644 --- a/pkg/minikube/out/register/register.go +++ b/pkg/minikube/out/register/register.go @@ -32,6 +32,7 @@ const ( StartingNode RegStep = "Starting Node" PullingBaseImage RegStep = "Pulling Base Image" RunningLocalhost RegStep = "Running on Localhost" + RunningRemotely RegStep = "Running Remotely" LocalOSRelease RegStep = "Local OS Release" CreatingContainer RegStep = "Creating Container" CreatingVM RegStep = "Creating VM" @@ -79,6 +80,7 @@ func init() { LocalOSRelease, CreatingContainer, CreatingVM, + RunningRemotely, PreparingKubernetes, PreparingKubernetesCerts, PreparingKubernetesControlPlane, diff --git a/pkg/minikube/registry/drvs/init.go b/pkg/minikube/registry/drvs/init.go index bca12775c3ff..06b11158273f 100644 --- a/pkg/minikube/registry/drvs/init.go +++ b/pkg/minikube/registry/drvs/init.go @@ -25,6 +25,7 @@ import ( _ "k8s.io/minikube/pkg/minikube/registry/drvs/none" _ "k8s.io/minikube/pkg/minikube/registry/drvs/parallels" _ "k8s.io/minikube/pkg/minikube/registry/drvs/podman" + _ "k8s.io/minikube/pkg/minikube/registry/drvs/ssh" _ "k8s.io/minikube/pkg/minikube/registry/drvs/virtualbox" _ "k8s.io/minikube/pkg/minikube/registry/drvs/vmware" _ "k8s.io/minikube/pkg/minikube/registry/drvs/vmwarefusion" diff --git a/pkg/minikube/registry/drvs/ssh/ssh.go b/pkg/minikube/registry/drvs/ssh/ssh.go new file mode 100644 index 000000000000..4d7446d6265f --- /dev/null +++ b/pkg/minikube/registry/drvs/ssh/ssh.go @@ -0,0 +1,73 @@ +/* +Copyright 2019 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 ssh + +import ( + "fmt" + + "github.com/docker/machine/libmachine/drivers" + "github.com/pkg/errors" + + "k8s.io/minikube/pkg/drivers/ssh" + "k8s.io/minikube/pkg/minikube/config" + "k8s.io/minikube/pkg/minikube/driver" + "k8s.io/minikube/pkg/minikube/localpath" + "k8s.io/minikube/pkg/minikube/registry" +) + +func init() { + err := registry.Register(registry.DriverDef{ + Name: driver.SSH, + Alias: []string{driver.AliasSSH}, + Config: configure, + Status: status, + Priority: registry.Fallback, + Init: func() drivers.Driver { return ssh.NewDriver(ssh.Config{}) }, + }) + if err != nil { + panic(fmt.Sprintf("unable to register: %v", err)) + } +} + +func configure(cc config.ClusterConfig, n config.Node) (interface{}, error) { + d := ssh.NewDriver(ssh.Config{ + MachineName: config.MachineName(cc, n), + StorePath: localpath.MiniPath(), + ContainerRuntime: cc.KubernetesConfig.ContainerRuntime, + }) + + if cc.SSHIPAddress == "" { + return nil, errors.Errorf("please provide an IP address") + } + + // We don't want the API server listening on loopback interface, + // even if we might use a tunneled VM port for the SSH service + if cc.SSHIPAddress == "127.0.0.1" || cc.SSHIPAddress == "localhost" { + return nil, errors.Errorf("please provide real IP address") + } + + d.IPAddress = cc.SSHIPAddress + d.SSHUser = cc.SSHUser + d.SSHKey = cc.SSHKey + d.SSHPort = cc.SSHPort + + return d, nil +} + +func status() registry.State { + return registry.State{Installed: true, Healthy: true} +} diff --git a/pkg/minikube/style/style.go b/pkg/minikube/style/style.go index 6707ab4fe1e1..3adc368609cd 100644 --- a/pkg/minikube/style/style.go +++ b/pkg/minikube/style/style.go @@ -126,6 +126,7 @@ var Config = map[Enum]Options{ Resetting: {Prefix: "๐Ÿ”„ "}, Shutdown: {Prefix: "๐Ÿ›‘ "}, StartingNone: {Prefix: "๐Ÿคน "}, + StartingSSH: {Prefix: "๐Ÿ”— "}, StartingVM: {Prefix: "๐Ÿ”ฅ ", OmitNewline: true, Spinner: true}, SubStep: {Prefix: " โ–ช ", LowPrefix: LowIndent, OmitNewline: true, Spinner: true}, // Indented bullet Tip: {Prefix: "๐Ÿ’ก "}, diff --git a/pkg/minikube/style/style_enum.go b/pkg/minikube/style/style_enum.go index 63a6c49e9fc7..7647fba2ea6d 100644 --- a/pkg/minikube/style/style_enum.go +++ b/pkg/minikube/style/style_enum.go @@ -81,6 +81,7 @@ const ( Shutdown Sparkle StartingNone + StartingSSH StartingVM Stopped Stopping diff --git a/site/content/en/docs/commands/start.md b/site/content/en/docs/commands/start.md index fe1ae8953982..13505e4190d3 100644 --- a/site/content/en/docs/commands/start.md +++ b/site/content/en/docs/commands/start.md @@ -88,6 +88,10 @@ minikube start [flags] --preload If set, download tarball of preloaded images if available to improve start time. Defaults to true. (default true) --registry-mirror strings Registry mirrors to pass to the Docker daemon --service-cluster-ip-range string The CIDR to be used for service cluster IPs. (default "10.96.0.0/12") + --ssh-ip-address string IP address (ssh driver only) + --ssh-key string SSH key (ssh driver only) + --ssh-port int SSH port (ssh driver only) (default 22) + --ssh-user string SSH user (ssh driver only) (default "root") --trace string Send trace events. Options include: [gcp] --uuid string Provide VM UUID to restore MAC address (hyperkit driver only) --vm Filter to use only VM Drivers diff --git a/site/content/en/docs/drivers/includes/ssh_usage.inc b/site/content/en/docs/drivers/includes/ssh_usage.inc new file mode 100644 index 000000000000..608383fe81cc --- /dev/null +++ b/site/content/en/docs/drivers/includes/ssh_usage.inc @@ -0,0 +1,25 @@ +## Requirements + +A Linux VM with the following: + +* systemd or OpenRC +* a container runtime, such as Docker or CRIO + +This VM must also meet the [kubeadm requirements](https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/), such as: + +* 2 CPU's +* 2GB RAM +* iptables (in legacy mode) +* conntrack +* crictl +* SELinux permissive +* cgroups v1 (v2 is not yet supported by Kubernetes) + +## Usage + +The ssh driver requires the IP address of the VM to use. + +```shell +minikube start --driver=ssh --ssh-ip-address=vm.example.com +``` + diff --git a/site/content/en/docs/drivers/ssh.md b/site/content/en/docs/drivers/ssh.md new file mode 100644 index 000000000000..50777aed6ae3 --- /dev/null +++ b/site/content/en/docs/drivers/ssh.md @@ -0,0 +1,22 @@ +--- +title: "ssh" +weight: 3 +description: > + Linux ssh (remote) driver +aliases: + - /docs/reference/drivers/ssh +--- + +## Overview + +This document is written for system integrators who wish to run minikube within a customized VM environment. The `ssh` driver allows advanced minikube users to skip VM creation, allowing minikube to be run on a user-supplied VM. + +{{% readfile file="/docs/drivers/includes/ssh_usage.inc" %}} + +## Issues + +* [Full list of open 'ssh' driver issues](https://github.com/kubernetes/minikube/labels/co%2Fssh-driver) + +## Troubleshooting + +* Run `minikube start --alsologtostderr -v=4` to debug crashes