Skip to content

✨ Use LoadBalancer IPv6 address - take 2 #1334

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .envrc.sample
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export KUBECONFIG=$PWD/.mgt-cluster-kubeconfig.yaml
export HCLOUD_TOKEN=...
export HCLOUD_SSH_KEY=test
export HCLOUD_REGION=fsn1
export CAPH_LATEST_VERSION=v1.0.0-beta.35
export CONTROL_PLANE_MACHINE_COUNT=1
export WORKER_MACHINE_COUNT=1
export KUBERNETES_VERSION=v1.29.4
Expand All @@ -13,5 +14,7 @@ export HCLOUD_WORKER_MACHINE_TYPE=cpx31
export SSH_KEY=$HOME/.ssh/id_rsa.pub
export HETZNER_SSH_PUB_PATH=$HOME/.ssh/id_rsa.pub
export HETZNER_SSH_PRIV_PATH=$HOME/.ssh/id_rsa
export HETZNER_SSH_PUB=$(cat ${HETZNER_SSH_PUB_PATH} | base64 --wrap 0)
export HETZNER_SSH_PRIV=$(cat ${HETZNER_SSH_PRIV_PATH} | base64 --wrap 0)
export HETZNER_ROBOT_USER=
export HETZNER_ROBOT_PASSWORD=
7 changes: 7 additions & 0 deletions api/v1beta1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,13 @@ type LoadBalancerSpec struct {

// Region contains the name of the HCloud location where the load balancer is running.
Region Region `json:"region,omitempty"`

// UseIPv6Endpoint defines whether to use the LoadBalancer's IPv6 address as
// the cluster endpoint instead of IPv4. This is useful if nodes are provisioned
// without IPv4 address. Defaults to 'false'.
// +optional
// +kubebuilder:default=false
UseIPv6Endpoint bool `json:"useIPv6Endpoint,omitempty"`
}

