Skip to content

Commit 0885b92

Browse files
SmirlTimothy-Dougherty
authored andcommitted
Add support for NodeSelector in TargetGroupBindings (kubernetes-sigs#1785)
* Add support for NodeSelector in TargetGroupBindings Signed-off-by: Alex Williams <[email protected]> * NodeSelector adds to default selector not overrides * Update docs on merging NodeSelector behaviour
1 parent 75df257 commit 0885b92

File tree

10 files changed

+552
-143
lines changed

10 files changed

+552
-143
lines changed

apis/elbv2/v1beta1/targetgroupbinding_types.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,10 @@ type TargetGroupBindingSpec struct {
127127
// networking defines the networking rules to allow ELBV2 LoadBalancer to access targets in TargetGroup.
128128
// +optional
129129
Networking *TargetGroupBindingNetworking `json:"networking,omitempty"`
130+
131+
// node selector for instance type target groups to only register certain nodes
132+
// +optional
133+
NodeSelector *metav1.LabelSelector `json:"nodeSelector,omitempty"`
130134
}
131135

132136
// TargetGroupBindingStatus defines the observed state of TargetGroupBinding

apis/elbv2/v1beta1/zz_generated.deepcopy.go

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/crd/bases/elbv2.k8s.aws_targetgroupbindings.yaml

Lines changed: 250 additions & 131 deletions
Large diffs are not rendered by default.

controllers/elbv2/eventhandlers/node.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package eventhandlers
22

33
import (
44
"context"
5+
56
"github.com/go-logr/logr"
67
corev1 "k8s.io/api/core/v1"
78
"k8s.io/apimachinery/pkg/labels"
@@ -79,7 +80,11 @@ func (h *enqueueRequestsForNodeEvent) enqueueImpactedTargetGroupBindings(queue w
7980
continue
8081
}
8182

82-
nodeSelector := backend.GetTrafficProxyNodeSelector(&tgb)
83+
nodeSelector, err := backend.GetTrafficProxyNodeSelector(&tgb)
84+
if err != nil {
85+
h.logger.Error(err, "failed to get nodeSelector", "TargetGroupBinding", tgb)
86+
return
87+
}
8388

8489
nodeOldIsTrafficProxy := false
8590
nodeNewIsTrafficProxy := false

docs/guide/targetgroupbinding/targetgroupbinding.md

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ TargetGroupBinding CR supports TargetGroups of either `instance` or `ip` TargetT
1818

1919

2020
## Sample YAML
21-
```
21+
```yaml
2222
apiVersion: elbv2.k8s.aws/v1beta1
2323
kind: TargetGroupBinding
2424
metadata:
@@ -30,5 +30,48 @@ spec:
3030
targetGroupARN: <arn-to-targetGroup>
3131
```
3232
33+
34+
## NodeSelector
35+
36+
### Default Node Selector
37+
38+
For `TargetType: instance`, all nodes of a cluster that match the following
39+
selector are added to the target group by default:
40+
41+
```yaml
42+
matchExpressions:
43+
- key: node-role.kubernetes.io/master
44+
operator: DoesNotExist
45+
- key: node.kubernetes.io/exclude-from-external-load-balancers
46+
operator: DoesNotExist
47+
- key: alpha.service-controller.kubernetes.io/exclude-balancer
48+
operator: DoesNotExist
49+
- key: eks.amazonaws.com/compute-type
50+
operator: NotIn
51+
values: ["fargate"]
52+
```
53+
54+
### Custom Node Selector
55+
56+
TargetGroupBinding CR supports `NodeSelector` which is a
57+
[LabelSelector][LabelSelector]. This will select nodes to attach to the
58+
`instance` TargetType target group and **is merged with the default node
59+
selector above**.
60+
61+
```yaml
62+
apiVersion: elbv2.k8s.aws/v1beta1
63+
kind: TargetGroupBinding
64+
metadata:
65+
name: my-tgb
66+
spec:
67+
nodeSelector:
68+
matchLabels:
69+
foo: bar
70+
...
71+
```
72+
73+
3374
## Reference
34-
See the [reference](./spec.md) for TargetGroupBinding CR
75+
See the [reference](./spec.md) for TargetGroupBinding CR
76+
77+
[LabelSelector]: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#labelselector-v1-meta

pkg/backend/endpoint_utils.go

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const (
1414
)
1515

1616
var (
17+
// Remember to update docs/guide/targetgroupbinding/targetgroupbinding.md if changing
1718
defaultTrafficProxyNodeLabelSelector = metav1.LabelSelector{
1819
MatchExpressions: []metav1.LabelSelectorRequirement{
1920
{
@@ -38,8 +39,18 @@ var (
3839
)
3940

4041
// GetTrafficProxyNodeSelector returns the trafficProxy node label selector for specific targetGroupBinding.
41-
func GetTrafficProxyNodeSelector(_ *elbv2api.TargetGroupBinding) labels.Selector {
42-
// TODO: consider expose nodeSelector on targetGroupBindings, so users can optionally specify different nodes for different tgbs.
43-
selector, _ := metav1.LabelSelectorAsSelector(&defaultTrafficProxyNodeLabelSelector)
44-
return selector
42+
func GetTrafficProxyNodeSelector(tgb *elbv2api.TargetGroupBinding) (labels.Selector, error) {
43+
selector, err := metav1.LabelSelectorAsSelector(&defaultTrafficProxyNodeLabelSelector)
44+
if err != nil {
45+
return nil, err
46+
}
47+
if tgb.Spec.NodeSelector != nil {
48+
customSelector, err := metav1.LabelSelectorAsSelector(tgb.Spec.NodeSelector)
49+
if err != nil {
50+
return nil, err
51+
}
52+
req, _ := customSelector.Requirements()
53+
selector = selector.Add(req...)
54+
}
55+
return selector, nil
4556
}

pkg/backend/endpoint_utils_test.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package backend
2+
3+
import (
4+
"errors"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
9+
"k8s.io/apimachinery/pkg/labels"
10+
"k8s.io/apimachinery/pkg/selection"
11+
elbv2api "sigs.k8s.io/aws-load-balancer-controller/apis/elbv2/v1beta1"
12+
)
13+
14+
func TestDefaultTrafficProxyNodeLabelSelector(t *testing.T) {
15+
// Test that the default is able to be converted into a label selector
16+
_, err := metav1.LabelSelectorAsSelector(&defaultTrafficProxyNodeLabelSelector)
17+
assert.NoError(t, err)
18+
}
19+
20+
func TestGetTrafficProxyNodeSelector(t *testing.T) {
21+
// Set up the labels.Selector expected objects
22+
defaultSelector, _ := metav1.LabelSelectorAsSelector(&defaultTrafficProxyNodeLabelSelector)
23+
reqs, _ := defaultSelector.Requirements()
24+
25+
customSelector := labels.NewSelector()
26+
req, _ := labels.NewRequirement("key", selection.Equals, []string{"value"})
27+
customSelector = customSelector.Add(*req)
28+
customSelector = customSelector.Add(reqs...) // add default selector rules as well
29+
30+
tests := []struct {
31+
name string
32+
targetGroupBinding *elbv2api.TargetGroupBinding
33+
want labels.Selector
34+
wantErr error
35+
}{
36+
{
37+
name: "default node selector when not specified",
38+
targetGroupBinding: &elbv2api.TargetGroupBinding{},
39+
want: defaultSelector,
40+
},
41+
{
42+
name: "selector from TargetGroupBinding",
43+
targetGroupBinding: &elbv2api.TargetGroupBinding{
44+
Spec: elbv2api.TargetGroupBindingSpec{
45+
NodeSelector: &metav1.LabelSelector{
46+
MatchLabels: map[string]string{
47+
"key": "value",
48+
},
49+
},
50+
},
51+
},
52+
want: customSelector,
53+
},
54+
{
55+
name: "error with bad selector",
56+
targetGroupBinding: &elbv2api.TargetGroupBinding{
57+
Spec: elbv2api.TargetGroupBindingSpec{
58+
NodeSelector: &metav1.LabelSelector{
59+
MatchExpressions: []metav1.LabelSelectorRequirement{
60+
{Key: "key", Operator: "BadOperatorValue", Values: []string{"value"}},
61+
},
62+
},
63+
},
64+
},
65+
wantErr: errors.New("\"BadOperatorValue\" is not a valid pod selector operator"),
66+
},
67+
}
68+
for _, tt := range tests {
69+
t.Run(tt.name, func(t *testing.T) {
70+
got, err := GetTrafficProxyNodeSelector(tt.targetGroupBinding)
71+
if tt.wantErr != nil {
72+
assert.EqualError(t, err, tt.wantErr.Error())
73+
} else {
74+
assert.Equal(t, tt.want, got)
75+
}
76+
})
77+
}
78+
}

pkg/targetgroupbinding/resource_manager.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"context"
55
"encoding/json"
66
"fmt"
7+
"time"
8+
79
awssdk "github.com/aws/aws-sdk-go/aws"
810
"github.com/aws/aws-sdk-go/aws/awserr"
911
elbv2sdk "github.com/aws/aws-sdk-go/service/elbv2"
@@ -22,7 +24,6 @@ import (
2224
"sigs.k8s.io/aws-load-balancer-controller/pkg/networking"
2325
"sigs.k8s.io/aws-load-balancer-controller/pkg/runtime"
2426
"sigs.k8s.io/controller-runtime/pkg/client"
25-
"time"
2627
)
2728

2829
const defaultTargetHealthRequeueDuration = 15 * time.Second
@@ -137,7 +138,11 @@ func (m *defaultResourceManager) reconcileWithIPTargetType(ctx context.Context,
137138

138139
func (m *defaultResourceManager) reconcileWithInstanceTargetType(ctx context.Context, tgb *elbv2api.TargetGroupBinding) error {
139140
svcKey := buildServiceReferenceKey(tgb, tgb.Spec.ServiceRef)
140-
nodeSelector := backend.GetTrafficProxyNodeSelector(tgb)
141+
nodeSelector, err := backend.GetTrafficProxyNodeSelector(tgb)
142+
if err != nil {
143+
return err
144+
}
145+
141146
resolveOpts := []backend.EndpointResolveOption{backend.WithNodeSelector(nodeSelector)}
142147
endpoints, err := m.endpointResolver.ResolveNodePortEndpoints(ctx, svcKey, tgb.Spec.ServiceRef.Port, resolveOpts...)
143148
if err != nil {

webhooks/elbv2/targetgroupbinding_validator.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@ package elbv2
22

33
import (
44
"context"
5+
"strings"
6+
57
"github.com/go-logr/logr"
68
"github.com/pkg/errors"
79
"k8s.io/apimachinery/pkg/runtime"
810
elbv2api "sigs.k8s.io/aws-load-balancer-controller/apis/elbv2/v1beta1"
911
"sigs.k8s.io/aws-load-balancer-controller/pkg/webhook"
1012
ctrl "sigs.k8s.io/controller-runtime"
1113
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
12-
"strings"
1314
)
1415

1516
const apiPathValidateELBv2TargetGroupBinding = "/validate-elbv2-k8s-aws-v1beta1-targetgroupbinding"
@@ -36,6 +37,9 @@ func (v *targetGroupBindingValidator) ValidateCreate(ctx context.Context, obj ru
3637
if err := v.checkRequiredFields(tgb); err != nil {
3738
return err
3839
}
40+
if err := v.checkNodeSelector(tgb); err != nil {
41+
return err
42+
}
3943
return nil
4044
}
4145

@@ -48,7 +52,9 @@ func (v *targetGroupBindingValidator) ValidateUpdate(ctx context.Context, obj ru
4852
if err := v.checkImmutableFields(tgb, oldTgb); err != nil {
4953
return err
5054
}
51-
55+
if err := v.checkNodeSelector(tgb); err != nil {
56+
return err
57+
}
5258
return nil
5359
}
5460

@@ -87,6 +93,14 @@ func (v *targetGroupBindingValidator) checkImmutableFields(tgb *elbv2api.TargetG
8793
return nil
8894
}
8995

96+
//checkNodeSelector ensures that NodeSelector is only set when TargetType is ip
97+
func (v *targetGroupBindingValidator) checkNodeSelector(tgb *elbv2api.TargetGroupBinding) error {
98+
if (*tgb.Spec.TargetType == elbv2api.TargetTypeIP) && (tgb.Spec.NodeSelector != nil) {
99+
return errors.Errorf("TargetGroupBinding cannot set NodeSelector when TargetType is ip")
100+
}
101+
return nil
102+
}
103+
90104
// +kubebuilder:webhook:path=/validate-elbv2-k8s-aws-v1beta1-targetgroupbinding,mutating=false,failurePolicy=fail,groups=elbv2.k8s.aws,resources=targetgroupbindings,verbs=create;update,versions=v1beta1,name=vtargetgroupbinding.elbv2.k8s.aws,sideEffects=None,webhookVersions=v1beta1
91105

92106
func (v *targetGroupBindingValidator) SetupWithManager(mgr ctrl.Manager) {

0 commit comments

Comments
 (0)