Skip to content

Commit

Permalink
minikube tunnel (#3015)
Browse files Browse the repository at this point in the history
This commit introduces a new command, `minikube tunnel`, a LoadBalancer emulator functionality, that must be run with root permissions.

This command:

* Establishes networking routes from the host into the VM for all IP ranges used by Kubernetes.
* Enables a cluster controller that allocates IPs to services external `LoadBalancer` IPs.
* Cleans up routes and IPs when stopped (Ctrl+C), when `minikube` stops, and when `minikube tunnel` is ran with the `--cleanup` flag
  • Loading branch information
balopat authored Oct 18, 2018
1 parent e77d95a commit ae9f4b2
Show file tree
Hide file tree
Showing 47 changed files with 4,584 additions and 146 deletions.
2 changes: 1 addition & 1 deletion cmd/minikube/cmd/config/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ func EnableOrDisableAddon(name string, val string) error {
if err != nil {
return err
}
host, err := cluster.CheckIfApiExistsAndLoad(api)
host, err := cluster.CheckIfHostExistsAndLoad(api, config.GetMachineName())
if err != nil {
return errors.Wrap(err, "getting host")
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/minikube/cmd/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ var dockerEnvCmd = &cobra.Command{
os.Exit(1)
}
defer api.Close()
host, err := cluster.CheckIfApiExistsAndLoad(api)
host, err := cluster.CheckIfHostExistsAndLoad(api, config.GetMachineName())
if err != nil {
fmt.Fprintf(os.Stderr, "Error getting host: %s\n", err)
os.Exit(1)
Expand Down
14 changes: 9 additions & 5 deletions cmd/minikube/cmd/env_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,12 @@ func (f FakeNoProxyGetter) GetNoProxyVar() (string, string) {
}

var defaultAPI = &tests.MockAPI{
Hosts: map[string]*host.Host{
config.GetMachineName(): {
Name: config.GetMachineName(),
Driver: &tests.MockDriver{},
FakeStore: tests.FakeStore{
Hosts: map[string]*host.Host{
config.GetMachineName(): {
Name: config.GetMachineName(),
Driver: &tests.MockDriver{},
},
},
},
}
Expand Down Expand Up @@ -81,7 +83,9 @@ func TestShellCfgSet(t *testing.T) {
{
description: "no host specified",
api: &tests.MockAPI{
Hosts: make(map[string]*host.Host),
FakeStore: tests.FakeStore{
Hosts: make(map[string]*host.Host),
},
},
shell: "bash",
expectedShellCfg: nil,
Expand Down
3 changes: 2 additions & 1 deletion cmd/minikube/cmd/ssh.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"github.com/pkg/errors"
"github.com/spf13/cobra"
"k8s.io/minikube/pkg/minikube/cluster"
"k8s.io/minikube/pkg/minikube/config"
"k8s.io/minikube/pkg/minikube/machine"
)

Expand All @@ -39,7 +40,7 @@ var sshCmd = &cobra.Command{
os.Exit(1)
}
defer api.Close()
host, err := cluster.CheckIfApiExistsAndLoad(api)
host, err := cluster.CheckIfHostExistsAndLoad(api, config.GetMachineName())
if err != nil {
fmt.Fprintf(os.Stderr, "Error getting host: %s\n", err)
os.Exit(1)
Expand Down
22 changes: 1 addition & 21 deletions cmd/minikube/cmd/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ func runStart(cmd *cobra.Command, args []string) {
selectedKubernetesVersion = constants.DefaultKubernetesVersion
}
// Load profile cluster config from file
cc, err := loadConfigFromFile(viper.GetString(cfg.MachineProfile))
cc, err := cfg.Load()
if err != nil && !os.IsNotExist(err) {
glog.Errorln("Error loading profile config: ", err)
}
Expand Down Expand Up @@ -463,23 +463,3 @@ func saveConfigToFile(data []byte, file string) error {
}
return nil
}

func loadConfigFromFile(profile string) (cfg.Config, error) {
var cc cfg.Config

profileConfigFile := constants.GetProfileFile(profile)

if _, err := os.Stat(profileConfigFile); os.IsNotExist(err) {
return cc, err
}

data, err := ioutil.ReadFile(profileConfigFile)
if err != nil {
return cc, err
}

if err := json.Unmarshal(data, &cc); err != nil {
return cc, err
}
return cc, nil
}
2 changes: 1 addition & 1 deletion cmd/minikube/cmd/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ var statusCmd = &cobra.Command{
returnCode |= clusterNotRunningStatusFlag
}

ip, err := cluster.GetHostDriverIP(api)
ip, err := cluster.GetHostDriverIP(api, config.GetMachineName())
if err != nil {
glog.Errorln("Error host driver ip status:", err)
cmdUtil.MaybeReportErrorAndExitWithCode(err, internalErrorCode)
Expand Down
83 changes: 83 additions & 0 deletions cmd/minikube/cmd/tunnel.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
Copyright 2018 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 cmd

import (
"context"
"os"
"os/signal"

"github.com/golang/glog"
"github.com/spf13/cobra"
"k8s.io/minikube/pkg/minikube/config"
"k8s.io/minikube/pkg/minikube/machine"
"k8s.io/minikube/pkg/minikube/service"
"k8s.io/minikube/pkg/minikube/tunnel"
)

var cleanup bool

// tunnelCmd represents the tunnel command
var tunnelCmd = &cobra.Command{
Use: "tunnel",
Short: "tunnel makes services of type LoadBalancer accessible on localhost",
Long: `tunnel creates a route to services deployed with type LoadBalancer and sets their Ingress to their ClusterIP`,
PersistentPreRun: func(cmd *cobra.Command, args []string) {
RootCmd.PersistentPreRun(cmd, args)
},
Run: func(cmd *cobra.Command, args []string) {
manager := tunnel.NewManager()

if cleanup {
glog.Info("Checking for tunnels to cleanup...")
if err := manager.CleanupNotRunningTunnels(); err != nil {
glog.Errorf("error cleaning up: %s", err)
}
return
}

glog.Infof("Creating docker machine client...")
api, err := machine.NewAPIClient()
if err != nil {
glog.Fatalf("error creating dockermachine client: %s", err)
}
glog.Infof("Creating k8s client...")
clientset, err := service.K8s.GetClientset()
if err != nil {
glog.Fatalf("error creating K8S clientset: %s", err)
}

ctrlC := make(chan os.Signal, 1)
signal.Notify(ctrlC, os.Interrupt)
ctx, cancel := context.WithCancel(context.Background())
go func() {
<-ctrlC
cancel()
}()

done, err := manager.StartTunnel(ctx, config.GetMachineName(), api, config.DefaultLoader, clientset.CoreV1())
if err != nil {
glog.Fatalf("error starting tunnel: %s", err)
}
<-done
},
}

func init() {
tunnelCmd.Flags().BoolVarP(&cleanup, "cleanup", "c", false, "call with cleanup=true to remove old tunnels")
RootCmd.AddCommand(tunnelCmd)
}
7 changes: 4 additions & 3 deletions cmd/minikube/cmd/update-context.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,18 @@ var updateContextCmd = &cobra.Command{
os.Exit(1)
}
defer api.Close()
ip, err := cluster.GetHostDriverIP(api)
machineName := config.GetMachineName()
ip, err := cluster.GetHostDriverIP(api, machineName)
if err != nil {
glog.Errorln("Error host driver ip status:", err)
cmdUtil.MaybeReportErrorAndExit(err)
}
kstatus, err := kcfg.UpdateKubeconfigIP(ip, constants.KubeconfigPath, config.GetMachineName())
ok, err := kcfg.UpdateKubeconfigIP(ip, constants.KubeconfigPath, machineName)
if err != nil {
glog.Errorln("Error kubeconfig status:", err)
cmdUtil.MaybeReportErrorAndExit(err)
}
if kstatus {
if ok {
fmt.Println("Reconfigured kubeconfig IP, now pointing at " + ip.String())
} else {
fmt.Println("Kubeconfig IP correctly configured, pointing at " + ip.String())
Expand Down
50 changes: 50 additions & 0 deletions docs/networking.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,53 @@ To determine the NodePort for your service, you can use a `kubectl` command like
We also have a shortcut for fetching the minikube IP and a service's `NodePort`:

`minikube service --url $SERVICE`

### LoadBalancer emulation (`minikube tunnel`)

Services of type `LoadBalancer` can be exposed via the `minikube tunnel` command.

````shell
minikube tunnel
````

Will output:

```
out/minikube tunnel
Password: *****
Status:
machine: minikube
pid: 59088
route: 10.96.0.0/12 -> 192.168.99.101
minikube: Running
services: []
errors:
minikube: no errors
router: no errors
loadbalancer emulator: no errors
````

Tunnel might ask you for password for creating and deleting network routes.

# Cleaning up orphaned routes

If the `minikube tunnel` shuts down in an unclean way, it might leave a network route around.
This case the ~/.minikube/tunnels.json file will contain an entry for that tunnel.
To cleanup orphaned routes, run:
````
minikube tunnel --cleanup
````

# (Advanced) Running tunnel as root to avoid entering password multiple times

`minikube tunnel` runs as a separate daemon, creates a network route on the host to the service CIDR of the cluster using the cluster's IP address as a gateway.
Adding a route requires root privileges for the user, and thus there are differences in how to run `minikube tunnel` depending on the OS.

Recommended way to use on Linux with KVM2 driver and MacOSX with Hyperkit driver:

`sudo -E minikube tunnel`

Using VirtualBox on Windows, Mac and Linux _both_ `minikube start` and `minikube tunnel` needs to be started from the same Administrator user session otherwise [VBoxManage can't recognize the created VM](https://forums.virtualbox.org/viewtopic.php?f=6&t=81551).

Loading

0 comments on commit ae9f4b2

Please sign in to comment.