// LoadBalancerServiceSpec defines a load balancer Target.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,13 @@ spec:
- lb21
- lb31
type: string
useIPv6Endpoint:
default: false
description: UseIPv6Endpoint defines whether to use the LoadBalancer's
IPv6 address as the cluster endpoint instead of IPv4. This is
useful if nodes are provisioned without IPv4 address. Defaults
to 'false'.
type: boolean
type: object
controlPlaneRegions:
description: |-
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,13 @@ spec:
- lb21
- lb31
type: string
useIPv6Endpoint:
default: false
description: UseIPv6Endpoint defines whether to use the
LoadBalancer's IPv6 address as the cluster endpoint
instead of IPv4. This is useful if nodes are provisioned
without IPv4 address. Defaults to 'false'.
type: boolean
type: object
controlPlaneRegions:
description: |-
Expand Down
7 changes: 4 additions & 3 deletions controllers/controllers_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,9 +154,10 @@ func getDefaultHetznerClusterSpec() infrav1.HetznerClusterSpec {
Protocol: "tcp",
},
},
Port: 6443,
Region: "fsn1",
Type: "lb11",
Port: 6443,
Region: "fsn1",
Type: "lb11",
UseIPv6Endpoint: false,
},
ControlPlaneEndpoint: &clusterv1.APIEndpoint{},
ControlPlaneRegions: []infrav1.Region{"fsn1"},
Expand Down
9 changes: 7 additions & 2 deletions controllers/hetznercluster_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -267,8 +267,13 @@ func (r *HetznerClusterReconciler) reconcileNormal(ctx context.Context, clusterS

func processControlPlaneEndpoint(hetznerCluster *infrav1.HetznerCluster) {
if hetznerCluster.Spec.ControlPlaneLoadBalancer.Enabled {
if hetznerCluster.Status.ControlPlaneLoadBalancer.IPv4 != "<nil>" {
defaultHost := hetznerCluster.Status.ControlPlaneLoadBalancer.IPv4
ip := hetznerCluster.Status.ControlPlaneLoadBalancer.IPv4
if hetznerCluster.Spec.ControlPlaneLoadBalancer.UseIPv6Endpoint {
ip = hetznerCluster.Status.ControlPlaneLoadBalancer.IPv6
}

if ip != "<nil>" {
defaultHost := ip
defaultPort := int32(hetznerCluster.Spec.ControlPlaneLoadBalancer.Port)

if hetznerCluster.Spec.ControlPlaneEndpoint == nil {
Expand Down
174 changes: 174 additions & 0 deletions controllers/hetznercluster_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1208,4 +1208,178 @@ func TestSetControlPlaneEndpoint(t *testing.T) {
t.Fatalf("return value should be true")
}
})

t.Run("return false if load balancer is enabled with UseIPv6Endpoint and IPv6 is 'nil'. ControlPlaneEndpoint should not change", func(t *testing.T) {
hetznerCluster := &infrav1.HetznerCluster{
Spec: infrav1.HetznerClusterSpec{
ControlPlaneLoadBalancer: infrav1.LoadBalancerSpec{
UseIPv6Endpoint: true,
Enabled: true,
},
ControlPlaneEndpoint: nil,
},
Status: infrav1.HetznerClusterStatus{
ControlPlaneLoadBalancer: &infrav1.LoadBalancerStatus{
IPv4: "xyz",
IPv6: "<nil>",
},
},
}

processControlPlaneEndpoint(hetznerCluster)

if hetznerCluster.Spec.ControlPlaneEndpoint != nil {
t.Fatalf("ControlPlaneEndpoint should not change. It should remain nil")
}

if hetznerCluster.Status.Ready != false {
t.Fatalf("return value should be false")
}

if !conditions.Has(hetznerCluster, infrav1.ControlPlaneEndpointSetCondition) {
t.Fatalf("ControlPlaneEndpointSetCondition should exist")
}

condition := conditions.Get(hetznerCluster, infrav1.ControlPlaneEndpointSetCondition)
if condition.Status != corev1.ConditionFalse {
t.Fatalf("condition status should be false")
}
})

t.Run("return true if load balancer is enabled with UseIPv6Endpoint, IPv6 is not nil and ControlPlaneEndpoint is nil. Values of ControlPlaneEndpoint.Host and ControlPlaneEndpoint.Port will get updated", func(t *testing.T) {
hetznerCluster := &infrav1.HetznerCluster{
Spec: infrav1.HetznerClusterSpec{
ControlPlaneLoadBalancer: infrav1.LoadBalancerSpec{
UseIPv6Endpoint: true,
Enabled: true,
Port: 11,
},
ControlPlaneEndpoint: nil,
},
Status: infrav1.HetznerClusterStatus{
ControlPlaneLoadBalancer: &infrav1.LoadBalancerStatus{
IPv6: "abc",
},
},
}

processControlPlaneEndpoint(hetznerCluster)

if hetznerCluster.Spec.ControlPlaneEndpoint.Host != "abc" {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest to use require.Equal(t, a, b)

t.Fatalf("Wrong value for Host set. Got: %s, Want: 'abc'", hetznerCluster.Spec.ControlPlaneEndpoint.Host)
}

if hetznerCluster.Spec.ControlPlaneEndpoint.Port != 11 {
t.Fatalf("Wrong value for Port set. Got: %d, Want: 11", hetznerCluster.Spec.ControlPlaneEndpoint.Port)
}

if hetznerCluster.Status.Ready != true {
t.Fatalf("return value should be true")
}
})

t.Run("return true if load balancer is enabled with UseIPv6Endopint, IPv6 is not nil, ControlPlaneEndpoint.Host is an empty string and ControlPlaneEndpoint.Port is 0. Values of ControlPlaneEndpoint.Host and ControlPlaneEndpoint.Port should update", func(t *testing.T) {
hetznerCluster := &infrav1.HetznerCluster{
Spec: infrav1.HetznerClusterSpec{
ControlPlaneLoadBalancer: infrav1.LoadBalancerSpec{
UseIPv6Endpoint: true,
Enabled: true,
Port: 11,
},
ControlPlaneEndpoint: &clusterv1.APIEndpoint{
Host: "",
Port: 0,
},
},
Status: infrav1.HetznerClusterStatus{
ControlPlaneLoadBalancer: &infrav1.LoadBalancerStatus{
IPv6: "abc",
},
},
}

processControlPlaneEndpoint(hetznerCluster)

if hetznerCluster.Spec.ControlPlaneEndpoint.Host != "abc" {
t.Fatalf("Wrong value for Host set. Got: %s, Want: 'abc'", hetznerCluster.Spec.ControlPlaneEndpoint.Host)
}

if hetznerCluster.Spec.ControlPlaneEndpoint.Port != 11 {
t.Fatalf("Wrong value for Port set. Got: %d, Want: 11", hetznerCluster.Spec.ControlPlaneEndpoint.Port)
}

if hetznerCluster.Status.Ready != true {
t.Fatalf("return value should be true")
}
})

t.Run("return true if load balancer is enabled with UseIPv6Endpoint, IPv6 is not nil, ControlPlaneEndpoint.Host is an empty string and ControlPlaneEndpoint.Port is 21. Value of ControlPlaneEndpoint.Host will change and ControlPlaneEndpoint.Port should remain same", func(t *testing.T) {
hetznerCluster := &infrav1.HetznerCluster{
Spec: infrav1.HetznerClusterSpec{
ControlPlaneLoadBalancer: infrav1.LoadBalancerSpec{
UseIPv6Endpoint: true,
Enabled: true,
Port: 11,
},
ControlPlaneEndpoint: &clusterv1.APIEndpoint{
Host: "",
Port: 21,
},
},
Status: infrav1.HetznerClusterStatus{
ControlPlaneLoadBalancer: &infrav1.LoadBalancerStatus{
IPv6: "abc",
},
},
}

processControlPlaneEndpoint(hetznerCluster)

if hetznerCluster.Spec.ControlPlaneEndpoint.Host != "abc" {
t.Fatalf("Wrong value for Host set. Got: %s, Want: 'abc'", hetznerCluster.Spec.ControlPlaneEndpoint.Host)
}

if hetznerCluster.Spec.ControlPlaneEndpoint.Port != 21 {
t.Fatalf("Wrong value for Port set. Got: %d, Want: 21", hetznerCluster.Spec.ControlPlaneEndpoint.Port)
}

if hetznerCluster.Status.Ready != true {
t.Fatalf("return value should be true")
}
})

t.Run("return true if load balancer is enabled with UseIPv6Endpoint, IPv6 is not nil, ControlPlaneEndpoint.Host is 'xyz' and ControlPlaneEndpoint.Port is 21. Value of ControlPlaneEndpoint.Host and ControlPlaneEndpoint.Port should remain unchanged", func(t *testing.T) {
hetznerCluster := &infrav1.HetznerCluster{
Spec: infrav1.HetznerClusterSpec{
ControlPlaneLoadBalancer: infrav1.LoadBalancerSpec{
UseIPv6Endpoint: true,
Enabled: true,
Port: 11,
},
ControlPlaneEndpoint: &clusterv1.APIEndpoint{
Host: "xyz",
Port: 21,
},
},
Status: infrav1.HetznerClusterStatus{
ControlPlaneLoadBalancer: &infrav1.LoadBalancerStatus{
IPv6: "abc",
},
},
}

processControlPlaneEndpoint(hetznerCluster)

if hetznerCluster.Spec.ControlPlaneEndpoint.Host != "xyz" {
t.Fatalf("Wrong value for Host set. Got: %s, Want: 'xyz'", hetznerCluster.Spec.ControlPlaneEndpoint.Host)
}

if hetznerCluster.Spec.ControlPlaneEndpoint.Port != 21 {
t.Fatalf("Wrong value for Port set. Got: %d, Want: 21", hetznerCluster.Spec.ControlPlaneEndpoint.Port)
}

if hetznerCluster.Status.Ready != true {
t.Fatalf("return value should be true")
}
})
}
5 changes: 3 additions & 2 deletions hack/kind-dev.sh
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,9 @@ kindV1Alpha4Cluster:
- role: control-plane
image: ghcr.io/fluxcd/kindest/node:${CLUSTER_VERSION}-amd64
networking:
podSubnet: "10.244.0.0/16"
serviceSubnet: "10.96.0.0/12"
podSubnet: "10.244.0.0/16,fd00:10:244::/56"
serviceSubnet: "10.96.0.0/12,fd00:10:96::/112"
ipFamily: dual
EOF
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: HetznerCluster
metadata:
name: "${CLUSTER_NAME}"
spec:
hcloudNetwork:
enabled: true
controlPlaneRegions:
- "${HCLOUD_REGION}"
controlPlaneEndpoint:
host: ""
port: 443
controlPlaneLoadBalancer:
region: "${HCLOUD_REGION}"
useIPv6Endpoint: true
sshKeys:
hcloud:
- name: "${HCLOUD_SSH_KEY}"
hetznerSecretRef:
name: hetzner
key:
hcloudToken: hcloud
hetznerRobotPassword: robot-password
hetznerRobotUser: robot-user
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
kind: HCloudMachineTemplate
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
metadata:
name: "${CLUSTER_NAME}-control-plane"
spec:
template:
spec:
type: "${HCLOUD_CONTROL_PLANE_MACHINE_TYPE}"
imageName: "ubuntu-22.04"
publicNetwork:
enableIPv4: false
enableIPv6: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: HCloudMachineTemplate
metadata:
name: "${CLUSTER_NAME}-md-0"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should give the machine deployment a different name, so that the name does not clash with the other MD. I suggest "${CLUSTER_NAME}-md-ipv6only-hcloud".

spec:
template:
spec:
type: "${HCLOUD_WORKER_MACHINE_TYPE}"
imageName: "ubuntu-22.04"
publicNetwork:
enableIPv4: false
enableIPv6: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
bases:
- ../bases/capi-cluster-kubeadm.yaml
- ../bases/hcloud-hetznerCluster-network-ipv6-only.yaml
- ../bases/hcloud-kcp-ubuntu.yaml
- ../bases/hcloud-mt-control-plane-ubuntu-ipv6-only.yaml
- ../bases/hcloud-mhc-control-plane.yaml
- ../bases/hcloud-md-0-kubeadm.yaml
- ../bases/kct-md-0-ubuntu.yaml
- ../bases/hcloud-mt-md-0-ubuntu-ipv6-only.yaml
- ../bases/hcloud-mhc-md-0.yaml
patchesStrategicMerge:
- ../bases/hcloud-hetznerCluster-placementGroup_patch.yaml
- ../bases/hcloud-mt-control-plane-placementGroup_patch.yaml
- ../bases/hcloud-mt-md-0-placementGroup_patch.yaml