Skip to content

Commit 9ff2282

Browse files
authored
Merge pull request #4328 from praddy26/fix-4279-resource-tags-propogation
fix: Resource tags don't propagate to frontend NLB #4279
2 parents 7c7bc8e + 1b5b4b9 commit 9ff2282

File tree

5 files changed

+335
-11
lines changed

5 files changed

+335
-11
lines changed

docs/guide/ingress/annotations.md

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ You can add annotations to kubernetes Ingress and Service objects to customize t
8282
| [alb.ingress.kubernetes.io/frontend-nlb-healthcheck-healthy-threshold-count](#frontend-nlb-healthcheck-healthy-threshold-count) | integer |3| Ingress | N/A |
8383
| [alb.ingress.kubernetes.io/frontend-nlb-healthcheck-unhealthy-threshold-count](#frontend-nlb-healthcheck-unhealthy-threshold-count) | integer |3| Ingress | N/A |
8484
| [alb.ingress.kubernetes.io/frontend-nlb-healthcheck-success-codes](#frontend-nlb-healthcheck-success-codes) | string |200| Ingress | N/A |
85+
| [alb.ingress.kubernetes.io/frontend-nlb-tags](#frontend-nlb-tags) | stringMap | N/A | Ingress | Exclusive |
8586

8687
## IngressGroup
8788
IngressGroup feature enables you to group multiple Ingress resources together.
@@ -104,7 +105,7 @@ By default, Ingresses don't belong to any IngressGroup, and we treat it as a "im
104105
other Kubernetes users may create/modify their Ingresses to belong to the same IngressGroup, and can thus add more rules or overwrite existing rules with higher priority to the ALB for your Ingress.
105106

106107
We'll add more fine-grained access-control in future versions.
107-
108+
108109
!!!note "Rename behavior"
109110
The ALB for an IngressGroup is found by searching for an AWS tag `ingress.k8s.aws/stack` tag with the name of the IngressGroup as its value. For an implicit IngressGroup, the value is `namespace/ingressname`.
110111

@@ -195,9 +196,9 @@ Traffic Routing can be controlled with following annotations:
195196
- Once defined on a single Ingress, it impacts every Ingress within the IngressGroup.
196197

197198
!!!note "Annotation Behavior"
198-
199+
199200
- This annotation **takes effect only during the creation** of the Ingress. If the Ingress already exists, the change will not be applied until the Ingress is **deleted and recreated**.
200-
201+
201202
!!!example
202203
```
203204
alb.ingress.kubernetes.io/load-balancer-name: custom-name
@@ -499,9 +500,9 @@ Traffic Routing can be controlled with following annotations:
499500
name: use-annotation
500501
```
501502

502-
!!!note
503+
!!!note
503504
If you are using `alb.ingress.kubernetes.io/target-group-attributes` with `stickiness.enabled=true`, you should add `TargetGroupStickinessConfig` under `alb.ingress.kubernetes.io/actions.weighted-routing`
504-
505+
505506
!!!example
506507

507508
```yaml
@@ -843,10 +844,10 @@ TLS support can be controlled with the following annotations:
843844

844845
- <a name="mutual-authentication">`alb.ingress.kubernetes.io/mutual-authentication`</a> specifies the mutual authentication configuration that should be assigned to the Application Load Balancer secure listener ports. See [Mutual authentication with TLS](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/mutual-authentication.html) in the AWS documentation for more details.
845846

846-
!!!note
847+
!!!note
847848
- This annotation is not applicable for Outposts, Local Zones or Wavelength zones.
848849
- "Configuration Options"
849-
- `port: listen port `
850+
- `port: listen port `
850851
- Must be an HTTPS port specified by [listen-ports](#listen-ports).
851852
- `mode: "off" (default) | "passthrough" | "verify"`
852853
- `verify` mode requires an existing trust store resource.
@@ -857,7 +858,7 @@ TLS support can be controlled with the following annotations:
857858
- `ignoreClientCertificateExpiry : true | false (default)`
858859
- `advertiseTrustStoreCaNames : "on" | "off" (default)`
859860
- Once the Mutual Authentication is set, to turn it off, you will have to explicitly pass in this annotation with `mode : "off"`.
860-
861+
861862
!!!example
862863
- [listen-ports](#listen-ports) specifies four HTTPS ports: `80, 443, 8080, 8443`
863864
- listener `HTTPS:80` will be set to `passthrough` mode
@@ -910,7 +911,7 @@ Custom attributes to LoadBalancers and TargetGroups can be controlled with follo
910911
```
911912
- set client_keep_alive to 3600 seconds
912913
```
913-
alb.ingress.kubernetes.io/load-balancer-attributes: client_keep_alive.seconds=3600
914+
alb.ingress.kubernetes.io/load-balancer-attributes: client_keep_alive.seconds=3600
914915
```
915916
- enable [connection logs](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-connection-logs.html)
916917
```
@@ -1077,11 +1078,11 @@ Load balancer capacity unit reservation can be configured via following annotati
10771078
## Enable frontend NLB
10781079
When this option is set to true, the controller will automatically provision a Network Load Balancer and register the Application Load Balancer as its target. Additional annotations are available to customize the NLB configurations, including options for scheme, security groups, subnets, and health check. The ingress resource will have two status entries, one for the NLB DNS and one for the ALB DNS. This allows users to combine the benefits of NLB and ALB into a single solution, leveraging NLB features like static IP address and PrivateLink, while retaining the rich routing capabilities of ALB.
10791080
1080-
!!!warning
1081+
!!!warning
10811082
- If you need to change the ALB [scheme](#scheme), make sure to disable this feature first. Changing the scheme will create a new ALB, which could interfere with the current configuration.
10821083
- If you create ingress and enable the feature at once, provisioning the NLB and registering the ALB as target can take up to 3-4 mins to complete.
10831084
1084-
- <a name="enable-frontend-nlb">`alb.ingress.kubernetes.io/enable-frontend-nlb`</a> enables frontend Network Load Balancer functionality.
1085+
- <a name="enable-frontend-nlb">`alb.ingress.kubernetes.io/enable-frontend-nlb`</a> enables frontend Network Load Balancer functionality.
10851086
10861087
!!!example
10871088
- Enable frontend nlb
@@ -1187,3 +1188,14 @@ When this option is set to true, the controller will automatically provision a N
11871188
```
11881189
alb.ingress.kubernetes.io/frontend-nlb-healthcheck-success-codes: '200'
11891190
```
1191+
1192+
- <a name="frontend-nlb-tags">`alb.ingress.kubernetes.io/frontend-nlb-tags`</a> specifies additional tags to be applied to the frontend NLB. If not specified, the tags from ALB (specified via `alb.ingress.kubernetes.io/tags`) will be propagated to the NLB.
1193+
1194+
!!!note "Merge Behavior"
1195+
`frontend-nlb-tags` is exclusive across all Ingresses in IngressGroup.
1196+
If specified on multiple Ingresses within IngressGroup, the values must match.
1197+
1198+
!!!example
1199+
```
1200+
alb.ingress.kubernetes.io/frontend-nlb-tags: Environment=prod,Team=platform
1201+
```

pkg/annotations/constants.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ const (
7373
IngressSuffixFrontendNlbHealthCheckHealthyThresholdCount = "frontend-nlb-healthcheck-healthy-threshold-count"
7474
IngressSuffixFrontendNlHealthCheckbUnhealthyThresholdCount = "frontend-nlb-healthcheck-unhealthy-threshold-count"
7575
IngressSuffixFrontendNlbHealthCheckSuccessCodes = "frontend-nlb-healthcheck-success-codes"
76+
IngressSuffixFrontendNlbTags = "frontend-nlb-tags"
7677

7778
// NLB annotation suffixes
7879
// prefixes service.beta.kubernetes.io, service.kubernetes.io

pkg/ingress/model_build_frontend_nlb.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,13 +184,19 @@ func (t *defaultModelBuildTask) buildFrontendNlbSpec(ctx context.Context, scheme
184184
return elbv2model.LoadBalancerSpec{}, err
185185
}
186186

187+
tags, err := t.buildFrontendNlbTags(ctx, alb)
188+
if err != nil {
189+
return elbv2model.LoadBalancerSpec{}, err
190+
}
191+
187192
spec := elbv2model.LoadBalancerSpec{
188193
Name: name,
189194
Type: elbv2model.LoadBalancerTypeNetwork,
190195
Scheme: scheme,
191196
IPAddressType: alb.Spec.IPAddressType,
192197
SecurityGroups: securityGroups,
193198
SubnetMappings: subnetMappings,
199+
Tags: tags,
194200
}
195201

196202
return spec, nil
@@ -765,3 +771,38 @@ func mergeHealthCheckField[T comparable](fieldName string, finalValue **T, curre
765771
}
766772
return nil
767773
}
774+
775+
func (t *defaultModelBuildTask) buildFrontendNlbTags(ctx context.Context, alb *elbv2model.LoadBalancer) (map[string]string, error) {
776+
// First check for frontend-nlb specific tags
777+
var frontendNlbTags map[string]string
778+
for _, member := range t.ingGroup.Members {
779+
rawFrontendNlbTags := make(map[string]string)
780+
exists, err := t.annotationParser.ParseStringMapAnnotation(annotations.IngressSuffixFrontendNlbTags, &rawFrontendNlbTags, member.Ing.Annotations)
781+
if err != nil {
782+
return nil, err
783+
}
784+
if !exists {
785+
continue
786+
}
787+
788+
if frontendNlbTags != nil {
789+
// If we already found tags from another ingress in the group, they must match
790+
if !cmp.Equal(frontendNlbTags, rawFrontendNlbTags) {
791+
return nil, errors.Errorf("conflicting frontend NLB tags: %v | %v", frontendNlbTags, rawFrontendNlbTags)
792+
}
793+
} else {
794+
frontendNlbTags = rawFrontendNlbTags
795+
}
796+
}
797+
798+
if frontendNlbTags == nil {
799+
// If no frontend-nlb specific tags are found, use the ALB tags
800+
albTags, err := t.buildLoadBalancerTags(ctx)
801+
if err != nil {
802+
return nil, err
803+
}
804+
return albTags, nil
805+
}
806+
807+
return frontendNlbTags, nil
808+
}

pkg/ingress/model_build_frontend_nlb_test.go

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -662,6 +662,207 @@ func Test_buildEnableFrontendNlbViaAnnotation(t *testing.T) {
662662
}
663663
}
664664

665+
func Test_buildFrontendNlbTags(t *testing.T) {
666+
tests := []struct {
667+
name string
668+
ingGroup Group
669+
defaultTags map[string]string
670+
wantTags map[string]string
671+
wantErr bool
672+
errMsg string
673+
}{
674+
{
675+
name: "no tags specified",
676+
ingGroup: Group{
677+
Members: []ClassifiedIngress{
678+
{
679+
Ing: &networking.Ingress{
680+
ObjectMeta: metav1.ObjectMeta{
681+
Namespace: "test-ns",
682+
Name: "ing-1",
683+
Annotations: map[string]string{},
684+
},
685+
},
686+
},
687+
},
688+
},
689+
defaultTags: nil,
690+
wantTags: make(map[string]string), // Expect an empty map, not nil
691+
wantErr: false,
692+
},
693+
{
694+
name: "frontend-nlb-specific tags",
695+
ingGroup: Group{
696+
Members: []ClassifiedIngress{
697+
{
698+
Ing: &networking.Ingress{
699+
ObjectMeta: metav1.ObjectMeta{
700+
Namespace: "test-ns",
701+
Name: "ing-1",
702+
Annotations: map[string]string{
703+
"alb.ingress.kubernetes.io/frontend-nlb-tags": "key1=value1,key2=value2",
704+
},
705+
},
706+
},
707+
},
708+
},
709+
},
710+
defaultTags: map[string]string{
711+
"default": "value",
712+
},
713+
wantTags: map[string]string{
714+
"key1": "value1",
715+
"key2": "value2",
716+
},
717+
wantErr: false,
718+
},
719+
{
720+
name: "ALB tags propagation when no frontend-nlb-tags",
721+
ingGroup: Group{
722+
Members: []ClassifiedIngress{
723+
{
724+
Ing: &networking.Ingress{
725+
ObjectMeta: metav1.ObjectMeta{
726+
Namespace: "test-ns",
727+
Name: "ing-1",
728+
Annotations: map[string]string{
729+
"alb.ingress.kubernetes.io/tags": "key1=value1,key2=value2",
730+
},
731+
},
732+
},
733+
},
734+
},
735+
},
736+
defaultTags: nil,
737+
wantTags: map[string]string{
738+
"key1": "value1",
739+
"key2": "value2",
740+
},
741+
wantErr: false,
742+
},
743+
{
744+
name: "frontend-nlb-tags take precedence over ALB tags",
745+
ingGroup: Group{
746+
Members: []ClassifiedIngress{
747+
{
748+
Ing: &networking.Ingress{
749+
ObjectMeta: metav1.ObjectMeta{
750+
Namespace: "test-ns",
751+
Name: "ing-1",
752+
Annotations: map[string]string{
753+
"alb.ingress.kubernetes.io/frontend-nlb-tags": "nlb-key=nlb-value",
754+
"alb.ingress.kubernetes.io/tags": "alb-key=alb-value",
755+
},
756+
},
757+
},
758+
},
759+
},
760+
},
761+
defaultTags: map[string]string{
762+
"default": "value",
763+
},
764+
wantTags: map[string]string{
765+
"nlb-key": "nlb-value",
766+
},
767+
wantErr: false,
768+
},
769+
{
770+
name: "conflicting frontend-nlb-tags",
771+
ingGroup: Group{
772+
Members: []ClassifiedIngress{
773+
{
774+
Ing: &networking.Ingress{
775+
ObjectMeta: metav1.ObjectMeta{
776+
Namespace: "test-ns",
777+
Name: "ing-1",
778+
Annotations: map[string]string{
779+
"alb.ingress.kubernetes.io/frontend-nlb-tags": "key1=value1",
780+
},
781+
},
782+
},
783+
},
784+
{
785+
Ing: &networking.Ingress{
786+
ObjectMeta: metav1.ObjectMeta{
787+
Namespace: "test-ns",
788+
Name: "ing-2",
789+
Annotations: map[string]string{
790+
"alb.ingress.kubernetes.io/frontend-nlb-tags": "key1=value2",
791+
},
792+
},
793+
},
794+
},
795+
},
796+
},
797+
defaultTags: nil,
798+
wantTags: nil,
799+
wantErr: true,
800+
errMsg: "conflicting frontend NLB tags",
801+
},
802+
{
803+
name: "consistent frontend-nlb-tags across ingresses",
804+
ingGroup: Group{
805+
Members: []ClassifiedIngress{
806+
{
807+
Ing: &networking.Ingress{
808+
ObjectMeta: metav1.ObjectMeta{
809+
Namespace: "test-ns",
810+
Name: "ing-1",
811+
Annotations: map[string]string{
812+
"alb.ingress.kubernetes.io/frontend-nlb-tags": "key1=value1",
813+
},
814+
},
815+
},
816+
},
817+
{
818+
Ing: &networking.Ingress{
819+
ObjectMeta: metav1.ObjectMeta{
820+
Namespace: "test-ns",
821+
Name: "ing-2",
822+
Annotations: map[string]string{
823+
"alb.ingress.kubernetes.io/frontend-nlb-tags": "key1=value1",
824+
},
825+
},
826+
},
827+
},
828+
},
829+
},
830+
defaultTags: nil,
831+
wantTags: map[string]string{
832+
"key1": "value1",
833+
},
834+
wantErr: false,
835+
},
836+
}
837+
838+
for _, tt := range tests {
839+
t.Run(tt.name, func(t *testing.T) {
840+
// Create a mock task that embeds defaultModelBuildTask and overrides buildLoadBalancerTags
841+
task := &defaultModelBuildTask{
842+
ingGroup: tt.ingGroup,
843+
annotationParser: annotations.NewSuffixAnnotationParser("alb.ingress.kubernetes.io"),
844+
// Default implementation will return an empty map when no tags are specified
845+
defaultTags: tt.defaultTags,
846+
}
847+
848+
got, err := task.buildFrontendNlbTags(context.Background(), nil)
849+
850+
if tt.wantErr {
851+
assert.Error(t, err)
852+
if tt.errMsg != "" {
853+
assert.Contains(t, err.Error(), tt.errMsg)
854+
}
855+
} else {
856+
assert.NoError(t, err)
857+
assert.Equal(t, tt.wantTags, got)
858+
if got == nil {
859+
t.Error("got nil map, expected non-nil map")
860+
}
861+
}
862+
})
863+
}
864+
}
865+
665866
func Test_mergeFrontendNlbListenPortConfigs(t *testing.T) {
666867
tests := []struct {
667868
name string

0 commit comments

Comments
 (0)