Skip to content

Commit c70e4ac

Browse files
authored
Merge pull request #1318 from kmala/automated-cherry-pick-of-#1153-#1161-#1215-#1217-#1214-upstream-release-1.31
Automated cherry pick of #1153: e2e/deps: enhance test scenarios with NLB #1161: e2e/loadbalancer: implement hairpin connection cases #1215: refact: e2e tests documenting hooks and enhance logging/steps #1217: e2e/debug: increase data collection on e2e failures #1214: doc/service: describe supported target group attributes
2 parents c407a35 + 6a6becc commit c70e4ac

File tree

9 files changed

+2329
-72
lines changed

9 files changed

+2329
-72
lines changed

docs/service_controller.md

Lines changed: 83 additions & 30 deletions
Large diffs are not rendered by default.

pkg/providers/v1/aws.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,12 @@ const ServiceAnnotationLoadBalancerHCTimeout = "service.beta.kubernetes.io/aws-l
215215
// service to specify, in seconds, the interval between health checks.
216216
const ServiceAnnotationLoadBalancerHCInterval = "service.beta.kubernetes.io/aws-load-balancer-healthcheck-interval"
217217

218+
// ServiceAnnotationLoadBalancerTargetGroupAttributes is the annotation used on the
219+
// service to specify a comma-separated list of key-value pairs which will be applied as
220+
// target group attributes.
221+
// For example: "preserve_client_ip.enabled=false,proxy_protocol_v2.enabled=true"
222+
const ServiceAnnotationLoadBalancerTargetGroupAttributes = "service.beta.kubernetes.io/aws-load-balancer-target-group-attributes"
223+
218224
// ServiceAnnotationLoadBalancerEIPAllocations is the annotation used on the
219225
// service to specify a comma separated list of EIP allocations to use as
220226
// static IP addresses for the NLB. Only supported on elbv2 (NLB)
@@ -266,6 +272,20 @@ const (
266272
regularAvailabilityZoneType = "availability-zone"
267273
)
268274

275+
// Target Group Attributes
276+
// https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2@main/types#TargetGroupAttribute
277+
const (
278+
// targetGroupAttributePreserveClientIPEnabled is the target group attribute preserve_client_ip.enabled.
279+
// Indicates whether client IP preservation is enabled.
280+
// Valid values are true or false.
281+
targetGroupAttributePreserveClientIPEnabled = "preserve_client_ip.enabled"
282+
283+
// targetGroupAttributeProxyProtocolV2Enabled is the target group attribute proxy_protocol_v2.enabled.
284+
// Indicates whether Proxy Protocol version 2 is enabled.
285+
// Valid values are true or false.
286+
targetGroupAttributeProxyProtocolV2Enabled = "proxy_protocol_v2.enabled"
287+
)
288+
269289
// awsTagNameMasterRoles is a set of well-known AWS tag names that indicate the instance is a master
270290
var awsTagNameMasterRoles = sets.NewString("kubernetes.io/role/master", "k8s.io/role/master")
271291

@@ -2097,6 +2117,14 @@ func (c *Cloud) EnsureLoadBalancer(ctx context.Context, clusterName string, apiS
20972117
klog.V(2).Infof("EnsureLoadBalancer(%v, %v, %v, %v, %v, %v, %v)",
20982118
clusterName, apiService.Namespace, apiService.Name, c.region, apiService.Spec.LoadBalancerIP, apiService.Spec.Ports, annotations)
20992119

2120+
// pre-flight validations for EnsureLoadBalancer.
2121+
if err := ensureLoadBalancerValidation(&awsValidationInput{
2122+
apiService: apiService,
2123+
annotations: annotations,
2124+
}); err != nil {
2125+
return nil, err
2126+
}
2127+
21002128
if apiService.Spec.SessionAffinity != v1.ServiceAffinityNone {
21012129
// ELB supports sticky sessions, but only when configured for HTTP/HTTPS
21022130
return nil, fmt.Errorf("unsupported load balancer affinity: %v", apiService.Spec.SessionAffinity)

pkg/providers/v1/aws_loadbalancer.go

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,14 @@ func (c *Cloud) ensureLoadBalancerv2(ctx context.Context, namespacedName types.N
397397
loadBalancer = &loadBalancers.LoadBalancers[0]
398398
}
399399
}
400+
401+
// Reconcile target group attributes.
402+
if _, present := annotations[ServiceAnnotationLoadBalancerTargetGroupAttributes]; present {
403+
if err := c.reconcileTargetGroupsAttributes(ctx, aws.ToString(loadBalancer.LoadBalancerArn), annotations); err != nil {
404+
return nil, fmt.Errorf("error reconciling target group attributes: %q", err)
405+
}
406+
}
407+
400408
return loadBalancer, nil
401409
}
402410

