From 711e1ed048352389fd1226e3b1614a72b7920084 Mon Sep 17 00:00:00 2001 From: iwilltry42 Date: Fri, 12 Jun 2020 12:57:40 +0200 Subject: [PATCH 1/3] createCluster: use random hostPort for API-Port by default - this makes it easier to create multiple clusters without having to worry about choosing a different port every time - This is for you @ibuildthecloud ;) --- cmd/create/createCluster.go | 2 +- cmd/util/ports.go | 19 ++++++++++++------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/cmd/create/createCluster.go b/cmd/create/createCluster.go index f05e2c57a..0ad2c7900 100644 --- a/cmd/create/createCluster.go +++ b/cmd/create/createCluster.go @@ -110,7 +110,7 @@ func NewCmdCreateCluster() *cobra.Command { /********* * Flags * *********/ - cmd.Flags().StringP("api-port", "a", k3d.DefaultAPIPort, "Specify the Kubernetes API server port exposed on the LoadBalancer (Format: `--api-port [HOST:]HOSTPORT`)\n - Example: `k3d create -m 3 -a 0.0.0.0:6550`") + cmd.Flags().StringP("api-port", "a", "", "Specify the Kubernetes API server port exposed on the LoadBalancer (Format: `--api-port [HOST:]HOSTPORT`)\n - Example: `k3d create -m 3 -a 0.0.0.0:6550`") cmd.Flags().IntP("masters", "m", 1, "Specify how many masters you want to create") cmd.Flags().IntP("workers", "w", 0, "Specify how many workers you want to create") cmd.Flags().StringP("image", "i", fmt.Sprintf("%s:%s", k3d.DefaultK3sImageRepo, version.GetK3sVersion(false)), "Specify k3s image that you want to use for the nodes") diff --git a/cmd/util/ports.go b/cmd/util/ports.go index b77fb30cd..2c8218b4c 100644 --- a/cmd/util/ports.go +++ b/cmd/util/ports.go @@ -54,14 +54,19 @@ func ParseAPIPort(portString string) (k3d.ExposeAPI, error) { } // Verify 'port' is an integer and within port ranges - p, err := strconv.Atoi(exposeAPI.Port) - if err != nil { - return exposeAPI, err - } + if exposeAPI.Port != "" { + p, err := strconv.Atoi(exposeAPI.Port) + if err != nil { + log.Errorln("Failed to parse port mapping") + return exposeAPI, err + } - if p < 0 || p > 65535 { - log.Errorln("Failed to parse API Port specification") - return exposeAPI, fmt.Errorf("port value '%d' out of range", p) + if p < 0 || p > 65535 { + log.Errorln("Failed to parse API Port specification") + return exposeAPI, fmt.Errorf("port value '%d' out of range", p) + } + } else { + log.Debugf("API-Port Mapping didn't specify hostPort!") } return exposeAPI, nil From 3205cbac67cbd928d18e8425588f80f91202e717 Mon Sep 17 00:00:00 2001 From: iwilltry42 Date: Fri, 12 Jun 2020 13:22:21 +0200 Subject: [PATCH 2/3] createCluster: ask OS/kernel for free port... ... instead of leaving this up to docker, since then we don't know about it when creating the labels, etc. used e.g. for updating the kubeconfig with the correct port --- cmd/util/ports.go | 44 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/cmd/util/ports.go b/cmd/util/ports.go index 2c8218b4c..e445a4336 100644 --- a/cmd/util/ports.go +++ b/cmd/util/ports.go @@ -54,19 +54,25 @@ func ParseAPIPort(portString string) (k3d.ExposeAPI, error) { } // Verify 'port' is an integer and within port ranges - if exposeAPI.Port != "" { - p, err := strconv.Atoi(exposeAPI.Port) - if err != nil { - log.Errorln("Failed to parse port mapping") + if exposeAPI.Port == "" { + log.Debugf("API-Port Mapping didn't specify hostPort, choosing one randomly...") + freePort, err := GetFreePort() + if err != nil || freePort == 0 { + log.Errorln("Failed to get a free port") return exposeAPI, err } + exposeAPI.Port = strconv.Itoa(freePort) + log.Debugf("Got free port for API: '%d'", freePort) + } + p, err := strconv.Atoi(exposeAPI.Port) + if err != nil { + log.Errorln("Failed to parse port mapping") + return exposeAPI, err + } - if p < 0 || p > 65535 { - log.Errorln("Failed to parse API Port specification") - return exposeAPI, fmt.Errorf("port value '%d' out of range", p) - } - } else { - log.Debugf("API-Port Mapping didn't specify hostPort!") + if p < 0 || p > 65535 { + log.Errorln("Failed to parse API Port specification") + return exposeAPI, fmt.Errorf("Port value '%d' out of range", p) } return exposeAPI, nil @@ -77,3 +83,21 @@ func ParseAPIPort(portString string) (k3d.ExposeAPI, error) { func ValidatePortMap(portmap string) (string, error) { return portmap, nil // TODO: ValidatePortMap: add validation of port mapping } + +// GetFreePort tries to fetch an open port from the OS-Kernel +func GetFreePort() (int, error) { + tcpAddress, err := net.ResolveTCPAddr("tcp", "localhost:0") + if err != nil { + log.Errorln("Failed to resolve address") + return 0, err + } + + tcpListener, err := net.ListenTCP("tcp", tcpAddress) + if err != nil { + log.Errorln("Failed to create TCP Listener") + return 0, err + } + defer tcpListener.Close() + + return tcpListener.Addr().(*net.TCPAddr).Port, nil +} From d6b5d4dbf5c73588a3f133abc0d96169792f059c Mon Sep 17 00:00:00 2001 From: iwilltry42 Date: Fri, 12 Jun 2020 13:32:19 +0200 Subject: [PATCH 3/3] createCluster: fallback to default api port if random allocation fails --- cmd/create/createCluster.go | 2 +- cmd/util/ports.go | 12 +++++++----- tests/test_basic.sh | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/cmd/create/createCluster.go b/cmd/create/createCluster.go index 0ad2c7900..9cb02cff4 100644 --- a/cmd/create/createCluster.go +++ b/cmd/create/createCluster.go @@ -110,7 +110,7 @@ func NewCmdCreateCluster() *cobra.Command { /********* * Flags * *********/ - cmd.Flags().StringP("api-port", "a", "", "Specify the Kubernetes API server port exposed on the LoadBalancer (Format: `--api-port [HOST:]HOSTPORT`)\n - Example: `k3d create -m 3 -a 0.0.0.0:6550`") + cmd.Flags().StringP("api-port", "a", "random", "Specify the Kubernetes API server port exposed on the LoadBalancer (Format: `--api-port [HOST:]HOSTPORT`)\n - Example: `k3d create -m 3 -a 0.0.0.0:6550`") cmd.Flags().IntP("masters", "m", 1, "Specify how many masters you want to create") cmd.Flags().IntP("workers", "w", 0, "Specify how many workers you want to create") cmd.Flags().StringP("image", "i", fmt.Sprintf("%s:%s", k3d.DefaultK3sImageRepo, version.GetK3sVersion(false)), "Specify k3s image that you want to use for the nodes") diff --git a/cmd/util/ports.go b/cmd/util/ports.go index e445a4336..1728b7846 100644 --- a/cmd/util/ports.go +++ b/cmd/util/ports.go @@ -54,15 +54,17 @@ func ParseAPIPort(portString string) (k3d.ExposeAPI, error) { } // Verify 'port' is an integer and within port ranges - if exposeAPI.Port == "" { + if exposeAPI.Port == "" || exposeAPI.Port == "random" { log.Debugf("API-Port Mapping didn't specify hostPort, choosing one randomly...") freePort, err := GetFreePort() if err != nil || freePort == 0 { - log.Errorln("Failed to get a free port") - return exposeAPI, err + log.Warnf("Failed to get random free port:\n%+v", err) + log.Warnf("Falling back to default port %s (may be blocked though)...", k3d.DefaultAPIPort) + exposeAPI.Port = k3d.DefaultAPIPort + } else { + exposeAPI.Port = strconv.Itoa(freePort) + log.Debugf("Got free port for API: '%d'", freePort) } - exposeAPI.Port = strconv.Itoa(freePort) - log.Debugf("Got free port for API: '%d'", freePort) } p, err := strconv.Atoi(exposeAPI.Port) if err != nil { diff --git a/tests/test_basic.sh b/tests/test_basic.sh index bdf9b930d..d615ed61d 100755 --- a/tests/test_basic.sh +++ b/tests/test_basic.sh @@ -8,7 +8,7 @@ source "$CURR_DIR/common.sh" info "Creating two clusters..." $EXE create cluster c1 --wait --timeout 60s --api-port 6443 || failed "could not create cluster c1" -$EXE create cluster c2 --wait --timeout 60s --api-port 6444 || failed "could not create cluster c2" +$EXE create cluster c2 --wait --timeout 60s || failed "could not create cluster c2" info "Checking that we can get both clusters..." check_cluster_count 2