Skip to content

Commit 74b857c

Browse files
authored
Merge pull request kubernetes#90943 from foobarfran/feature-aws-lb-node-targeting
feat: use annotation to filter AWS LB target nodes
2 parents fb9e194 + 70820bb commit 74b857c

File tree

4 files changed

+111
-15
lines changed

4 files changed

+111
-15
lines changed

staging/src/k8s.io/legacy-cloud-providers/aws/aws.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,12 @@ const ServiceAnnotationLoadBalancerHCInterval = "service.beta.kubernetes.io/aws-
210210
// static IP addresses for the NLB. Only supported on elbv2 (NLB)
211211
const ServiceAnnotationLoadBalancerEIPAllocations = "service.beta.kubernetes.io/aws-load-balancer-eip-allocations"
212212

213+
// ServiceAnnotationLoadBalancerTargetNodeLabels is the annotation used on the service
214+
// to specify a comma-separated list of key-value pairs which will be used to select
215+
// the target nodes for the load balancer
216+
// For example: "Key1=Val1,Key2=Val2,KeyNoVal1=,KeyNoVal2"
217+
const ServiceAnnotationLoadBalancerTargetNodeLabels = "service.beta.kubernetes.io/aws-load-balancer-target-node-labels"
218+
213219
// Event key when a volume is stuck on attaching state when being attached to a volume
214220
const volumeAttachmentStuck = "VolumeAttachmentStuck"
215221

