Skip to content

Commit 62d13d1

Browse files
authored
Merge pull request #4338 from praddy26/feat-config-tg-diff-ports
feat: Support configure target-group-attributes for different service port #4326
2 parents 7cd6d46 + f587f69 commit 62d13d1

File tree

3 files changed

+291
-25
lines changed

3 files changed

+291
-25
lines changed

docs/guide/service/annotations.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,13 +264,27 @@ for proxy protocol v2 configuration.
264264
The only valid value for this annotation is `*`.
265265

266266
- <a name="target-group-attributes">`service.beta.kubernetes.io/aws-load-balancer-target-group-attributes`</a> specifies the
267-
[Target Group Attributes](https://docs.aws.amazon.com/elasticloadbalancing/latest/network/load-balancer-target-groups.html#target-group-attributes) to be configured.
267+
[Target Group Attributes](https://docs.aws.amazon.com/elasticloadbalancing/latest/network/load-balancer-target-groups.html#target-group-attributes) to be configured. You can specify attributes globally for all target groups or override them for specific ports using port-specific annotations.
268268

269269
!!!example
270270
- set the deregistration delay to 120 seconds (available range is 0-3600 seconds)
271271
```
272+
# Global attributes for all target groups
272273
service.beta.kubernetes.io/aws-load-balancer-target-group-attributes: deregistration_delay.timeout_seconds=120
274+
275+
# Port-specific attributes (overrides global attributes for port 3306)
276+
service.beta.kubernetes.io/aws-load-balancer-target-group-attributes.3306: proxy_protocol_v2.client_to_server.header_placement=on_first_ack
273277
```
278+
279+
- Port-specific attributes with empty values
280+
```
281+
# Global attributes for all target groups
282+
service.beta.kubernetes.io/aws-load-balancer-target-group-attributes: target.group-attr-1=80
283+
284+
# Port-specific attributes (empty values don't override global attributes)
285+
service.beta.kubernetes.io/aws-load-balancer-target-group-attributes.3306: target.group-attr-1=
286+
```
287+
Note: When a port-specific attribute value is empty, it will not override the global attribute value. This allows you to selectively remove attributes for specific ports without affecting other ports that should inherit the global value.
274288
- enable source IP affinity
275289
```
276290
service.beta.kubernetes.io/aws-load-balancer-target-group-attributes: stickiness.enabled=true,stickiness.type=source_ip

pkg/service/model_build_target_group.go

Lines changed: 73 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"encoding/hex"
77
"fmt"
88
"regexp"
9-
"sigs.k8s.io/aws-load-balancer-controller/pkg/shared_constants"
109
"sort"
1110
"strconv"
1211
"strings"
@@ -24,6 +23,7 @@ import (
2423
elbv2model "sigs.k8s.io/aws-load-balancer-controller/pkg/model/elbv2"
2524
elbv2modelk8s "sigs.k8s.io/aws-load-balancer-controller/pkg/model/elbv2/k8s"
2625
"sigs.k8s.io/aws-load-balancer-controller/pkg/networking"
26+
"sigs.k8s.io/aws-load-balancer-controller/pkg/shared_constants"
2727
)
2828

2929
func (t *defaultModelBuildTask) buildTargetGroup(ctx context.Context, port corev1.ServicePort, tgProtocol elbv2model.Protocol, scheme elbv2model.LoadBalancerScheme) (*elbv2model.TargetGroup, error) {
@@ -208,18 +208,75 @@ func (t *defaultModelBuildTask) buildTargetGroupName(_ context.Context, svcPort
208208
return fmt.Sprintf("k8s-%.8s-%.8s-%.10s", sanitizedNamespace, sanitizedName, uuid)
209209
}
210210

211-
func (t *defaultModelBuildTask) buildTargetGroupAttributes(_ context.Context, port corev1.ServicePort) ([]elbv2model.TargetGroupAttribute, error) {
212-
var rawAttributes map[string]string
213-
if _, err := t.annotationParser.ParseStringMapAnnotation(annotations.SvcLBSuffixTargetGroupAttributes, &rawAttributes, t.service.Annotations); err != nil {
211+
func (t *defaultModelBuildTask) buildPortSpecificTargetGroupAttributes(_ context.Context, port corev1.ServicePort) (map[string]string, error) {
212+
attributes := make(map[string]string)
213+
214+
// Parse and validate base attributes first
215+
baseAttrs, err := t.validateAndParseAttributes(annotations.SvcLBSuffixTargetGroupAttributes)
216+
if err != nil {
214217
return nil, err
215218
}
216-
if rawAttributes == nil {
217-
rawAttributes = make(map[string]string)
219+
220+
for k, v := range baseAttrs {
221+
attributes[k] = v
222+
}
223+
224+
// Parse and validate port-specific attributes
225+
portAnnotation := fmt.Sprintf("%s.%d", annotations.SvcLBSuffixTargetGroupAttributes, port.Port)
226+
portAttrs, err := t.validateAndParseAttributes(portAnnotation)
227+
if err != nil {
228+
return nil, err
218229
}
219-
if _, ok := rawAttributes[shared_constants.TGAttributeProxyProtocolV2Enabled]; !ok {
220-
rawAttributes[shared_constants.TGAttributeProxyProtocolV2Enabled] = strconv.FormatBool(t.defaultProxyProtocolV2Enabled)
230+
231+
for k, v := range portAttrs {
232+
// If port-specific value is empty, keep the base value
233+
if v != "" {
234+
attributes[k] = v
235+
}
236+
}
237+
238+
return attributes, nil
239+
}
240+
241+
func (t *defaultModelBuildTask) validateAndParseAttributes(annotation string) (map[string]string, error) {
242+
var attrs map[string]string
243+
if _, err := t.annotationParser.ParseStringMapAnnotation(annotation, &attrs, t.service.Annotations); err != nil {
244+
return nil, err
245+
}
246+
if attrs == nil {
247+
return nil, nil
221248
}
222249

250+
// Validate special attributes
251+
for k, v := range attrs {
252+
if k == shared_constants.TGAttributePreserveClientIPEnabled {
253+
if _, err := strconv.ParseBool(v); err != nil {
254+
return nil, errors.Wrapf(err, "failed to parse attribute %v=%v", k, v)
255+
}
256+
}
257+
}
258+
return attrs, nil
259+
}
260+
261+
func (t *defaultModelBuildTask) buildTargetGroupAttributes(ctx context.Context, port corev1.ServicePort) ([]elbv2model.TargetGroupAttribute, error) {
262+
// Start with defaults
263+
rawAttributes := make(map[string]string)
264+
rawAttributes[shared_constants.TGAttributeProxyProtocolV2Enabled] = strconv.FormatBool(t.defaultProxyProtocolV2Enabled)
265+
266+
// Get base and port-specific attributes
267+
baseAndPortAttributes, err := t.buildPortSpecificTargetGroupAttributes(ctx, port)
268+
if err != nil {
269+
return nil, err
270+
}
271+
272+
for k, v := range baseAndPortAttributes {
273+
rawAttributes[k] = v
274+
}
275+
276+
// Handle proxy protocol settings - these override any previous settings
277+
currentPortStr := strconv.FormatInt(int64(port.Port), 10)
278+
279+
// Check proxy protocol per target group first
223280
var proxyProtocolPerTG string
224281
if t.annotationParser.ParseStringAnnotation(annotations.SvcLBSuffixProxyProtocolPerTargetGroup, &proxyProtocolPerTG, t.service.Annotations) {
225282
ports := strings.Split(proxyProtocolPerTG, ",")
@@ -234,14 +291,14 @@ func (t *defaultModelBuildTask) buildTargetGroupAttributes(_ context.Context, po
234291
}
235292
}
236293

237-
currentPortStr := strconv.FormatInt(int64(port.Port), 10)
238294
if _, enabled := enabledPorts[currentPortStr]; enabled {
239295
rawAttributes[shared_constants.TGAttributeProxyProtocolV2Enabled] = "true"
240296
} else {
241297
rawAttributes[shared_constants.TGAttributeProxyProtocolV2Enabled] = "false"
242298
}
243299
}
244300

301+
// Global proxy protocol override takes precedence over per-target group settings
245302
proxyV2Annotation := ""
246303
if exists := t.annotationParser.ParseStringAnnotation(annotations.SvcLBSuffixProxyProtocol, &proxyV2Annotation, t.service.Annotations); exists {
247304
if proxyV2Annotation != "*" {
@@ -250,15 +307,15 @@ func (t *defaultModelBuildTask) buildTargetGroupAttributes(_ context.Context, po
250307
rawAttributes[shared_constants.TGAttributeProxyProtocolV2Enabled] = "true"
251308
}
252309

253-
if rawPreserveIPEnabled, ok := rawAttributes[shared_constants.TGAttributePreserveClientIPEnabled]; ok {
254-
_, err := strconv.ParseBool(rawPreserveIPEnabled)
255-
if err != nil {
256-
return nil, errors.Wrapf(err, "failed to parse attribute %v=%v", shared_constants.TGAttributePreserveClientIPEnabled, rawPreserveIPEnabled)
257-
}
258-
}
259-
310+
// Convert map to sorted array of attributes
260311
attributes := make([]elbv2model.TargetGroupAttribute, 0, len(rawAttributes))
261312
for attrKey, attrValue := range rawAttributes {
313+
// Special handling for empty values:
314+
// - Skip empty proxy protocol attribute (use default)
315+
// - Preserve other empty values as they may be intentional overrides
316+
if attrValue == "" && attrKey == shared_constants.TGAttributeProxyProtocolV2Enabled {
317+
continue
318+
}
262319
attributes = append(attributes, elbv2model.TargetGroupAttribute{
263320
Key: attrKey,
264321
Value: attrValue,

0 commit comments

Comments
 (0)