From b4dc7c80db0aaaf93e4bdd17feaf637c4bfef4db Mon Sep 17 00:00:00 2001 From: Glenn Pratt Date: Mon, 13 Nov 2023 15:41:57 -0800 Subject: [PATCH] Allow specifying Private IP by annotation for VLAN / VPC support --- README.md | 23 +++++++++++++++++++++-- cloud/linode/loadbalancers.go | 14 ++++++++++++-- cloud/linode/loadbalancers_test.go | 23 +++++++++++++++++++++-- 3 files changed, 54 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 118d75f3..571cc155 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,10 @@ Kubernetes Services of type `LoadBalancer` will be served through a [Linode Node The Linode CCM accepts several annotations which affect the properties of the underlying NodeBalancer deployment. -All of the service annotation names listed below have been shortened for readability. Each annotation **MUST** be prefixed with `service.beta.kubernetes.io/linode-loadbalancer-`. The values, such as `http`, are case-sensitive. +All of the Service annotation names listed below have been shortened for readability. The values, such as `http`, are case-sensitive. + +Each *Service* annotation **MUST** be prefixed with:
+**`service.beta.kubernetes.io/linode-loadbalancer-`** Annotation (Suffix) | Values | Default | Description ---|---|---|--- @@ -80,7 +83,23 @@ Key | Values | Default | Description `proxy-protocol` | `none`, `v1`, `v2` | `none` | Specifies whether to use a version of Proxy Protocol on the underlying NodeBalancer. Overwrites `default-proxy-protocol`. `tls-secret-name` | string | | Specifies a secret to use for TLS. The secret type should be `kubernetes.io/tls`. -#### Example usage +### Nodes + +Kubernetes Nodes can be configured with the following annotations. + +Each *Node* annotation **MUST** be prefixed with:
+**`node.k8s.linode.com/`** + +Key | Values | Default | Description +---|---|---|--- +`private-ip` | `IPv4` | `none` | Specifies the Linode Private IP overriding default detection of the Node InternalIP.
When using a [VLAN] or [VPC], the Node InternalIP may not be a Linode Private IP as [required for NodeBalancers] and should be specified. + + +[required for NodeBalancers]: https://www.linode.com/docs/api/nodebalancers/#nodebalancer-create__request-body-schema +[VLAN]: https://www.linode.com/products/vlan/ +[VPC]: https://www.linode.com/blog/linode/new-betas-coming-to-green-light/ + +### Example usage ```yaml kind: Service diff --git a/cloud/linode/loadbalancers.go b/cloud/linode/loadbalancers.go index 54dd0f26..95da8fa7 100644 --- a/cloud/linode/loadbalancers.go +++ b/cloud/linode/loadbalancers.go @@ -50,6 +50,8 @@ const ( annLinodeHostnameOnlyIngress = "service.beta.kubernetes.io/linode-loadbalancer-hostname-only-ingress" annLinodeLoadBalancerTags = "service.beta.kubernetes.io/linode-loadbalancer-tags" + + annLinodeNodePrivateIP = "node.k8s.linode.com/private-ip" ) var ( @@ -644,7 +646,7 @@ func (l *loadbalancers) buildLoadBalancerRequest(ctx context.Context, clusterNam func (l *loadbalancers) buildNodeBalancerNodeCreateOptions(node *v1.Node, nodePort int32) linodego.NodeBalancerNodeCreateOptions { return linodego.NodeBalancerNodeCreateOptions{ - Address: fmt.Sprintf("%v:%v", getNodeInternalIP(node), nodePort), + Address: fmt.Sprintf("%v:%v", getNodePrivateIP(node), nodePort), Label: node.Name, Mode: "accept", Weight: 100, @@ -758,7 +760,15 @@ func getPortConfigAnnotation(service *v1.Service, port int) (portConfigAnnotatio return annotation, nil } -func getNodeInternalIP(node *v1.Node) string { +// getNodePrivateIP should provide the Linode Private IP the NodeBalance +// will communicate with. When using a VLAN or VPC for the Kubernetes cluster +// network, this will not be the NodeInternalIP, so this prefers an annotation +// cluster operators may specify in such a situation. +func getNodePrivateIP(node *v1.Node) string { + if address, exists := node.Annotations[annLinodeNodePrivateIP]; exists { + return address + } + for _, addr := range node.Status.Addresses { if addr.Type == v1.NodeInternalIP { return addr.Address diff --git a/cloud/linode/loadbalancers_test.go b/cloud/linode/loadbalancers_test.go index 05d327f8..63afdcc7 100644 --- a/cloud/linode/loadbalancers_test.go +++ b/cloud/linode/loadbalancers_test.go @@ -1112,7 +1112,7 @@ func Test_getHealthCheckType(t *testing.T) { } } -func Test_getNodeInternalIP(t *testing.T) { +func Test_getNodePrivateIP(t *testing.T) { testcases := []struct { name string node *v1.Node @@ -1146,11 +1146,30 @@ func Test_getNodeInternalIP(t *testing.T) { }, "", }, + { + "node internal ip annotation present", + &v1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + annLinodeNodePrivateIP: "192.168.42.42", + }, + }, + Status: v1.NodeStatus{ + Addresses: []v1.NodeAddress{ + { + Type: v1.NodeInternalIP, + Address: "10.0.1.1", + }, + }, + }, + }, + "192.168.42.42", + }, } for _, test := range testcases { t.Run(test.name, func(t *testing.T) { - ip := getNodeInternalIP(test.node) + ip := getNodePrivateIP(test.node) if ip != test.address { t.Error("unexpected certificate") t.Logf("expected: %q", test.address)