@@ -3568,7 +3574,7 @@ func (c *Cloud) buildELBSecurityGroupList(serviceName types.NamespacedName, load
35683574
// Create a security group for the load balancer
35693575
sgName := "k8s-elb-" + loadBalancerName
35703576
sgDescription := fmt.Sprintf("Security group for Kubernetes ELB %s (%v)", loadBalancerName, serviceName)
3571-
securityGroupID, err = c.ensureSecurityGroup(sgName, sgDescription, getLoadBalancerAdditionalTags(annotations))
3577+
securityGroupID, err = c.ensureSecurityGroup(sgName, sgDescription, getKeyValuePropertiesFromAnnotation(annotations, ServiceAnnotationLoadBalancerAdditionalTags))
35723578
if err != nil {
35733579
klog.Errorf("Error creating load balancer security group: %q", err)
35743580
return nil, setupSg, err
@@ -3686,7 +3692,7 @@ func (c *Cloud) EnsureLoadBalancer(ctx context.Context, clusterName string, apiS
36863692
return nil, fmt.Errorf("LoadBalancerIP cannot be specified for AWS ELB")
36873693
}
36883694

3689-
instances, err := c.findInstancesForELB(nodes)
3695+
instances, err := c.findInstancesForELB(nodes, annotations)
36903696
if err != nil {
36913697
return nil, err
36923698
}
@@ -4470,7 +4476,7 @@ func (c *Cloud) EnsureLoadBalancerDeleted(ctx context.Context, clusterName strin
44704476

44714477
// UpdateLoadBalancer implements LoadBalancer.UpdateLoadBalancer
44724478
func (c *Cloud) UpdateLoadBalancer(ctx context.Context, clusterName string, service *v1.Service, nodes []*v1.Node) error {
4473-
instances, err := c.findInstancesForELB(nodes)
4479+
instances, err := c.findInstancesForELB(nodes, service.Annotations)
44744480
if err != nil {
44754481
return err
44764482
}

staging/src/k8s.io/legacy-cloud-providers/aws/aws_loadbalancer.go

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -84,12 +84,11 @@ type nlbPortMapping struct {
8484
SSLPolicy string
8585
}
8686

87-
// getLoadBalancerAdditionalTags converts the comma separated list of key-value
88-
// pairs in the ServiceAnnotationLoadBalancerAdditionalTags annotation and returns
89-
// it as a map.
90-
func getLoadBalancerAdditionalTags(annotations map[string]string) map[string]string {
87+
// getKeyValuePropertiesFromAnnotation converts the comma separated list of key-value
88+
// pairs from the specified annotation and returns it as a map.
89+
func getKeyValuePropertiesFromAnnotation(annotations map[string]string, annotation string) map[string]string {
9190
additionalTags := make(map[string]string)
92-
if additionalTagsList, ok := annotations[ServiceAnnotationLoadBalancerAdditionalTags]; ok {
91+
if additionalTagsList, ok := annotations[annotation]; ok {
9392
additionalTagsList = strings.TrimSpace(additionalTagsList)
9493

9594
// Break up list of "Key1=Val,Key2=Val2"
@@ -123,7 +122,7 @@ func (c *Cloud) ensureLoadBalancerv2(namespacedName types.NamespacedName, loadBa
123122
dirty := false
124123

125124
// Get additional tags set by the user
126-
tags := getLoadBalancerAdditionalTags(annotations)
125+
tags := getKeyValuePropertiesFromAnnotation(annotations, ServiceAnnotationLoadBalancerAdditionalTags)
127126
// Add default tags
128127
tags[TagNameKubernetesService] = namespacedName.String()
129128
tags = c.tagging.buildTags(ResourceLifecycleOwned, tags)
@@ -939,7 +938,7 @@ func (c *Cloud) ensureLoadBalancer(namespacedName types.NamespacedName, loadBala
939938
}
940939

941940
// Get additional tags set by the user
942-
tags := getLoadBalancerAdditionalTags(annotations)
941+
tags := getKeyValuePropertiesFromAnnotation(annotations, ServiceAnnotationLoadBalancerAdditionalTags)
943942

944943
// Add default tags
945944
tags[TagNameKubernetesService] = namespacedName.String()
@@ -1128,7 +1127,7 @@ func (c *Cloud) ensureLoadBalancer(namespacedName types.NamespacedName, loadBala
11281127
{
11291128
// Add additional tags
11301129
klog.V(2).Infof("Creating additional load balancer tags for %s", loadBalancerName)
1131-
tags := getLoadBalancerAdditionalTags(annotations)
1130+
tags := getKeyValuePropertiesFromAnnotation(annotations, ServiceAnnotationLoadBalancerAdditionalTags)
11321131
if len(tags) > 0 {
11331132
err := c.addLoadBalancerTags(loadBalancerName, tags)
11341133
if err != nil {
@@ -1521,9 +1520,12 @@ func proxyProtocolEnabled(backend *elb.BackendServerDescription) bool {
15211520
// findInstancesForELB gets the EC2 instances corresponding to the Nodes, for setting up an ELB
15221521
// We ignore Nodes (with a log message) where the instanceid cannot be determined from the provider,
15231522
// and we ignore instances which are not found
1524-
func (c *Cloud) findInstancesForELB(nodes []*v1.Node) (map[InstanceID]*ec2.Instance, error) {
1523+
func (c *Cloud) findInstancesForELB(nodes []*v1.Node, annotations map[string]string) (map[InstanceID]*ec2.Instance, error) {
1524+
1525+
targetNodes := filterTargetNodes(nodes, annotations)
1526+
15251527
// Map to instance ids ignoring Nodes where we cannot find the id (but logging)
1526-
instanceIDs := mapToAWSInstanceIDsTolerant(nodes)
1528+
instanceIDs := mapToAWSInstanceIDsTolerant(targetNodes)
15271529

15281530
cacheCriteria := cacheCriteria{
15291531
// MaxAge not required, because we only care about security groups, which should not change
@@ -1539,3 +1541,35 @@ func (c *Cloud) findInstancesForELB(nodes []*v1.Node) (map[InstanceID]*ec2.Insta
15391541

15401542
return instances, nil
15411543
}
1544+
1545+
// filterTargetNodes uses node labels to filter the nodes that should be targeted by the ELB,
1546+
// checking if all the labels provided in an annotation are present in the nodes
1547+
func filterTargetNodes(nodes []*v1.Node, annotations map[string]string) []*v1.Node {
1548+
1549+
targetNodeLabels := getKeyValuePropertiesFromAnnotation(annotations, ServiceAnnotationLoadBalancerTargetNodeLabels)
1550+
1551+
if len(targetNodeLabels) == 0 {
1552+
return nodes
1553+
}
1554+
1555+
targetNodes := make([]*v1.Node, 0, len(nodes))
1556+
1557+
for _, node := range nodes {
1558+
if node.Labels != nil && len(node.Labels) > 0 {
1559+
allFiltersMatch := true
1560+
1561+
for targetLabelKey, targetLabelValue := range targetNodeLabels {
1562+
if nodeLabelValue, ok := node.Labels[targetLabelKey]; !ok || (nodeLabelValue != targetLabelValue && targetLabelValue != "") {
1563+
allFiltersMatch = false
1564+
break
1565+
}
1566+
}
1567+
1568+
if allFiltersMatch {
1569+
targetNodes = append(targetNodes, node)
1570+
}
1571+
}
1572+
}
1573+
1574+
return targetNodes
1575+
}

staging/src/k8s.io/legacy-cloud-providers/aws/aws_loadbalancer_test.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ import (
2525
"github.com/aws/aws-sdk-go/aws"
2626
"github.com/aws/aws-sdk-go/service/elb"
2727
"github.com/stretchr/testify/assert"
28+
29+
"k8s.io/api/core/v1"
2830
)
2931

3032
func TestElbProtocolsAreEqual(t *testing.T) {
@@ -420,3 +422,57 @@ func TestBuildTargetGroupName(t *testing.T) {
420422
})
421423
}
422424
}
425+
426+
func TestFilterTargetNodes(t *testing.T) {
427+
tests := []struct {
428+
name string
429+
nodeLabels, annotations map[string]string
430+
nodeTargeted bool
431+
}{
432+
{
433+
name: "when no filter is provided, node should be targeted",
434+
nodeLabels: map[string]string{"k1": "v1"},
435+
nodeTargeted: true,
436+
},
437+
{
438+
name: "when all key-value filters match, node should be targeted",
439+
nodeLabels: map[string]string{"k1": "v1", "k2": "v2"},
440+
annotations: map[string]string{ServiceAnnotationLoadBalancerTargetNodeLabels: "k1=v1,k2=v2"},
441+
nodeTargeted: true,
442+
},
443+
{
444+
name: "when all just-key filter match, node should be targeted",
445+
nodeLabels: map[string]string{"k1": "v1", "k2": "v2"},
446+
annotations: map[string]string{ServiceAnnotationLoadBalancerTargetNodeLabels: "k1,k2"},
447+
nodeTargeted: true,
448+
},
449+
{
450+
name: "when some filters do not match, node should not be targeted",
451+
nodeLabels: map[string]string{"k1": "v1"},
452+
annotations: map[string]string{ServiceAnnotationLoadBalancerTargetNodeLabels: "k1=v1,k2"},
453+
nodeTargeted: false,
454+
},
455+
{
456+
name: "when no filter matches, node should not be targeted",
457+
nodeLabels: map[string]string{"k1": "v1", "k2": "v2"},
458+
annotations: map[string]string{ServiceAnnotationLoadBalancerTargetNodeLabels: "k3=v3"},
459+
nodeTargeted: false,
460+
},
461+
}
462+
463+
for _, test := range tests {
464+
t.Run(test.name, func(t *testing.T) {
465+
node := &v1.Node{}
466+
node.Labels = test.nodeLabels
467+
468+
nodes := []*v1.Node{node}
469+
targetNodes := filterTargetNodes(nodes, test.annotations)
470+
471+
if test.nodeTargeted {
472+
assert.Equal(t, nodes, targetNodes)
473+
} else {
474+
assert.Empty(t, targetNodes)
475+
}
476+
})
477+
}
478+
}

staging/src/k8s.io/legacy-cloud-providers/aws/aws_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1530,7 +1530,7 @@ func TestProxyProtocolEnabled(t *testing.T) {
15301530
assert.False(t, result, "did not expect to find %s in %s", ProxyProtocolPolicyName, policies)
15311531
}
15321532

1533-
func TestGetLoadBalancerAdditionalTags(t *testing.T) {
1533+
func TestGetKeyValuePropertiesFromAnnotation(t *testing.T) {
15341534
tagTests := []struct {
15351535
Annotations map[string]string
15361536
Tags map[string]string
@@ -1581,7 +1581,7 @@ func TestGetLoadBalancerAdditionalTags(t *testing.T) {
15811581
}
15821582

15831583
for _, tagTest := range tagTests {
1584-
result := getLoadBalancerAdditionalTags(tagTest.Annotations)
1584+
result := getKeyValuePropertiesFromAnnotation(tagTest.Annotations, ServiceAnnotationLoadBalancerAdditionalTags)
15851585
for k, v := range result {
15861586
if len(result) != len(tagTest.Tags) {
15871587
t.Errorf("incorrect expected length: %v != %v", result, tagTest.Tags)

0 commit comments

Comments
 (0)