Skip to content
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

Allow specifying Private IP by annotation for VLAN / VPC support #141

Merged
merged 1 commit into from
Nov 28, 2023
Merged
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
23 changes: 21 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:<br />
**`service.beta.kubernetes.io/linode-loadbalancer-`**

Annotation (Suffix) | Values | Default | Description
---|---|---|---
Expand Down Expand Up @@ -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:<br />
**`node.k8s.linode.com/`**

Key | Values | Default | Description
---|---|---|---
`private-ip` | `IPv4` | `none` | Specifies the Linode Private IP overriding default detection of the Node InternalIP.<br />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
Expand Down
14 changes: 12 additions & 2 deletions cloud/linode/loadbalancers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
luthermonson marked this conversation as resolved.
Show resolved Hide resolved
)

var (
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down
23 changes: 21 additions & 2 deletions cloud/linode/loadbalancers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
Loading