Skip to content

Commit 924037d

Browse files
kishorjTimothy-Dougherty
authored andcommitted
support target node labels for ingress
1 parent 706852d commit 924037d

File tree

4 files changed

+161
-5
lines changed

4 files changed

+161
-5
lines changed

docs/guide/ingress/annotations.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ You can add annotations to kubernetes Ingress and Service objects to customize t
5454
|[alb.ingress.kubernetes.io/auth-session-timeout](#auth-session-timeout)|integer|'604800'|Ingress,Service|N/A|
5555
|[alb.ingress.kubernetes.io/actions.${action-name}](#actions)|json|N/A|Ingress|N/A|
5656
|[alb.ingress.kubernetes.io/conditions.${conditions-name}](#conditions)|json|N/A|Ingress|N/A|
57+
|[alb.ingress.kubernetes.io/target-node-labels](#target-node-labels)|stringMap|N/A|Ingress,Service|N/A|
5758

5859
## IngressGroup
5960
IngressGroup feature enables you to group multiple Ingress resources together.
@@ -176,6 +177,12 @@ Traffic Routing can be controlled with following annotations:
176177
```
177178
alb.ingress.kubernetes.io/target-type: instance
178179
```
180+
-<a name="target-node-labels">`alb.ingress.kubernetes.io/target-node-labels`</a> specifies which nodes to include in the target group registration for `instance` target type.
181+
182+
!!!example
183+
```
184+
alb.ingress.kubernetes.io/target-node-labels: label1=value1, label2=value2
185+
```
179186

180187
- <a name="backend-protocol">`alb.ingress.kubernetes.io/backend-protocol`</a> specifies the protocol used when route traffic to pods.
181188

pkg/annotations/constants.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ const (
4343
IngressSuffixAuthScope = "auth-scope"
4444
IngressSuffixAuthSessionCookie = "auth-session-cookie"
4545
IngressSuffixAuthSessionTimeout = "auth-session-timeout"
46+
IngressSuffixTargetNodeLabels = "target-node-labels"
4647

4748
// NLB annotation suffixes
4849
// prefixes service.beta.kubernetes.io, service.kubernetes.io

pkg/ingress/model_build_target_group.go

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,19 +35,23 @@ func (t *defaultModelBuildTask) buildTargetGroup(ctx context.Context,
3535
if err != nil {
3636
return nil, err
3737
}
38+
nodeSelector, err := t.buildTargetGroupBindingNodeSelector(ctx, ing, svc, tgSpec.TargetType)
39+
if err != nil {
40+
return nil, err
41+
}
3842
tg := elbv2model.NewTargetGroup(t.stack, tgResID, tgSpec)
3943
t.tgByResID[tgResID] = tg
40-
_ = t.buildTargetGroupBinding(ctx, tg, svc, port)
44+
_ = t.buildTargetGroupBinding(ctx, tg, svc, port, nodeSelector)
4145
return tg, nil
4246
}
4347

44-
func (t *defaultModelBuildTask) buildTargetGroupBinding(ctx context.Context, tg *elbv2model.TargetGroup, svc *corev1.Service, port intstr.IntOrString) *elbv2model.TargetGroupBindingResource {
45-
tgbSpec := t.buildTargetGroupBindingSpec(ctx, tg, svc, port)
48+
func (t *defaultModelBuildTask) buildTargetGroupBinding(ctx context.Context, tg *elbv2model.TargetGroup, svc *corev1.Service, port intstr.IntOrString, nodeSelector *metav1.LabelSelector) *elbv2model.TargetGroupBindingResource {
49+
tgbSpec := t.buildTargetGroupBindingSpec(ctx, tg, svc, port, nodeSelector)
4650
tgb := elbv2model.NewTargetGroupBindingResource(t.stack, tg.ID(), tgbSpec)
4751
return tgb
4852
}
4953

50-
func (t *defaultModelBuildTask) buildTargetGroupBindingSpec(ctx context.Context, tg *elbv2model.TargetGroup, svc *corev1.Service, port intstr.IntOrString) elbv2model.TargetGroupBindingResourceSpec {
54+
func (t *defaultModelBuildTask) buildTargetGroupBindingSpec(ctx context.Context, tg *elbv2model.TargetGroup, svc *corev1.Service, port intstr.IntOrString, nodeSelector *metav1.LabelSelector) elbv2model.TargetGroupBindingResourceSpec {
5155
targetType := elbv2api.TargetType(tg.Spec.TargetType)
5256
tgbNetworking := t.buildTargetGroupBindingNetworking(ctx)
5357
return elbv2model.TargetGroupBindingResourceSpec{
@@ -63,7 +67,8 @@ func (t *defaultModelBuildTask) buildTargetGroupBindingSpec(ctx context.Context,
6367
Name: svc.Name,
6468
Port: port,
6569
},
66-
Networking: tgbNetworking,
70+
Networking: tgbNetworking,
71+
NodeSelector: nodeSelector,
6772
},
6873
},
6974
}
@@ -400,3 +405,21 @@ func (t *defaultModelBuildTask) buildTargetGroupTags(_ context.Context, svcAndIn
400405
func (t *defaultModelBuildTask) buildTargetGroupResourceID(ingKey types.NamespacedName, svcKey types.NamespacedName, port intstr.IntOrString) string {
401406
return fmt.Sprintf("%s/%s-%s:%s", ingKey.Namespace, ingKey.Name, svcKey.Name, port.String())
402407
}
408+
409+
func (t *defaultModelBuildTask) buildTargetGroupBindingNodeSelector(_ context.Context, ing *networking.Ingress, svc *corev1.Service, targetType elbv2model.TargetType) (*metav1.LabelSelector, error) {
410+
if targetType != elbv2model.TargetTypeInstance {
411+
return nil, nil
412+
}
413+
var targetNodeLabels map[string]string
414+
svcAndIngAnnotations := algorithm.MergeStringMap(svc.Annotations, ing.Annotations)
415+
416+
if _, err := t.annotationParser.ParseStringMapAnnotation(annotations.IngressSuffixTargetNodeLabels, &targetNodeLabels, svcAndIngAnnotations); err != nil {
417+
return nil, err
418+
}
419+
if len(targetNodeLabels) == 0 {
420+
return nil, nil
421+
}
422+
return &metav1.LabelSelector{
423+
MatchLabels: targetNodeLabels,
424+
}, nil
425+
}

pkg/ingress/model_build_target_group_test.go

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ package ingress
33
import (
44
"context"
55
awssdk "github.com/aws/aws-sdk-go/aws"
6+
"github.com/pkg/errors"
67
"github.com/stretchr/testify/assert"
78
corev1 "k8s.io/api/core/v1"
9+
networking "k8s.io/api/networking/v1beta1"
810
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
911
"k8s.io/apimachinery/pkg/types"
1012
"k8s.io/apimachinery/pkg/util/intstr"
@@ -510,3 +512,126 @@ func Test_defaultModelBuildTask_buildTargetGroupHealthCheckMatcher(t *testing.T)
510512
})
511513
}
512514
}
515+
516+
func Test_defaultModelBuildTask_buildTargetGroupBindingNodeSelector(t *testing.T) {
517+
type fields struct {
518+
ing *networking.Ingress
519+
svc *corev1.Service
520+
targetType elbv2model.TargetType
521+
}
522+
tests := []struct {
523+
name string
524+
fields fields
525+
want *metav1.LabelSelector
526+
wantErr error
527+
}{
528+
{
529+
name: "no annotation",
530+
fields: fields{
531+
ing: &networking.Ingress{
532+
ObjectMeta: metav1.ObjectMeta{
533+
Annotations: map[string]string{},
534+
},
535+
},
536+
svc: &corev1.Service{},
537+
},
538+
want: nil,
539+
},
540+
{
541+
name: "ingress has annotation",
542+
fields: fields{
543+
ing: &networking.Ingress{
544+
ObjectMeta: metav1.ObjectMeta{
545+
Annotations: map[string]string{
546+
"alb.ingress.kubernetes.io/target-node-labels": "key1=value1, node.label/key2=value.2",
547+
},
548+
},
549+
},
550+
svc: &corev1.Service{},
551+
targetType: elbv2model.TargetTypeInstance,
552+
},
553+
want: &metav1.LabelSelector{
554+
MatchLabels: map[string]string{
555+
"key1": "value1",
556+
"node.label/key2": "value.2",
557+
},
558+
},
559+
},
560+
{
561+
name: "service annotation overrides ingress",
562+
fields: fields{
563+
ing: &networking.Ingress{
564+
ObjectMeta: metav1.ObjectMeta{
565+
Annotations: map[string]string{
566+
"alb.ingress.kubernetes.io/target-node-labels": "key1=value1, node.label/key2=value.2",
567+
},
568+
},
569+
},
570+
svc: &corev1.Service{
571+
ObjectMeta: metav1.ObjectMeta{
572+
Annotations: map[string]string{
573+
"alb.ingress.kubernetes.io/target-node-labels": "service/key1=value1.service, service.node.label/key2=value.2.service",
574+
},
575+
},
576+
},
577+
targetType: elbv2model.TargetTypeInstance,
578+
},
579+
want: &metav1.LabelSelector{
580+
MatchLabels: map[string]string{
581+
"service/key1": "value1.service",
582+
"service.node.label/key2": "value.2.service",
583+
},
584+
},
585+
},
586+
{
587+
name: "target type ip",
588+
fields: fields{
589+
ing: &networking.Ingress{
590+
ObjectMeta: metav1.ObjectMeta{
591+
Annotations: map[string]string{
592+
"alb.ingress.kubernetes.io/target-node-labels": "key1=value1, node.label/key2=value.2",
593+
},
594+
},
595+
},
596+
svc: &corev1.Service{
597+
ObjectMeta: metav1.ObjectMeta{
598+
Annotations: map[string]string{
599+
"alb.ingress.kubernetes.io/target-node-labels": "service/key1=value1.service, service.node.label/key2=value.2.service",
600+
},
601+
},
602+
},
603+
targetType: elbv2model.TargetTypeIP,
604+
},
605+
want: nil,
606+
},
607+
{
608+
name: "annotation parse error",
609+
fields: fields{
610+
ing: &networking.Ingress{
611+
ObjectMeta: metav1.ObjectMeta{
612+
Annotations: map[string]string{
613+
"alb.ingress.kubernetes.io/target-node-labels": "key1",
614+
},
615+
},
616+
},
617+
svc: &corev1.Service{},
618+
targetType: elbv2model.TargetTypeInstance,
619+
},
620+
wantErr: errors.New("failed to parse stringMap annotation, alb.ingress.kubernetes.io/target-node-labels: key1"),
621+
},
622+
}
623+
for _, tt := range tests {
624+
t.Run(tt.name, func(t *testing.T) {
625+
task := &defaultModelBuildTask{
626+
annotationParser: annotations.NewSuffixAnnotationParser("alb.ingress.kubernetes.io"),
627+
}
628+
got, err := task.buildTargetGroupBindingNodeSelector(context.Background(), tt.fields.ing, tt.fields.svc, tt.fields.targetType)
629+
if tt.wantErr != nil {
630+
assert.EqualError(t, err, tt.wantErr.Error())
631+
} else {
632+
assert.NoError(t, err)
633+
assert.Equal(t, tt.want, got)
634+
}
635+
})
636+
}
637+
}

0 commit comments

Comments
 (0)