Skip to content

Commit

Permalink
Add ipv6 support option
Browse files Browse the repository at this point in the history
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

kubernetes-sigs#278

Reference: kubernetes-sigs#280

Signed-off-by: Antonio Ojea <[email protected]>
  • Loading branch information
aojea authored and aojeagarcia committed Feb 25, 2019
1 parent 2c51957 commit 1a1ca90
Show file tree
Hide file tree
Showing 8 changed files with 62 additions and 12 deletions.
3 changes: 3 additions & 0 deletions cmd/kind/create/cluster/createcluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ type flagpole struct {
Config string
ImageName string
Retain bool
IPv6 bool
Wait time.Duration
}

Expand All @@ -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
}
Expand Down Expand Up @@ -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")
Expand Down
7 changes: 7 additions & 0 deletions pkg/cluster/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
1 change: 1 addition & 0 deletions pkg/cluster/internal/create/createcontext.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/cluster/internal/create/haproxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
21 changes: 20 additions & 1 deletion pkg/cluster/internal/create/kubeadm-config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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,
Expand All @@ -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,
},
Expand Down Expand Up @@ -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)
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/cluster/internal/create/kubeadm-join.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand Down
17 changes: 14 additions & 3 deletions pkg/cluster/internal/kubeadm/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
21 changes: 15 additions & 6 deletions pkg/cluster/nodes/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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+$`)
Expand Down

0 comments on commit 1a1ca90

Please sign in to comment.