@@ -11,6 +11,7 @@ import (
1111 "github.com/cloudscale-ch/cloudscale-go-sdk/v6"
1212 v1 "k8s.io/api/core/v1"
1313 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
14+ "k8s.io/apimachinery/pkg/labels"
1415 "k8s.io/client-go/kubernetes"
1516 "k8s.io/klog/v2"
1617 "k8s.io/utils/ptr"
@@ -208,7 +209,7 @@ const (
208209 // connections timing out while the monitor is updated.
209210 LoadBalancerHealthMonitorTimeoutS = "k8s.cloudscale.ch/loadbalancer-health-monitor-timeout-s"
210211
211- // LoadBalancerHealthMonitorDownThreshold is the number of the checks that
212+ // LoadBalancerHealthMonitorUpThreshold is the number of the checks that
212213 // need to succeed before a pool member is considered up. Defaults to 2.
213214 LoadBalancerHealthMonitorUpThreshold = "k8s.cloudscale.ch/loadbalancer-health-monitor-up-threshold"
214215
@@ -278,7 +279,7 @@ const (
278279 // Changing this annotation on an established service is considered safe.
279280 LoadBalancerListenerTimeoutMemberDataMS = "k8s.cloudscale.ch/loadbalancer-timeout-member-data-ms"
280281
281- // LoadBalancerSubnetLimit is a JSON list of subnet UUIDs that the
282+ // LoadBalancerListenerAllowedSubnets is a JSON list of subnet UUIDs that the
282283 // loadbalancer should use. By default, all subnets of a node are used:
283284 //
284285 // * `[]` means that anyone is allowed to connect (default).
@@ -291,6 +292,10 @@ const (
291292 // This is an advanced feature, useful if you have nodes that are in
292293 // multiple private subnets.
293294 LoadBalancerListenerAllowedSubnets = "k8s.cloudscale.ch/loadbalancer-listener-allowed-subnets"
295+
296+ // LoadBalancerNodeSelector can be set to restrict which nodes are added to the LB pool.
297+ // It accepts a standard Kubernetes label selector string.
298+ LoadBalancerNodeSelector = "k8s.cloudscale.ch/loadbalancer-node-selector"
294299)
295300
296301type loadbalancer struct {
@@ -387,6 +392,11 @@ func (l *loadbalancer) EnsureLoadBalancer(
387392 return nil , err
388393 }
389394
395+ nodes , err := filterNodesBySelector (serviceInfo , nodes )
396+ if err != nil {
397+ return nil , err
398+ }
399+
390400 // Refuse to do anything if there are no nodes
391401 if len (nodes ) == 0 {
392402 return nil , errors .New (
@@ -396,7 +406,7 @@ func (l *loadbalancer) EnsureLoadBalancer(
396406 }
397407
398408 // Reconcile
399- err : = reconcileLbState (ctx , l .lbs .client , func () (* lbState , error ) {
409+ err = reconcileLbState (ctx , l .lbs .client , func () (* lbState , error ) {
400410 // Get the desired state from Kubernetes
401411 servers , err := l .srv .mapNodes (ctx , nodes ).All ()
402412 if err != nil {
@@ -442,6 +452,28 @@ func (l *loadbalancer) EnsureLoadBalancer(
442452 return result , nil
443453}
444454
455+ func filterNodesBySelector (
456+ serviceInfo * serviceInfo ,
457+ nodes []* v1.Node ,
458+ ) ([]* v1.Node , error ) {
459+ selector := labels .Everything ()
460+ if v := serviceInfo .annotation (LoadBalancerNodeSelector ); v != "" {
461+ var err error
462+ selector , err = labels .Parse (v )
463+ if err != nil {
464+ return nil , fmt .Errorf ("unable to parse selector: %w" , err )
465+ }
466+ }
467+ selectedNodes := make ([]* v1.Node , 0 , len (nodes ))
468+ for _ , node := range nodes {
469+ if selector .Matches (labels .Set (node .Labels )) {
470+ selectedNodes = append (selectedNodes , node )
471+ }
472+ }
473+
474+ return selectedNodes , nil
475+ }
476+
445477// UpdateLoadBalancer updates hosts under the specified load balancer.
446478// Implementations must treat the *v1.Service and *v1.Node
447479// parameters as read-only and not modify them.
@@ -461,6 +493,19 @@ func (l *loadbalancer) UpdateLoadBalancer(
461493 return err
462494 }
463495
496+ nodes , err := filterNodesBySelector (serviceInfo , nodes )
497+ if err != nil {
498+ return err
499+ }
500+
501+ // Refuse to do anything if there are no nodes
502+ if len (nodes ) == 0 {
503+ return errors .New (
504+ "no valid nodes for service found, please verify there is " +
505+ "at least one that allows load balancers" ,
506+ )
507+ }
508+
464509 // Reconcile
465510 return reconcileLbState (ctx , l .lbs .client , func () (* lbState , error ) {
466511 // Get the desired state from Kubernetes
0 commit comments