Skip to content

Commit 4a8678c

Browse files
rahulaitHenry Wagner
authored andcommitted
[feat] add support for different nodebalancer types (linode#316)
* add support for different nodebalancer types * add e2e test for premium nodebalancers * address review comments
1 parent 53fc2a5 commit 4a8678c

File tree

12 files changed

+317
-1
lines changed

12 files changed

+317
-1
lines changed

cloud/annotations/annotations.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ const (
2727

2828
AnnLinodeLoadBalancerPreserve = "service.beta.kubernetes.io/linode-loadbalancer-preserve"
2929
AnnLinodeNodeBalancerID = "service.beta.kubernetes.io/linode-loadbalancer-nodebalancer-id"
30+
AnnLinodeNodeBalancerType = "service.beta.kubernetes.io/linode-loadbalancer-nodebalancer-type"
3031

3132
AnnLinodeHostnameOnlyIngress = "service.beta.kubernetes.io/linode-loadbalancer-hostname-only-ingress"
3233
AnnLinodeLoadBalancerTags = "service.beta.kubernetes.io/linode-loadbalancer-tags"

cloud/linode/cloud.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ var Options struct {
4747
IpHolderSuffix string
4848
LinodeExternalNetwork *net.IPNet
4949
NodeBalancerTags []string
50+
DefaultNBType string
5051
GlobalStopChannel chan<- struct{}
5152
}
5253

cloud/linode/loadbalancers.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -664,17 +664,29 @@ func (l *loadbalancers) GetLoadBalancerTags(_ context.Context, clusterName strin
664664
return tags
665665
}
666666

667+
// GetLinodeNBType returns the NodeBalancer type for the service.
668+
func (l *loadbalancers) GetLinodeNBType(service *v1.Service) linodego.NodeBalancerPlanType {
669+
typeStr, ok := service.GetAnnotations()[annotations.AnnLinodeNodeBalancerType]
670+
if ok && linodego.NodeBalancerPlanType(typeStr) == linodego.NBTypePremium {
671+
return linodego.NBTypePremium
672+
}
673+
674+
return linodego.NodeBalancerPlanType(Options.DefaultNBType)
675+
}
676+
667677
func (l *loadbalancers) createNodeBalancer(ctx context.Context, clusterName string, service *v1.Service, configs []*linodego.NodeBalancerConfigCreateOptions) (lb *linodego.NodeBalancer, err error) {
668678
connThrottle := getConnectionThrottle(service)
669679

670680
label := l.GetLoadBalancerName(ctx, clusterName, service)
671681
tags := l.GetLoadBalancerTags(ctx, clusterName, service)
682+
nbType := l.GetLinodeNBType(service)
672683
createOpts := linodego.NodeBalancerCreateOptions{
673684
Label: &label,
674685
Region: l.zone,
675686
ClientConnThrottle: &connThrottle,
676687
Configs: configs,
677688
Tags: tags,
689+
Type: nbType,
678690
}
679691

680692
// Check for static IPv4 address annotation

cloud/linode/loadbalancers_test.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"strings"
1818
"testing"
1919

20+
ciliumclient "github.com/cilium/cilium/pkg/k8s/client/clientset/versioned/typed/cilium.io/v2alpha1"
2021
"github.com/linode/linodego"
2122
v1 "k8s.io/api/core/v1"
2223
"k8s.io/apimachinery/pkg/api/errors"
@@ -27,6 +28,7 @@ import (
2728
"k8s.io/client-go/kubernetes/fake"
2829

2930
"github.com/linode/linode-cloud-controller-manager/cloud/annotations"
31+
"github.com/linode/linode-cloud-controller-manager/cloud/linode/client"
3032
"github.com/linode/linode-cloud-controller-manager/cloud/linode/firewall"
3133
)
3234

@@ -3675,3 +3677,101 @@ func Test_LoadbalNodeNameCoercion(t *testing.T) {
36753677
}
36763678
}
36773679
}
3680+
3681+
func Test_loadbalancers_GetLinodeNBType(t *testing.T) {
3682+
type fields struct {
3683+
client client.Client
3684+
zone string
3685+
kubeClient kubernetes.Interface
3686+
ciliumClient ciliumclient.CiliumV2alpha1Interface
3687+
loadBalancerType string
3688+
}
3689+
type args struct {
3690+
service *v1.Service
3691+
}
3692+
tests := []struct {
3693+
name string
3694+
fields fields
3695+
args args
3696+
defaultNB linodego.NodeBalancerPlanType
3697+
want linodego.NodeBalancerPlanType
3698+
}{
3699+
{
3700+
name: "No annotation in service and common as default",
3701+
fields: fields{
3702+
client: nil,
3703+
zone: "",
3704+
kubeClient: nil,
3705+
ciliumClient: nil,
3706+
loadBalancerType: "nodebalancer",
3707+
},
3708+
args: args{
3709+
service: &v1.Service{
3710+
ObjectMeta: metav1.ObjectMeta{
3711+
Name: "test",
3712+
Annotations: map[string]string{},
3713+
},
3714+
},
3715+
},
3716+
defaultNB: linodego.NBTypeCommon,
3717+
want: linodego.NBTypeCommon,
3718+
},
3719+
{
3720+
name: "No annotation in service and premium as default",
3721+
fields: fields{
3722+
client: nil,
3723+
zone: "",
3724+
kubeClient: nil,
3725+
ciliumClient: nil,
3726+
loadBalancerType: "nodebalancer",
3727+
},
3728+
args: args{
3729+
service: &v1.Service{
3730+
ObjectMeta: metav1.ObjectMeta{
3731+
Name: "test",
3732+
Annotations: map[string]string{},
3733+
},
3734+
},
3735+
},
3736+
defaultNB: linodego.NBTypePremium,
3737+
want: linodego.NBTypePremium,
3738+
},
3739+
{
3740+
name: "Nodebalancer type annotation in service",
3741+
fields: fields{
3742+
client: nil,
3743+
zone: "",
3744+
kubeClient: nil,
3745+
ciliumClient: nil,
3746+
loadBalancerType: "nodebalancer",
3747+
},
3748+
args: args{
3749+
service: &v1.Service{
3750+
ObjectMeta: metav1.ObjectMeta{
3751+
Name: "test",
3752+
Annotations: map[string]string{
3753+
annotations.AnnLinodeNodeBalancerType: string(linodego.NBTypePremium),
3754+
},
3755+
},
3756+
},
3757+
},
3758+
defaultNB: linodego.NBTypeCommon,
3759+
want: linodego.NBTypePremium,
3760+
},
3761+
}
3762+
for _, tt := range tests {
3763+
t.Run(tt.name, func(t *testing.T) {
3764+
l := &loadbalancers{
3765+
client: tt.fields.client,
3766+
zone: tt.fields.zone,
3767+
kubeClient: tt.fields.kubeClient,
3768+
ciliumClient: tt.fields.ciliumClient,
3769+
loadBalancerType: tt.fields.loadBalancerType,
3770+
}
3771+
Options.DefaultNBType = string(tt.defaultNB)
3772+
if got := l.GetLinodeNBType(tt.args.service); !reflect.DeepEqual(got, tt.want) {
3773+
t.Errorf("loadbalancers.GetLinodeNBType() = %v, want %v", got, tt.want)
3774+
}
3775+
})
3776+
}
3777+
}

deploy/chart/templates/daemonset.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,9 @@ spec:
106106
{{- if .Values.allowUnauthorizedMetrics }}
107107
- --authorization-always-allow-paths="/metrics"
108108
{{- end }}
109+
{{- if .Values.defaultNBType }}
110+
- --default-nodebalancer-type={{ .Values.defaultNBType }}
111+
{{- end }}
109112
{{- with .Values.containerSecurityContext }}
110113
securityContext:
111114
{{- toYaml . | nindent 12 }}

deploy/chart/values.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,9 @@ tolerations:
8383
# Enable Linode token health checker
8484
# tokenHealthChecker: true
8585

86+
# Default NodeBalancer type to create("common" or "premium"). Default is "common"
87+
# defaultNBType: "common"
88+
8689
# This section adds the ability to pass environment variables to adjust CCM defaults
8790
# https://github.com/linode/linode-cloud-controller-manager/blob/master/cloud/linode/loadbalancers.go
8891
# LINODE_HOSTNAME_ONLY_INGRESS type bool is supported

docs/configuration/annotations.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ For implementation details, see:
3232
| `tags` | string | | A comma separated list of tags to be applied to the NodeBalancer instance |
3333
| `firewall-id` | string | | An existing Cloud Firewall ID to be attached to the NodeBalancer instance. See [Firewall Setup](firewall.md) |
3434
| `firewall-acl` | string | | The Firewall rules to be applied to the NodeBalancer. See [Firewall Configuration](#firewall-configuration) |
35+
| `nodebalancer-type` | string | | The type of NodeBalancer to create (options: common, premium). See [NodeBalancer Types](#nodebalancer-type) |
3536

3637
### Port Specific Configuration
3738

@@ -104,6 +105,14 @@ metadata:
104105
}
105106
```
106107
108+
### NodeBalancer Type
109+
Linode supports nodebalancers of different types: common and premium. By default, nodebalancers of type common are provisioned. If an account is allowed to provision premium nodebalancers and one wants to use them, it can be achieved by specifying the annotation:
110+
```yaml
111+
metadata:
112+
annotations:
113+
service.beta.kubernetes.io/linode-loadbalancer-nodebalancer-type: premium
114+
```
115+
107116
For more examples and detailed configuration options, see:
108117
- [LoadBalancer Configuration](loadbalancer.md)
109118
- [Firewall Configuration](firewall.md)
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# yaml-language-server: $schema=https://raw.githubusercontent.com/kyverno/chainsaw/main/.schemas/json/test-chainsaw-v1alpha1.json
2+
apiVersion: chainsaw.kyverno.io/v1alpha1
3+
kind: Test
4+
metadata:
5+
name: lb-premium-nb
6+
labels:
7+
all:
8+
spec:
9+
namespace: "lb-premium-nb"
10+
steps:
11+
- name: Create pods and services
12+
try:
13+
- apply:
14+
file: create-pods-services.yaml
15+
catch:
16+
- describe:
17+
apiVersion: v1
18+
kind: Pod
19+
- describe:
20+
apiVersion: v1
21+
kind: Service
22+
- name: Check that loadbalancer ip is assigned
23+
try:
24+
- assert:
25+
resource:
26+
apiVersion: v1
27+
kind: Service
28+
metadata:
29+
name: svc-test
30+
status:
31+
(loadBalancer.ingress[0].ip != null): true
32+
- name: Fetch loadbalancer ip and check both pods reachable
33+
try:
34+
- script:
35+
content: |
36+
set -e
37+
IP=$(kubectl get svc svc-test -n $NAMESPACE -o json | jq -r .status.loadBalancer.ingress[0].ip)
38+
39+
podnames=()
40+
41+
for i in {1..10}; do
42+
if [[ ${#podnames[@]} -lt 2 ]]; then
43+
output=$(curl -s $IP:80 | jq -e .podName || true)
44+
45+
if [[ "$output" == *"test-"* ]]; then
46+
unique=true
47+
for i in "${array[@]}"; do
48+
if [[ "$i" == "$output" ]]; then
49+
unique=false
50+
break
51+
fi
52+
done
53+
if [[ "$unique" == true ]]; then
54+
podnames+=($output)
55+
fi
56+
fi
57+
else
58+
break
59+
fi
60+
sleep 10
61+
done
62+
63+
if [[ ${#podnames[@]} -lt 2 ]]; then
64+
echo "all pods failed to respond"
65+
else
66+
echo "all pods responded"
67+
fi
68+
check:
69+
($error == null): true
70+
(contains($stdout, 'all pods responded')): true
71+
- name: Check nodebalancer type
72+
try:
73+
- script:
74+
content: |
75+
set -e
76+
77+
nbid=$(KUBECONFIG=$KUBECONFIG NAMESPACE=$NAMESPACE LINODE_TOKEN=$LINODE_TOKEN ../scripts/get-nb-id.sh)
78+
for i in {1..10}; do
79+
type=$(curl -s --request GET \
80+
-H "Authorization: Bearer $LINODE_TOKEN" \
81+
-H "Content-Type: application/json" \
82+
-H "accept: application/json" \
83+
"https://api.linode.com/v4/nodebalancers/${nbid}" | jq -r '.type')
84+
85+
if [[ $type == "premium" ]]; then
86+
echo "nodebalancer type is premium"
87+
break
88+
fi
89+
sleep 5
90+
done
91+
check:
92+
($error == null): true
93+
(contains($stdout, 'nodebalancer type is premium')): true
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
---
2+
apiVersion: apps/v1
3+
kind: Deployment
4+
metadata:
5+
labels:
6+
app: lb-premium-nb
7+
name: test
8+
spec:
9+
replicas: 2
10+
selector:
11+
matchLabels:
12+
app: lb-premium-nb
13+
template:
14+
metadata:
15+
labels:
16+
app: lb-premium-nb
17+
spec:
18+
affinity:
19+
podAntiAffinity:
20+
preferredDuringSchedulingIgnoredDuringExecution:
21+
- podAffinityTerm:
22+
labelSelector:
23+
matchExpressions:
24+
- key: app
25+
operator: In
26+
values:
27+
- simple-lb
28+
topologyKey: kubernetes.io/hostname
29+
weight: 100
30+
containers:
31+
- image: appscode/test-server:2.3
32+
name: test
33+
ports:
34+
- name: http-1
35+
containerPort: 8080
36+
protocol: TCP
37+
env:
38+
- name: POD_NAME
39+
valueFrom:
40+
fieldRef:
41+
apiVersion: v1
42+
fieldPath: metadata.name
43+
---
44+
apiVersion: v1
45+
kind: Service
46+
metadata:
47+
name: svc-test
48+
annotations:
49+
service.beta.kubernetes.io/linode-loadbalancer-nodebalancer-type: premium
50+
labels:
51+
app: lb-premium-nb
52+
spec:
53+
type: LoadBalancer
54+
selector:
55+
app: lb-premium-nb
56+
ports:
57+
- name: http-1
58+
protocol: TCP
59+
port: 80
60+
targetPort: 8080
61+
sessionAffinity: None

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ require (
1313
github.com/golang/mock v1.6.0
1414
github.com/google/uuid v1.6.0
1515
github.com/hexdigest/gowrap v1.4.2
16-
github.com/linode/linodego v1.47.0
16+
github.com/linode/linodego v1.47.1-0.20250228182220-41a199a7ed93
1717
github.com/prometheus/client_golang v1.21.0
1818
github.com/spf13/pflag v1.0.6
1919
github.com/stretchr/testify v1.10.0

0 commit comments

Comments
 (0)