Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions controllers/service/service_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const (

func NewServiceReconciler(cloud services.Cloud, k8sClient client.Client, eventRecorder record.EventRecorder,
finalizerManager k8s.FinalizerManager, networkingManager networking.NetworkingManager, networkingSGManager networking.SecurityGroupManager,
networkingSGReconciler networking.SecurityGroupReconciler, subnetsResolver networking.SubnetsResolver,
networkingSGReconciler networking.SecurityGroupReconciler, subnetsResolver networking.SubnetsResolver, eipResolver networking.EIPResolver,
vpcInfoProvider networking.VPCInfoProvider, elbv2TaggingManager elbv2deploy.TaggingManager, controllerConfig config.ControllerConfig,
backendSGProvider networking.BackendSGProvider, sgResolver networking.SecurityGroupResolver, logger logr.Logger, metricsCollector lbcmetrics.MetricCollector, reconcileCounters *metricsutil.ReconcileCounters,
targetGroupCollector awsmetrics.TargetGroupCollector) *serviceReconciler {
Expand All @@ -53,7 +53,7 @@ func NewServiceReconciler(cloud services.Cloud, k8sClient client.Client, eventRe
trackingProvider := tracking.NewDefaultProvider(serviceTagPrefix, controllerConfig.ClusterName)
serviceUtils := service.NewServiceUtils(annotationParser, shared_constants.ServiceFinalizer, controllerConfig.ServiceConfig.LoadBalancerClass, controllerConfig.FeatureGates)
enhancedBackendBuilder := service.NewDefaultEnhancedBackendBuilder(k8sClient, annotationParser, logger)
modelBuilder := service.NewDefaultModelBuilder(annotationParser, subnetsResolver, vpcInfoProvider, cloud.VpcID(), trackingProvider,
modelBuilder := service.NewDefaultModelBuilder(annotationParser, subnetsResolver, eipResolver, vpcInfoProvider, cloud.VpcID(), trackingProvider,
elbv2TaggingManager, cloud.EC2(), controllerConfig.FeatureGates, controllerConfig.ClusterName, controllerConfig.DefaultTags, controllerConfig.ExternalManagedTags,
controllerConfig.DefaultSSLPolicy, controllerConfig.DefaultTargetType, controllerConfig.DefaultLoadBalancerScheme, controllerConfig.FeatureGates.Enabled(config.EnableIPTargetType), serviceUtils,
backendSGProvider, sgResolver, controllerConfig.EnableBackendSecurityGroup, controllerConfig.EnableManageBackendSecurityGroupRules, controllerConfig.DisableRestrictedSGRules, logger, metricsCollector, controllerConfig.FeatureGates.Enabled(config.EnableTCPUDPListenerType), enhancedBackendBuilder)
Expand Down
15 changes: 15 additions & 0 deletions docs/guide/service/annotations.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
| [service.beta.kubernetes.io/aws-load-balancer-healthcheck-interval](#healthcheck-interval) | integer | 10 | |
| [service.beta.kubernetes.io/aws-load-balancer-healthcheck-success-codes](#healthcheck-success-codes) | string | 200-399 | |
| [service.beta.kubernetes.io/aws-load-balancer-eip-allocations](#eip-allocations) | stringList | | internet-facing lb only. Length must match the number of subnets |
| [service.beta.kubernetes.io/aws-load-balancer-eip-allocations-discovery-tags](#eip-allocations-discovery-tags) | stringMap | | internet-facing lb only. Mutually exclusive with [eip-allocations](#eip-allocations) |
| [service.beta.kubernetes.io/aws-load-balancer-private-ipv4-addresses](#private-ipv4-addresses) | stringList | | internal lb only. Length must match the number of subnets |
| [service.beta.kubernetes.io/aws-load-balancer-ipv6-addresses](#ipv6-addresses) | stringList | | dualstack lb only. Length must match the number of subnets |
| [service.beta.kubernetes.io/aws-load-balancer-target-group-attributes](#target-group-attributes) | stringMap | | |
Expand Down Expand Up @@ -178,6 +179,20 @@ on the load balancer.
service.beta.kubernetes.io/aws-load-balancer-eip-allocations: eipalloc-xyz, eipalloc-zzz
```

- <a name="eip-allocations-discovery-tags">`service.beta.kubernetes.io/aws-load-balancer-eip-allocations-discovery-tags`</a> discovers [elastic IP address](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/elastic-ip-addresses-eip.html) allocation IDs by EC2 resource tags instead of listing them explicitly.

!!!note
- This configuration is optional, and you can use it to assign static IP addresses to your NLB without hardcoding allocation IDs
- NLB must be internet-facing
- Mutually exclusive with [aws-load-balancer-eip-allocations](#eip-allocations)
- Tag one VPC-scoped EIP per load balancer subnet Availability Zone; the controller matches EIPs to subnets by AZ
- Tags must uniquely identify the intended EIP set for the service

!!!example
```
service.beta.kubernetes.io/aws-load-balancer-eip-allocations-discovery-tags: pod=pod998,service=zorg,visibility=external
```


- <a name="private-ipv4-addresses">`service.beta.kubernetes.io/aws-load-balancer-private-ipv4-addresses`</a> specifies a list of private IPv4 addresses for an internal NLB.

Expand Down
3 changes: 2 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ func main() {
controllerCFG.FeatureGates.Enabled(config.ALBSingleSubnet),
controllerCFG.FeatureGates.Enabled(config.SubnetDiscoveryByReachability),
ctrl.Log.WithName("subnets-resolver"))
eipResolver := networking.NewDefaultEIPResolver(cloud.EC2())
multiClusterManager := targetgroupbinding.NewMultiClusterManager(mgr.GetClient(), mgr.GetAPIReader(), ctrl.Log)

nodeInfoProvider := networking.NewDefaultNodeInfoProvider(cloud.EC2(), ctrl.Log)
Expand All @@ -221,7 +222,7 @@ func main() {
controllerCFG, backendSGProvider, sgResolver, secretsManager, ctrl.Log.WithName("controllers").WithName("ingress"), lbcMetricsCollector, reconcileCounters,
targetGroupCollector, tgArnMapper)
svcReconciler := service.NewServiceReconciler(cloud, mgr.GetClient(), mgr.GetEventRecorderFor("service"),
finalizerManager, networkingManager, sgManager, sgReconciler, subnetResolver, vpcInfoProvider, elbv2TaggingManager,
finalizerManager, networkingManager, sgManager, sgReconciler, subnetResolver, eipResolver, vpcInfoProvider, elbv2TaggingManager,
controllerCFG, backendSGProvider, sgResolver, ctrl.Log.WithName("controllers").WithName("service"), lbcMetricsCollector, reconcileCounters,
targetGroupCollector)

Expand Down
1 change: 1 addition & 0 deletions pkg/annotations/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ const (
SvcLBSuffixTargetGroupAttributes = "aws-load-balancer-target-group-attributes"
SvcLBSuffixSubnets = "aws-load-balancer-subnets"
SvcLBSuffixEIPAllocations = "aws-load-balancer-eip-allocations"
SvcLBSuffixEIPAllocationsDiscoveryTags = "aws-load-balancer-eip-allocations-discovery-tags"
SvcLBSuffixPrivateIpv4Addresses = "aws-load-balancer-private-ipv4-addresses"
SvcLBSuffixIpv6Addresses = "aws-load-balancer-ipv6-addresses"
SvcLBSuffixALPNPolicy = "aws-load-balancer-alpn-policy"
Expand Down
15 changes: 15 additions & 0 deletions pkg/aws/services/ec2.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ type EC2 interface {
// DescribeRouteTablesAsList wraps the DescribeRouteTablesWithContext API, which aggregates paged results into list.
DescribeRouteTablesAsList(ctx context.Context, input *ec2.DescribeRouteTablesInput) ([]types.RouteTable, error)

// DescribeAddressesAsList wraps the DescribeAddresses API, which aggregates paged results into list.
DescribeAddressesAsList(ctx context.Context, input *ec2.DescribeAddressesInput) ([]types.Address, error)

CreateTagsWithContext(ctx context.Context, input *ec2.CreateTagsInput) (*ec2.CreateTagsOutput, error)
DeleteTagsWithContext(ctx context.Context, input *ec2.DeleteTagsInput) (*ec2.DeleteTagsOutput, error)
CreateSecurityGroupWithContext(ctx context.Context, input *ec2.CreateSecurityGroupInput) (*ec2.CreateSecurityGroupOutput, error)
Expand Down Expand Up @@ -162,6 +165,18 @@ func (c *ec2Client) DescribeRouteTablesAsList(ctx context.Context, input *ec2.De
return result, nil
}

func (c *ec2Client) DescribeAddressesAsList(ctx context.Context, input *ec2.DescribeAddressesInput) ([]types.Address, error) {
client, err := c.awsClientsProvider.GetEC2Client(ctx, "DescribeAddresses")
if err != nil {
return nil, err
}
output, err := client.DescribeAddresses(ctx, input)
if err != nil {
return nil, err
}
return output.Addresses, nil
}

func (c *ec2Client) CreateTagsWithContext(ctx context.Context, input *ec2.CreateTagsInput) (*ec2.CreateTagsOutput, error) {
client, err := c.awsClientsProvider.GetEC2Client(ctx, "CreateTags")
if err != nil {
Expand Down
15 changes: 15 additions & 0 deletions pkg/aws/services/ec2_mocks.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

107 changes: 107 additions & 0 deletions pkg/networking/eip_resolver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package networking

import (
"context"
"fmt"

awssdk "github.com/aws/aws-sdk-go-v2/aws"
ec2sdk "github.com/aws/aws-sdk-go-v2/service/ec2"
ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
"k8s.io/apimachinery/pkg/util/sets"
"sigs.k8s.io/aws-load-balancer-controller/v3/pkg/aws/services"
)

const ec2FilterNameDomain = "domain"

//go:generate mockgen -destination=eip_resolver_mocks.go -package=networking sigs.k8s.io/aws-load-balancer-controller/v3/pkg/networking EIPResolver

// EIPResolver resolves Elastic IP allocation IDs for NLB subnet mappings.
type EIPResolver interface {
ResolveForSubnets(ctx context.Context, tagFilters map[string]string, subnets []ec2types.Subnet) ([]string, error)
}

// NewDefaultEIPResolver constructs a new defaultEIPResolver.
func NewDefaultEIPResolver(ec2Client services.EC2) *defaultEIPResolver {
return &defaultEIPResolver{
ec2Client: ec2Client,
}
}

type defaultEIPResolver struct {
ec2Client services.EC2
}

var _ EIPResolver = &defaultEIPResolver{}

func (r *defaultEIPResolver) ResolveForSubnets(ctx context.Context, tagFilters map[string]string, subnets []ec2types.Subnet) ([]string, error) {
if len(tagFilters) == 0 {
return nil, fmt.Errorf("EIP discovery tags must not be empty")
}
if len(subnets) == 0 {
return nil, fmt.Errorf("subnets must not be empty for EIP discovery")
}

addresses, err := r.listAddressesByTagFilters(ctx, tagFilters)
if err != nil {
return nil, fmt.Errorf("failed to list EIPs by discovery tags: %w", err)
}

addressesByAZ := make(map[string][]ec2types.Address)
for _, addr := range addresses {
az := awssdk.ToString(addr.NetworkBorderGroup)
if az == "" {
return nil, fmt.Errorf("discovered EIP %s has empty network border group", awssdk.ToString(addr.AllocationId))
}
addressesByAZ[az] = append(addressesByAZ[az], addr)
}

allocationIDs := make([]string, 0, len(subnets))
for _, subnet := range subnets {
subnetAZ := awssdk.ToString(subnet.AvailabilityZone)
addrsInAZ := addressesByAZ[subnetAZ]
if len(addrsInAZ) == 0 {
return nil, fmt.Errorf("no EIP found for subnet %s in availability zone %s matching discovery tags", awssdk.ToString(subnet.SubnetId), subnetAZ)
}
if len(addrsInAZ) > 1 {
return nil, fmt.Errorf("multiple EIPs found for availability zone %s matching discovery tags", subnetAZ)
}
addr := addrsInAZ[0]
if err := validateDiscoveredEIP(addr); err != nil {
return nil, err
}
allocationIDs = append(allocationIDs, awssdk.ToString(addr.AllocationId))
}

return allocationIDs, nil
}

func validateDiscoveredEIP(addr ec2types.Address) error {
if addr.AllocationId == nil || awssdk.ToString(addr.AllocationId) == "" {
return fmt.Errorf("discovered EIP has empty allocation ID")
}
if addr.AssociationId != nil && awssdk.ToString(addr.AssociationId) != "" {
owner := awssdk.ToString(addr.NetworkInterfaceOwnerId)
if owner != "" && owner != "amazon-elb" {
return fmt.Errorf("EIP %s is associated with another resource", awssdk.ToString(addr.AllocationId))
}
}
return nil
}

func (r *defaultEIPResolver) listAddressesByTagFilters(ctx context.Context, tagFilters map[string]string) ([]ec2types.Address, error) {
req := &ec2sdk.DescribeAddressesInput{
Filters: []ec2types.Filter{
{
Name: awssdk.String(ec2FilterNameDomain),
Values: []string{"vpc"},
},
},
}
for _, key := range sets.StringKeySet(tagFilters).List() {
req.Filters = append(req.Filters, ec2types.Filter{
Name: awssdk.String("tag:" + key),
Values: []string{tagFilters[key]},
})
}
return r.ec2Client.DescribeAddressesAsList(ctx, req)
}
51 changes: 51 additions & 0 deletions pkg/networking/eip_resolver_mocks.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading