Skip to content

Commit 9ef8a7a

Browse files
authored
Merge pull request #100 from lubronzhan/topic/lubron/fix-61
Support loadbalancerClass
2 parents 4b97a95 + a41b3a1 commit 9ef8a7a

9 files changed

+831
-29
lines changed

README.md

+8-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ The `kube-vip-cloud-provider` will only implement the `loadBalancer` functionali
1919
- Setting the special IP `0.0.0.0` for DHCP workflow.
2020
- Support single stack IPv6 or IPv4
2121
- Support for dualstack via the annotation: `kube-vip.io/loadbalancerIPs: 192.168.10.10,2001:db8::1`
22-
- Support ascending and descending search order by setting search-order=desc
22+
- Support ascending and descending search order when allocating IP from pool or range by setting search-order=desc
23+
- Support loadbalancerClass `kube-vip.io/kube-vip-class`
2324

2425
## Installing the `kube-vip-cloud-provider`
2526

@@ -134,6 +135,12 @@ address in each of both IP families for the pool.
134135

135136
Set the CIDR to `0.0.0.0/32`, that will make the controller to give all _LoadBalancers_ the IP `0.0.0.0`.
136137

138+
139+
## LoadbalancerClass support
140+
141+
If users only want kube-vip-cloud-provider to allocate ip for specific set of services, they can pass `KUBEVIP_ENABLE_LOADBALANCERCLASS: true` as an environment variable to kube-vip-cloud-provider. kube-vip-cloud-provider will only allocate ip to service with `spec.loadBalancerClass: kube-vip.io/kube-vip-class`.
142+
143+
137144
## Debugging
138145

139146
The logs for the cloud-provider controller can be viewed with the following command:

go.mod

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ require (
1111
k8s.io/cloud-provider v0.29.1
1212
k8s.io/component-base v0.29.1
1313
k8s.io/klog v1.0.0
14+
k8s.io/klog/v2 v2.110.1
15+
k8s.io/utils v0.0.0-20230726121419-3b25d923346b
1416
)
1517