@@ -490,9 +498,156 @@ func (c *Cloud) reconcileLBAttributes(ctx context.Context, loadBalancerArn strin
490498
return fmt.Errorf("unable to update load balancer attributes during attribute sync: %q", err)
491499
}
492500
}
501+
502+
return nil
503+
}
504+
505+
// reconcileTargetGroupsAttributes reconciles the target group attributes for all target groups
506+
// associated with a load balancer to match the desired state specified in service annotations.
507+
// Only supported attributes by controller are reconciled.
508+
//
509+
// Parameters:
510+
// - ctx: context for AWS API calls with timeout and cancellation support
511+
// - lbARN: AWS load balancer ARN to identify which target groups to process
512+
// - annotations: service annotations containing desired target group attribute configuration
513+
//
514+
// Returns:
515+
// - error: validation errors, AWS API errors, or target group attribute update failures
516+
//
517+
// Documentation generated by Cursor AI
518+
func (c *Cloud) reconcileTargetGroupsAttributes(ctx context.Context, lbARN string, annotations map[string]string) error {
519+
if len(lbARN) == 0 {
520+
return fmt.Errorf("error updating target groups attributes: load balancer ARN is empty")
521+
}
522+
523+
describeTargetGroupsOutput, err := c.elbv2.DescribeTargetGroups(ctx, &elbv2.DescribeTargetGroupsInput{
524+
LoadBalancerArn: aws.String(lbARN),
525+
})
526+
if err != nil {
527+
return fmt.Errorf("error updating target groups attributes from load balancer %q: %w", lbARN, err)
528+
}
529+
530+
var errs []error
531+
for _, tg := range describeTargetGroupsOutput.TargetGroups {
532+
err := c.ensureTargetGroupAttributes(ctx, &tg, annotations)
533+
if err != nil {
534+
errs = append(errs, fmt.Errorf("error updating target group attributes for target group %q: %w", aws.ToString(tg.TargetGroupArn), err))
535+
}
536+
}
537+
if len(errs) > 0 {
538+
return fmt.Errorf("one or more errors occurred while updating target group attributes: %v", errs)
539+
}
540+
return nil
541+
}
542+
543+
// ensureTargetGroupAttributes ensures that the target group attributes for a specific
544+
// target group match the desired state specified in service annotations.
545+
//
546+
// Parameters:
547+
// - ctx: context for AWS API calls and cancellation
548+
// - tg: target group object containing ARN, protocol, and type information
549+
// - annotations: service annotations containing desired target group attributes
550+
//
551+
// Returns:
552+
// - error: validation errors, AWS API errors, or attribute building errors
553+
//
554+
// Documentation generated by Cursor AI
555+
func (c *Cloud) ensureTargetGroupAttributes(ctx context.Context, tg *elbv2types.TargetGroup, annotations map[string]string) error {
556+
if tg == nil {
557+
return fmt.Errorf("unable to reconcile target group attributes: target group is required")
558+
}
559+
560+
tgAttributes, err := c.elbv2.DescribeTargetGroupAttributes(ctx, &elbv2.DescribeTargetGroupAttributesInput{
561+
TargetGroupArn: tg.TargetGroupArn,
562+
})
563+
if err != nil {
564+
return fmt.Errorf("unable to retrieve target group attributes during attribute sync: %w", err)
565+
}
566+
567+
desiredTargetGroupAttributes, err := c.buildTargetGroupAttributes(tg, tgAttributes.Attributes, annotations)
568+
if err != nil {
569+
return fmt.Errorf("unable to build target group attributes: %w", err)
570+
}
571+
572+
if len(desiredTargetGroupAttributes) == 0 {
573+
return nil
574+
}
575+
klog.Infof("Updating attributes for target group %q", aws.ToString(tg.TargetGroupArn))
576+
577+
if _, err = c.elbv2.ModifyTargetGroupAttributes(ctx, &elbv2.ModifyTargetGroupAttributesInput{
578+
TargetGroupArn: tg.TargetGroupArn,
579+
Attributes: desiredTargetGroupAttributes,
580+
}); err != nil {
581+
return fmt.Errorf("unable to modify target group attributes during attribute sync: %w", err)
582+
}
583+
klog.Infof("Successfully updated target group attributes for %q", aws.ToString(tg.TargetGroupArn))
584+
493585
return nil
494586
}
495587

