From 1a1ca90d30d9a9e3fc2cec848c20315980f2e1e6 Mon Sep 17 00:00:00 2001 From: Antonio Ojea Date: Sun, 24 Feb 2019 21:35:12 +0100 Subject: [PATCH] Add ipv6 support option MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch adds an option to configure `kind` clusters with IPv6  `./kind create cluster --ipv6` the clusters are configured correctly but is no fully functional because it depends on the CNI configurations that's still WIP https://github.com/kubernetes-sigs/kind/issues/278 Reference: https://github.com/kubernetes-sigs/kind/issues/280 Signed-off-by: Antonio Ojea --- cmd/kind/create/cluster/createcluster.go | 3 +++ pkg/cluster/context.go | 7 +++++++ pkg/cluster/internal/create/createcontext.go | 1 + pkg/cluster/internal/create/haproxy.go | 2 +- pkg/cluster/internal/create/kubeadm-config.go | 21 ++++++++++++++++++- pkg/cluster/internal/create/kubeadm-join.go | 2 +- pkg/cluster/internal/kubeadm/config.go | 17 ++++++++++++--- pkg/cluster/nodes/node.go | 21 +++++++++++++------ 8 files changed, 62 insertions(+), 12 deletions(-) diff --git a/cmd/kind/create/cluster/createcluster.go b/cmd/kind/create/cluster/createcluster.go index 0a2b093644..13f3521a06 100644 --- a/cmd/kind/create/cluster/createcluster.go +++ b/cmd/kind/create/cluster/createcluster.go @@ -35,6 +35,7 @@ type flagpole struct { Config string ImageName string Retain bool + IPv6 bool Wait time.Duration } @@ -54,6 +55,7 @@ func NewCommand() *cobra.Command { cmd.Flags().StringVar(&flags.Config, "config", "", "path to a kind config file") cmd.Flags().StringVar(&flags.ImageName, "image", "", "node docker image to use for booting the cluster") cmd.Flags().BoolVar(&flags.Retain, "retain", false, "retain nodes for debugging when cluster creation fails") + cmd.Flags().BoolVar(&flags.IPv6, "ipv6", false, "creates an IPv6 cluster") cmd.Flags().DurationVar(&flags.Wait, "wait", time.Duration(0), "Wait for control plane node to be ready (default 0s)") return cmd } @@ -104,6 +106,7 @@ func runE(flags *flagpole, cmd *cobra.Command, args []string) error { fmt.Printf("Creating cluster %q ...\n", flags.Name) if err = ctx.Create(cfg, cluster.Retain(flags.Retain), + cluster.IPv6(flags.IPv6), cluster.WaitForReady(flags.Wait), ); err != nil { return errors.Wrap(err, "failed to create cluster") diff --git a/pkg/cluster/context.go b/pkg/cluster/context.go index 30307fa4a5..de2318f6d7 100644 --- a/pkg/cluster/context.go +++ b/pkg/cluster/context.go @@ -111,6 +111,13 @@ func Retain(retain bool) CreateOption { } } +// IPv6 configures create to use IPv6 for the cluster networking +func IPv6(ipv6 bool) CreateOption { + return func(c *create.Context) { + c.IPv6 = ipv6 + } +} + // WaitForReady configures create to use interval as maximum wait time for the control plane node to be ready func WaitForReady(interval time.Duration) CreateOption { return func(c *create.Context) { diff --git a/pkg/cluster/internal/create/createcontext.go b/pkg/cluster/internal/create/createcontext.go index a9f8016fd6..c454c33add 100644 --- a/pkg/cluster/internal/create/createcontext.go +++ b/pkg/cluster/internal/create/createcontext.go @@ -40,6 +40,7 @@ type Context struct { Config *config.Config *DerivedConfig Retain bool // if we should retain nodes after failing to create. + IPv6 bool // use IPv6 to configure the cluster. ExecOptions []ExecOption // options to be forwarded to the exec command. } diff --git a/pkg/cluster/internal/create/haproxy.go b/pkg/cluster/internal/create/haproxy.go index a9ce7161e5..2d0b04e891 100644 --- a/pkg/cluster/internal/create/haproxy.go +++ b/pkg/cluster/internal/create/haproxy.go @@ -61,7 +61,7 @@ func runHAProxy(ec *execContext, configNode *NodeReplica) error { } // gets the IP of the control plane node - controlPlaneIP, err := controlPlaneHandle.IP() + controlPlaneIP, err := controlPlaneHandle.IP(ec.Context.IPv6) if err != nil { return errors.Wrapf(err, "failed to get IP for node %s", n.Name) } diff --git a/pkg/cluster/internal/create/kubeadm-config.go b/pkg/cluster/internal/create/kubeadm-config.go index 219764e2d9..98a95d95da 100644 --- a/pkg/cluster/internal/create/kubeadm-config.go +++ b/pkg/cluster/internal/create/kubeadm-config.go @@ -68,6 +68,13 @@ func runKubeadmConfig(ec *execContext, configNode *NodeReplica) error { return errors.Wrap(err, "failed to get kubernetes version from node: %v") } + // get the address the API Server will advertise to other members of the cluster + apiAdvertiseAddress, err := node.IP(ec.Context.IPv6) + if err != nil { + return errors.Wrap(err, "failed to get IP for bootsrap node") + } + log.Infof("API Advertise Address:\n\n%s\n", apiAdvertiseAddress) + // get the control plane endpoint, in case the cluster has an external load balancer in // front of the control-plane nodes controlPlaneEndpoint, err := getControlPlaneEndpoint(ec) @@ -76,6 +83,17 @@ func runKubeadmConfig(ec *execContext, configNode *NodeReplica) error { return err } + if controlPlaneEndpoint == "" { + // gets the IP of the bootstrap control plane node + if ec.Context.IPv6 { + controlPlaneEndpoint = fmt.Sprintf("[%s]:%d", apiAdvertiseAddress, kubeadm.APIServerPort) + } else { + controlPlaneEndpoint = fmt.Sprintf("%s:%d", apiAdvertiseAddress, kubeadm.APIServerPort) + } + } + + log.Infof("Control Plane Endpoint:\n\n%s\n", controlPlaneEndpoint) + // get kubeadm config content kubeadmConfig, err := getKubeadmConfig( ec.Config, @@ -84,6 +102,7 @@ func runKubeadmConfig(ec *execContext, configNode *NodeReplica) error { ClusterName: ec.Name(), KubernetesVersion: kubeVersion, ControlPlaneEndpoint: controlPlaneEndpoint, + APIAdvertiseAddress: apiAdvertiseAddress, APIBindPort: kubeadm.APIServerPort, Token: kubeadm.Token, }, @@ -116,7 +135,7 @@ func getControlPlaneEndpoint(ec *execContext) (string, error) { } // gets the IP of the load balancer - loadBalancerIP, err := loadBalancerHandle.IP() + loadBalancerIP, err := loadBalancerHandle.IP(ec.Context.IPv6) if err != nil { return "", errors.Wrapf(err, "failed to get IP for node: %s", ec.ExternalLoadBalancer().Name) } diff --git a/pkg/cluster/internal/create/kubeadm-join.go b/pkg/cluster/internal/create/kubeadm-join.go index 6a0f693de6..c038e0cf6e 100644 --- a/pkg/cluster/internal/create/kubeadm-join.go +++ b/pkg/cluster/internal/create/kubeadm-join.go @@ -215,7 +215,7 @@ func getJoinAddress(ec *execContext) (string, error) { } // gets the IP of the bootstrap control plane node - controlPlaneIP, err := controlPlaneHandle.IP() + controlPlaneIP, err := controlPlaneHandle.IP(ec.Context.IPv6) if err != nil { return "", errors.Wrap(err, "failed to get IP for node") } diff --git a/pkg/cluster/internal/kubeadm/config.go b/pkg/cluster/internal/kubeadm/config.go index 7ab456bb38..4442a4e47d 100644 --- a/pkg/cluster/internal/kubeadm/config.go +++ b/pkg/cluster/internal/kubeadm/config.go @@ -32,6 +32,8 @@ type ConfigData struct { KubernetesVersion string // The ControlPlaneEndpoint, that is the address of the external loadbalancer, if defined ControlPlaneEndpoint string + // The local API Server address + APIAdvertiseAddress string // The Local API Server port APIBindPort int // The Token for TLS bootstrap @@ -74,11 +76,14 @@ clusterName: "{{.ClusterName}}" bootstrapTokens: - token: "{{ .Token }}" {{ if .ControlPlaneEndpoint -}} -controlPlaneEndpoint: {{ .ControlPlaneEndpoint }} +controlPlaneEndpoint: "{{ .ControlPlaneEndpoint }}" {{- end }} # we use a well know port for making the API server discoverable inside docker network. # from the host machine such port will be accessible via a random local port instead. api: + {{ if .APIAdvertiseAddress -}} + advertiseAddress: "{{ .APIAdvertiseAddress }}" + {{- end }} bindPort: {{.APIBindPort}} # we need nsswitch.conf so we use /etc/hosts # https://github.com/kubernetes/kubernetes/issues/69195 @@ -114,7 +119,7 @@ metadata: kubernetesVersion: {{.KubernetesVersion}} clusterName: "{{.ClusterName}}" {{ if .ControlPlaneEndpoint -}} -controlPlaneEndpoint: {{ .ControlPlaneEndpoint }} +controlPlaneEndpoint: "{{ .ControlPlaneEndpoint }}" {{- end }} # we need nsswitch.conf so we use /etc/hosts # https://github.com/kubernetes/kubernetes/issues/69195 @@ -139,6 +144,9 @@ bootstrapTokens: # we use a well know port for making the API server discoverable inside docker network. # from the host machine such port will be accessible via a random local port instead. apiEndpoint: + {{ if .APIAdvertiseAddress -}} + advertiseAddress: "{{ .APIAdvertiseAddress }}" + {{- end }} bindPort: {{.APIBindPort}} --- # no-op entry that exists solely so it can be patched @@ -176,7 +184,7 @@ metadata: kubernetesVersion: {{.KubernetesVersion}} clusterName: "{{.ClusterName}}" {{ if .ControlPlaneEndpoint -}} -controlPlaneEndpoint: {{ .ControlPlaneEndpoint }} +controlPlaneEndpoint: "{{ .ControlPlaneEndpoint }}" {{- end }} # on docker for mac we have to expose the api server via port forward, # so we need to ensure the cert is valid for localhost so we can talk @@ -194,6 +202,9 @@ bootstrapTokens: # we use a well know port for making the API server discoverable inside docker network. # from the host machine such port will be accessible via a random local port instead. localAPIEndpoint: + {{ if .APIAdvertiseAddress -}} + advertiseAddress: "{{ .APIAdvertiseAddress }}" + {{- end }} bindPort: {{.APIBindPort}} --- # no-op entry that exists solely so it can be patched diff --git a/pkg/cluster/nodes/node.go b/pkg/cluster/nodes/node.go index 35bfdd6975..f2fc6f048a 100644 --- a/pkg/cluster/nodes/node.go +++ b/pkg/cluster/nodes/node.go @@ -71,6 +71,7 @@ func (n *Node) Command(command string, args ...string) exec.Cmd { type nodeCache struct { kubernetesVersion string ip string + ipv6 string ports map[int]int role config.NodeRole containerCmder exec.Cmder @@ -218,21 +219,28 @@ func (n *Node) KubeVersion() (version string, err error) { } // IP returns the IP address of the node -func (n *Node) IP() (ip string, err error) { +func (n *Node) IP(isIPv6 bool) (ip string, err error) { + // Check the IP version + cachedIP := &n.nodeCache.ip + IPAddress := "IPAddress" + if isIPv6 { + cachedIP = &n.nodeCache.ipv6 + IPAddress = "GlobalIPv6Address" + } // use the cached version first - if n.nodeCache.ip != "" { - return n.nodeCache.ip, nil + if *cachedIP != "" { + return *cachedIP, nil } // retrive the IP address of the node using docker inspect - lines, err := docker.Inspect(n.nameOrID, "{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}") + lines, err := docker.Inspect(n.nameOrID, fmt.Sprintf("{{range .NetworkSettings.Networks}}{{.%s}}{{end}}", IPAddress)) if err != nil { return "", errors.Wrap(err, "failed to get file") } if len(lines) != 1 { return "", errors.Errorf("file should only be one line, got %d lines", len(lines)) } - n.nodeCache.ip = lines[0] - return n.nodeCache.ip, nil + *cachedIP = lines[0] + return *cachedIP, nil } // Ports returns a specific port mapping for the node @@ -283,6 +291,7 @@ func (n *Node) Role() (role config.NodeRole, err error) { // matches kubeconfig server entry like: // server: https://172.17.0.2:6443 +// server: https://[2010:836B:4179::836B:4179]:6443 // which we rewrite to: // server: https://localhost:$PORT var serverAddressRE = regexp.MustCompile(`^(\s+server:) https://.*:\d+$`)