1618
require (
@@ -97,10 +99,8 @@ require (
9799
k8s.io/apiserver v0.29.1 // indirect
98100
k8s.io/component-helpers v0.29.1 // indirect
99101
k8s.io/controller-manager v0.29.1 // indirect
100-
k8s.io/klog/v2 v2.110.1 // indirect
101102
k8s.io/kms v0.29.1 // indirect
102103
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect
103-
k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect
104104
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.28.0 // indirect
105105
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
106106
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect

main.go

+16-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package main
1919
import (
2020
"fmt"
2121
"os"
22+
"strconv"
2223

2324
"github.com/kube-vip/kube-vip-cloud-provider/pkg/provider"
2425
"github.com/spf13/pflag"
@@ -48,7 +49,21 @@ func main() {
4849

4950
fss := cliflag.NamedFlagSets{}
5051

51-
command := app.NewCloudControllerManagerCommand(opts, cloudInitializer, controllerInitializers(), names.CCMControllerAliases(), fss, wait.NeverStop)
52+
controllerInitializers := controllerInitializers()
53+
lbc := os.Getenv(provider.EnableLoadbalancerClassEnvKey)
54+
if len(lbc) > 0 {
55+
enableLBClass, err := strconv.ParseBool(lbc)
56+
if err != nil {
57+
fmt.Fprintf(os.Stderr, "%s value '%s' is invalid, %v\n", provider.EnableLoadbalancerClassEnvKey, lbc, err)
58+
os.Exit(1)
59+
}
60+
if enableLBClass {
61+
controllerInitializers = make(map[string]app.ControllerInitFuncConstructor)
62+
klog.Infoln("skipping default cloud-provider service controller")
63+
}
64+
}
65+
66+
command := app.NewCloudControllerManagerCommand(opts, cloudInitializer, controllerInitializers, names.CCMControllerAliases(), fss, wait.NeverStop)
5267

5368
command.Flags().BoolVar(&provider.OutSideCluster, "OutSideCluster", false, "Start Controller outside of cluster")
5469

pkg/provider/config.go

+5-4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55

66
v1 "k8s.io/api/core/v1"
77
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
8+
"k8s.io/client-go/kubernetes"
89
)
910

1011
// Services functions - once the service data is taken from the configMap, these functions will interact with the data
@@ -45,12 +46,12 @@ import (
4546
// return
4647
// }
4748

48-
func (k *kubevipLoadBalancerManager) GetConfigMap(ctx context.Context, cm, nm string) (*v1.ConfigMap, error) {
49+
func getConfigMap(ctx context.Context, kubeClient kubernetes.Interface, cm, nm string) (*v1.ConfigMap, error) {
4950
// Attempt to retrieve the config map
50-
return k.kubeClient.CoreV1().ConfigMaps(nm).Get(ctx, cm, metav1.GetOptions{})
51+
return kubeClient.CoreV1().ConfigMaps(nm).Get(ctx, cm, metav1.GetOptions{})
5152
}
5253

53-
func (k *kubevipLoadBalancerManager) CreateConfigMap(ctx context.Context, cm, nm string) (*v1.ConfigMap, error) {
54+
func createConfigMap(ctx context.Context, kubeClient kubernetes.Interface, cm, nm string) (*v1.ConfigMap, error) {
5455
// Create new configuration map in the correct namespace
5556
newConfigMap := v1.ConfigMap{
5657
ObjectMeta: metav1.ObjectMeta{
@@ -59,7 +60,7 @@ func (k *kubevipLoadBalancerManager) CreateConfigMap(ctx context.Context, cm, nm
5960
},
6061
}
6162
// Return results of configMap create
62-
return k.kubeClient.CoreV1().ConfigMaps(nm).Create(ctx, &newConfigMap, metav1.CreateOptions{})
63+
return kubeClient.CoreV1().ConfigMaps(nm).Create(ctx, &newConfigMap, metav1.CreateOptions{})
6364
}
6465

6566
// func (k *kubevipLoadBalancerManager) UpdateConfigMap(ctx context.Context, cm *v1.ConfigMap, s *kubevipServices) (*v1.ConfigMap, error) {

pkg/provider/loadBalancer.go

+15-15
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,11 @@ func newLoadBalancer(kubeClient kubernetes.Interface, ns, cm string) cloudprovid
4444
}
4545

4646
func (k *kubevipLoadBalancerManager) EnsureLoadBalancer(ctx context.Context, _ string, service *v1.Service, _ []*v1.Node) (lbs *v1.LoadBalancerStatus, err error) {
47-
return k.syncLoadBalancer(ctx, service)
47+
return syncLoadBalancer(ctx, k.kubeClient, service, k.cloudConfigMap, k.namespace)
4848
}
4949

5050
func (k *kubevipLoadBalancerManager) UpdateLoadBalancer(ctx context.Context, _ string, service *v1.Service, _ []*v1.Node) (err error) {
51-
_, err = k.syncLoadBalancer(ctx, service)
51+
_, err = syncLoadBalancer(ctx, k.kubeClient, service, k.cloudConfigMap, k.namespace)
5252
return err
5353
}
5454

@@ -87,7 +87,7 @@ func (k *kubevipLoadBalancerManager) deleteLoadBalancer(ctx context.Context, ser
8787
// 2b. Get the network configuration for this service (namespace) / (CIDR/Range)
8888
// 2c. Between the two find a free address
8989

90-
func (k *kubevipLoadBalancerManager) syncLoadBalancer(ctx context.Context, service *v1.Service) (*v1.LoadBalancerStatus, error) {
90+
func syncLoadBalancer(ctx context.Context, kubeClient kubernetes.Interface, service *v1.Service, cmName, cmNamespace string) (*v1.LoadBalancerStatus, error) {
9191
// This function reconciles the load balancer state
9292
klog.Infof("syncing service '%s' (%s)", service.Name, service.UID)
9393

@@ -97,7 +97,7 @@ func (k *kubevipLoadBalancerManager) syncLoadBalancer(ctx context.Context, servi
9797
klog.Warningf("service.Spec.LoadBalancerIP is defined but annotations '%s' is not, assume it's a legacy service, updates its annotations", loadbalancerIPsAnnotations)
9898
// assume it's legacy service, need to update the annotation.
9999
err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
100-
recentService, getErr := k.kubeClient.CoreV1().Services(service.Namespace).Get(ctx, service.Name, metav1.GetOptions{})
100+
recentService, getErr := kubeClient.CoreV1().Services(service.Namespace).Get(ctx, service.Name, metav1.GetOptions{})
101101
if getErr != nil {
102102
return getErr
103103
}
@@ -109,7 +109,7 @@ func (k *kubevipLoadBalancerManager) syncLoadBalancer(ctx context.Context, servi
109109
delete(recentService.Labels, legacyIpamAddressLabelKey)
110110

111111
// Update the actual service with the annotations
112-
_, updateErr := k.kubeClient.CoreV1().Services(recentService.Namespace).Update(ctx, recentService, metav1.UpdateOptions{})
112+
_, updateErr := kubeClient.CoreV1().Services(recentService.Namespace).Update(ctx, recentService, metav1.UpdateOptions{})
113113
return updateErr
114114
})
115115
if err != nil {
@@ -125,7 +125,7 @@ func (k *kubevipLoadBalancerManager) syncLoadBalancer(ctx context.Context, servi
125125
if service.Labels == nil || service.Labels[implementationLabelKey] != implementationLabelValue {
126126
klog.Infof("service '%s/%s' created with pre-defined ip '%s'", service.Namespace, service.Name, v)
127127
err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
128-
recentService, getErr := k.kubeClient.CoreV1().Services(service.Namespace).Get(ctx, service.Name, metav1.GetOptions{})
128+
recentService, getErr := kubeClient.CoreV1().Services(service.Namespace).Get(ctx, service.Name, metav1.GetOptions{})
129129
if getErr != nil {
130130
return getErr
131131
}
@@ -135,7 +135,7 @@ func (k *kubevipLoadBalancerManager) syncLoadBalancer(ctx context.Context, servi
135135
}
136136
recentService.Labels[implementationLabelKey] = implementationLabelValue
137137
// Update the actual service with the annotations
138-
_, updateErr := k.kubeClient.CoreV1().Services(recentService.Namespace).Update(ctx, recentService, metav1.UpdateOptions{})
138+
_, updateErr := kubeClient.CoreV1().Services(recentService.Namespace).Update(ctx, recentService, metav1.UpdateOptions{})
139139
return updateErr
140140
})
141141
if err != nil {
@@ -146,31 +146,31 @@ func (k *kubevipLoadBalancerManager) syncLoadBalancer(ctx context.Context, servi
146146
}
147147

148148
// Get the clound controller configuration map
149-
controllerCM, err := k.GetConfigMap(ctx, k.cloudConfigMap, k.namespace)
149+
controllerCM, err := getConfigMap(ctx, kubeClient, cmName, cmNamespace)
150150
if err != nil {
151-
klog.Errorf("Unable to retrieve kube-vip ipam config from configMap [%s] in %s", k.cloudConfigMap, k.namespace)
151+
klog.Errorf("Unable to retrieve kube-vip ipam config from configMap [%s] in %s", cmName, cmNamespace)
152152
// TODO - determine best course of action, create one if it doesn't exist
153-
controllerCM, err = k.CreateConfigMap(ctx, k.cloudConfigMap, k.namespace)
153+
controllerCM, err = createConfigMap(ctx, kubeClient, cmName, cmNamespace)
154154
if err != nil {
155155
return nil, err
156156
}
157157
}
158158

159159
// Get ip pool from configmap and determine if it is namespace specific or global
160-
pool, global, err := discoverPool(controllerCM, service.Namespace, k.cloudConfigMap)
160+
pool, global, err := discoverPool(controllerCM, service.Namespace, cmName)
161161
if err != nil {
162162
return nil, err
163163
}
164164

165165
// Get all services in this namespace or globally, that have the correct label
166166
var svcs *v1.ServiceList
167167
if global {
168-
svcs, err = k.kubeClient.CoreV1().Services("").List(ctx, metav1.ListOptions{LabelSelector: getKubevipImplementationLabel()})
168+
svcs, err = kubeClient.CoreV1().Services("").List(ctx, metav1.ListOptions{LabelSelector: getKubevipImplementationLabel()})
169169
if err != nil {
170170
return &service.Status.LoadBalancer, err
171171
}
172172
} else {
173-
svcs, err = k.kubeClient.CoreV1().Services(service.Namespace).List(ctx, metav1.ListOptions{LabelSelector: getKubevipImplementationLabel()})
173+
svcs, err = kubeClient.CoreV1().Services(service.Namespace).List(ctx, metav1.ListOptions{LabelSelector: getKubevipImplementationLabel()})
174174
if err != nil {
175175
return &service.Status.LoadBalancer, err
176176
}
@@ -201,7 +201,7 @@ func (k *kubevipLoadBalancerManager) syncLoadBalancer(ctx context.Context, servi
201201

202202
// Update the services with this new address
203203
retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error {
204-
recentService, getErr := k.kubeClient.CoreV1().Services(service.Namespace).Get(ctx, service.Name, metav1.GetOptions{})
204+
recentService, getErr := kubeClient.CoreV1().Services(service.Namespace).Get(ctx, service.Name, metav1.GetOptions{})
205205
if getErr != nil {
206206
return getErr
207207
}
@@ -226,7 +226,7 @@ func (k *kubevipLoadBalancerManager) syncLoadBalancer(ctx context.Context, servi
226226
recentService.Spec.LoadBalancerIP = strings.Split(loadBalancerIPs, ",")[0]
227227

228228
// Update the actual service with the address and the labels
229-
_, updateErr := k.kubeClient.CoreV1().Services(recentService.Namespace).Update(ctx, recentService, metav1.UpdateOptions{})
229+
_, updateErr := kubeClient.CoreV1().Services(recentService.Namespace).Update(ctx, recentService, metav1.UpdateOptions{})
230230
return updateErr
231231
})
232232
if retryErr != nil {

pkg/provider/loadBalancer_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -888,7 +888,7 @@ func Test_syncLoadBalancer(t *testing.T) {
888888
}
889889
}
890890

891-
_, err = mgr.syncLoadBalancer(context.Background(), &tt.originalService) // #nosec G601
891+
_, err = syncLoadBalancer(context.Background(), mgr.kubeClient, &tt.originalService, cm, ns) // #nosec G601
892892
if err != nil {
893893
t.Error(err)
894894
}

0 commit comments

Comments
 (0)