588+
// buildTargetGroupAttributes builds the list of target group attributes that need to be modified
589+
// based on the Service annotation, and current attribute values, calculating only the attributes
590+
// to be changed.
591+
//
592+
// Supported values to annotation ServiceAnnotationLoadBalancerTargetGroupAttributes:
593+
// - preserve_client_ip.enabled=true|false - whether to preserve client IP addresses
594+
// - proxy_protocol_v2.enabled=true|false - whether to enable proxy protocol v2
595+
596+
// Behavior when no annotations provided or removed:
597+
// - Target groups preserves the last set values, and skips any changes.
598+
//
599+
// Parameters:
600+
// - tg: target group object
601+
// - tgAttributes: current target group attributes from AWS resource
602+
// - annotations: service annotations containing desired attribute values
603+
//
604+
// Returns:
605+
// - []elbv2types.TargetGroupAttribute: list of attributes that need to be modified
606+
// - error: validation errors, parsing errors, or AWS restrictions
607+
//
608+
// Documentation generated by Cursor AI
609+
func (c *Cloud) buildTargetGroupAttributes(tg *elbv2types.TargetGroup, tgAttributes []elbv2types.TargetGroupAttribute, annotations map[string]string) ([]elbv2types.TargetGroupAttribute, error) {
610+
errPrefix := "error building target group attributes"
611+
if tg == nil {
612+
return nil, fmt.Errorf("%s: target group is nil", errPrefix)
613+
}
614+
if tgAttributes == nil {
615+
return nil, fmt.Errorf("%s: target group attributes are nil", errPrefix)
616+
}
617+
618+
// existingAttributes are current target group attributes from AWS.
619+
existingAttributes := make(map[string]string, len(tgAttributes))
620+
for _, attr := range tgAttributes {
621+
existingAttributes[aws.ToString(attr.Key)] = aws.ToString(attr.Value)
622+
}
623+
624+
// annotationAttributes are the user-defined attributes set through annotations.
625+
annotationAttributes := getKeyValuePropertiesFromAnnotation(annotations, ServiceAnnotationLoadBalancerTargetGroupAttributes)
626+
627+
// Calculate attribute difference between current and desired state.
628+
var diff []elbv2types.TargetGroupAttribute
629+
for attrKey, attrValue := range annotationAttributes {
630+
// Skip non-supported attributes by controller.
631+
if _, ok := existingAttributes[attrKey]; !ok {
632+
klog.V(2).Infof("Skipping non-supported target group attribute %q", attrKey)
633+
continue
634+
}
635+
636+
// Calculate the target value: annotation override > current value.
637+
if attrValue == existingAttributes[attrKey] {
638+
klog.V(2).Infof("Skipping changes to target group attribute %q, values are the same: %q", attrKey, attrValue)
639+
continue
640+
}
641+
klog.V(2).Infof("Setting from annotation the target group attribute %q value from %q to %q", attrKey, existingAttributes[attrKey], attrValue)
642+
643+
diff = append(diff, elbv2types.TargetGroupAttribute{
644+
Key: aws.String(attrKey),
645+
Value: aws.String(attrValue),
646+
})
647+
}
648+
return diff, nil
649+
}
650+
496651
var invalidELBV2NameRegex = regexp.MustCompile("[^[:alnum:]]")
497652

498653
// buildTargetGroupName will build unique name for targetGroup of service & port.

0 commit comments

Comments
 (0)