Skip to content

Commit e1c8af9

Browse files
shoekstraM00nF1sh
authored andcommitted
Allow TargetGroup endpoints outside the ELB VPC
To support the scenario where the ELB is deployed in a separate VPC to the EKS cluster, we need to conditionally set the `AvailabilityZone` field to `all` when the target is outside of the ELB VPC. This commit addresses this by checking if the pod IP is found in the ELB VPC's CIDR or a secondary CIDR; to improve performance and reduce calls to the AWS API, VPC info is cached for a configurable amount of time, defaulting to 5 minutes. Signed-off-by: Stephen Hoekstra <[email protected]>
1 parent db5fc12 commit e1c8af9

File tree

14 files changed

+411
-23
lines changed

14 files changed

+411
-23
lines changed

docs/deploy/configurations.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,12 @@ Currently, you can set only 1 namespace to watch in this flag. See [this Kuberne
6767

6868
|Flag | Type | Default | Description |
6969
|---------------------------------------|---------------------------------|-----------------|-------------|
70+
|aws-api-endpoints | AWS API Endpoints Config | | AWS API endpoints mapping, format: serviceID1=URL1,serviceID2=URL2 |
7071
|aws-api-throttle | AWS Throttle Config | [default value](#default-throttle-config ) | throttle settings for AWS APIs, format: serviceID1:operationRegex1=rate:burst,serviceID2:operationRegex2=rate:burst |
7172
|aws-max-retries | int | 10 | Maximum retries for AWS APIs |
7273
|aws-region | string | [instance metadata](#instance-metadata) | AWS Region for the kubernetes cluster |
73-
|aws-vpc-id | string | [instance metadata](#instance-metadata) | AWS VPC ID for the Kubernetes cluster |
74-
|aws-api-endpoints | AWS API Endpoints Config | | AWS API endpoints mapping, format: serviceID1=URL1,serviceID2=URL2 |
74+
|aws-vpc-cache-duration | string | 5 | Length of time in minutes to cache VPC info |
75+
|aws-vpc-id | string | [instance metadata](#instance-metadata) | ID of the AWS VPC where Load Balancer resources will be created |
7576
|cluster-name | string | | Kubernetes cluster name|
7677
|default-tags | stringMap | | AWS Tags that will be applied to all AWS resources managed by this controller. Specified Tags takes highest priority |
7778
|default-ssl-policy | string | ELBSecurityPolicy-2016-08 | Default SSL Policy that will be applied to all Ingresses or Services that do not have the SSL Policy annotation |

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ require (
1010
github.com/google/go-cmp v0.5.6
1111
github.com/onsi/ginkgo v1.16.4
1212
github.com/onsi/gomega v1.16.0
13+
github.com/patrickmn/go-cache v2.1.0+incompatible
1314
github.com/pkg/errors v0.9.1
1415
github.com/prometheus/client_golang v1.11.0
1516
github.com/spf13/pflag v1.0.5

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -643,6 +643,10 @@ github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnh
643643
github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
644644
github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
645645
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
646+
github.com/patrickmn/go-cache v1.0.0 h1:3gD5McaYs9CxjyK5AXGcq8gdeCARtd/9gJDUvVeaZ0Y=
647+
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
648+
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
649+
github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g=
646650
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
647651
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
648652
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=

main.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,10 +103,11 @@ func main() {
103103
sgManager := networking.NewDefaultSecurityGroupManager(cloud.EC2(), ctrl.Log)
104104
sgReconciler := networking.NewDefaultSecurityGroupReconciler(sgManager, ctrl.Log)
105105
azInfoProvider := networking.NewDefaultAZInfoProvider(cloud.EC2(), ctrl.Log.WithName("az-info-provider"))
106+
vpcInfoProvider := networking.NewDefaultVPCInfoProvider(cloud.VpcCacheDuration(), cloud.EC2(), ctrl.Log.WithName("vpc-info-provider"))
106107
subnetResolver := networking.NewDefaultSubnetsResolver(azInfoProvider, cloud.EC2(), cloud.VpcID(), controllerCFG.ClusterName, ctrl.Log.WithName("subnets-resolver"))
107108
vpcResolver := networking.NewDefaultVPCResolver(cloud.EC2(), cloud.VpcID(), ctrl.Log.WithName("vpc-resolver"))
108109
tgbResManager := targetgroupbinding.NewDefaultResourceManager(mgr.GetClient(), cloud.ELBV2(), cloud.EC2(),
109-
podInfoRepo, sgManager, sgReconciler, cloud.VpcID(), controllerCFG.ClusterName, mgr.GetEventRecorderFor("targetGroupBinding"), ctrl.Log, controllerCFG.EnableEndpointSlices, controllerCFG.DisableRestrictedSGRules)
110+
podInfoRepo, sgManager, sgReconciler, cloud.VpcID(), controllerCFG.ClusterName, mgr.GetEventRecorderFor("targetGroupBinding"), ctrl.Log, controllerCFG.EnableEndpointSlices, controllerCFG.DisableRestrictedSGRules, vpcInfoProvider)
110111
backendSGProvider := networking.NewBackendSGProvider(controllerCFG.ClusterName, controllerCFG.BackendSecurityGroup,
111112
cloud.VpcID(), cloud.EC2(), mgr.GetClient(), ctrl.Log.WithName("backend-sg-provider"))
112113
ingGroupReconciler := ingress.NewGroupReconciler(cloud, mgr.GetClient(), mgr.GetEventRecorderFor("ingress"),

pkg/aws/cloud.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,11 @@ type Cloud interface {
3838
// Region for the kubernetes cluster
3939
Region() string
4040

41-
// VPC ID for the the kubernetes cluster
41+
// ID of VPC to create load balancers in
4242
VpcID() string
43+
44+
// VPC cache duration in minutes
45+
VpcCacheDuration() int
4346
}
4447

4548
// NewCloud constructs new Cloud implementation.
@@ -149,3 +152,7 @@ func (c *defaultCloud) Region() string {
149152
func (c *defaultCloud) VpcID() string {
150153
return c.cfg.VpcID
151154
}
155+
156+
func (c *defaultCloud) VpcCacheDuration() int {
157+
return c.cfg.VpcCacheDuration
158+
}

pkg/aws/cloud_config.go

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,16 @@ import (
66
)
77

88
const (
9-
flagAWSRegion = "aws-region"
10-
flagAWSAPIThrottle = "aws-api-throttle"
11-
flagAWSVpcID = "aws-vpc-id"
12-
flagAWSMaxRetries = "aws-max-retries"
13-
flagAWSAPIEndpoints = "aws-api-endpoints"
14-
defaultVpcID = ""
15-
defaultRegion = ""
16-
defaultAPIMaxRetries = 10
9+
flagAWSRegion = "aws-region"
10+
flagAWSAPIEndpoints = "aws-api-endpoints"
11+
flagAWSAPIThrottle = "aws-api-throttle"
12+
flagAWSVpcID = "aws-vpc-id"
13+
flagAWSVpcCacheDuration = "aws-vpc-cache-duration"
14+
flagAWSMaxRetries = "aws-max-retries"
15+
defaultVpcID = ""
16+
defaultRegion = ""
17+
defaultAPIMaxRetries = 10
18+
defaultVpcCacheDuration = 5
1719
)
1820

1921
type CloudConfig struct {
@@ -23,9 +25,12 @@ type CloudConfig struct {
2325
// Throttle settings for AWS APIs
2426
ThrottleConfig *throttle.ServiceOperationsThrottleConfig
2527

26-
// VPC ID of the Kubernetes cluster
28+
// ID of VPC to create load balancers in
2729
VpcID string
2830

31+
// VPC cache duration in minutes
32+
VpcCacheDuration int
33+
2934
// Max retries configuration for AWS APIs
3035
MaxRetries int
3136

@@ -36,7 +41,8 @@ type CloudConfig struct {
3641
func (cfg *CloudConfig) BindFlags(fs *pflag.FlagSet) {
3742
fs.StringVar(&cfg.Region, flagAWSRegion, defaultRegion, "AWS Region for the kubernetes cluster")
3843
fs.Var(cfg.ThrottleConfig, flagAWSAPIThrottle, "throttle settings for AWS APIs, format: serviceID1:operationRegex1=rate:burst,serviceID2:operationRegex2=rate:burst")
39-
fs.StringVar(&cfg.VpcID, flagAWSVpcID, defaultVpcID, "AWS VPC ID for the Kubernetes cluster")
44+
fs.StringVar(&cfg.VpcID, flagAWSVpcID, defaultVpcID, "AWS ID of VPC to create load balancers in")
45+
fs.IntVar(&cfg.VpcCacheDuration, flagAWSVpcCacheDuration, defaultVpcCacheDuration, "VPC cache duration in minutes")
4046
fs.IntVar(&cfg.MaxRetries, flagAWSMaxRetries, defaultAPIMaxRetries, "Maximum retries for AWS APIs")
4147
fs.StringToStringVar(&cfg.AWSEndpoints, flagAWSAPIEndpoints, nil, "Custom AWS endpoint configuration, format: serviceID1=URL1,serviceID2=URL2")
4248
}

pkg/networking/vpc_info_provider.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package networking
2+
3+
import (
4+
"context"
5+
"sync"
6+
"time"
7+
8+
awssdk "github.com/aws/aws-sdk-go/aws"
9+
ec2sdk "github.com/aws/aws-sdk-go/service/ec2"
10+
"github.com/go-logr/logr"
11+
"github.com/patrickmn/go-cache"
12+
"sigs.k8s.io/aws-load-balancer-controller/pkg/aws/services"
13+
)
14+
15+
// VPCInfoProvider is responsible for providing VPC info.
16+
type VPCInfoProvider interface {
17+
FetchVPCInfo(ctx context.Context, vpcID string) (*ec2sdk.Vpc, error)
18+
}
19+
20+
// NewDefaultVPCInfoProvider constructs new defaultVPCInfoProvider.
21+
func NewDefaultVPCInfoProvider(cacheDuration int, ec2Client services.EC2, logger logr.Logger) *defaultVPCInfoProvider {
22+
return &defaultVPCInfoProvider{
23+
ec2Client: ec2Client,
24+
vpcInfoCache: cache.New(time.Duration(cacheDuration)*time.Minute, 10*time.Minute),
25+
vpcInfoCacheMutex: sync.RWMutex{},
26+
logger: logger,
27+
}
28+
}
29+
30+
var _ VPCInfoProvider = &defaultVPCInfoProvider{}
31+
32+
// default implementation for VPCInfoProvider.
33+
type defaultVPCInfoProvider struct {
34+
ec2Client services.EC2
35+
vpcInfoCache *cache.Cache
36+
vpcInfoCacheMutex sync.RWMutex
37+
38+
logger logr.Logger
39+
}
40+
41+
func (p *defaultVPCInfoProvider) FetchVPCInfo(ctx context.Context, vpcID string) (*ec2sdk.Vpc, error) {
42+
if vpcInfo := p.fetchVPCInfoFromCache(); vpcInfo != nil {
43+
return vpcInfo, nil
44+
}
45+
46+
// Fetch VPC info from the AWS API and cache response before returning.
47+
vpcInfo, err := p.fetchVPCInfoFromAWS(ctx, vpcID)
48+
if err != nil {
49+
return nil, nil
50+
}
51+
p.saveVPCInfoToCache(vpcInfo)
52+
53+
return vpcInfo, nil
54+
}
55+
56+
func (p *defaultVPCInfoProvider) fetchVPCInfoFromCache() *ec2sdk.Vpc {
57+
p.vpcInfoCacheMutex.RLock()
58+
defer p.vpcInfoCacheMutex.RUnlock()
59+
60+
vpcInfo, found := p.vpcInfoCache.Get("vpcInfo")
61+
if !found {
62+
return nil
63+
}
64+
65+
return vpcInfo.(*ec2sdk.Vpc)
66+
}
67+
68+
func (p *defaultVPCInfoProvider) saveVPCInfoToCache(vpcInfo *ec2sdk.Vpc) {
69+
p.vpcInfoCacheMutex.Lock()
70+
defer p.vpcInfoCacheMutex.Unlock()
71+
72+
p.vpcInfoCache.SetDefault("vpcInfo", vpcInfo)
73+
}
74+
75+
// fetchVPCInfoFromAWS will fetch VPC info from the AWS API.
76+
func (p *defaultVPCInfoProvider) fetchVPCInfoFromAWS(ctx context.Context, vpcID string) (*ec2sdk.Vpc, error) {
77+
req := &ec2sdk.DescribeVpcsInput{
78+
VpcIds: []*string{awssdk.String(vpcID)},
79+
}
80+
resp, err := p.ec2Client.DescribeVpcsWithContext(ctx, req)
81+
if err != nil {
82+
return nil, err
83+
}
84+
85+
return resp.Vpcs[0], nil
86+
}

pkg/networking/vpc_info_provider_mocks.go

Lines changed: 50 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package networking
2+
3+
import (
4+
"context"
5+
"reflect"
6+
"testing"
7+
8+
awssdk "github.com/aws/aws-sdk-go/aws"
9+
ec2sdk "github.com/aws/aws-sdk-go/service/ec2"
10+
gomock "github.com/golang/mock/gomock"
11+
"sigs.k8s.io/aws-load-balancer-controller/pkg/aws/services"
12+
"sigs.k8s.io/controller-runtime/pkg/log"
13+
)
14+
15+
func Test_defaultVPCInfoProvider_FetchVPCInfo(t *testing.T) {
16+
type describeVpcCall struct {
17+
input *ec2sdk.DescribeVpcsInput
18+
output *ec2sdk.DescribeVpcsOutput
19+
err error
20+
}
21+
22+
type fields struct {
23+
describeVpcCalls []describeVpcCall
24+
}
25+
tests := []struct {
26+
name string
27+
fields fields
28+
want *ec2sdk.Vpc
29+
wantErr bool
30+
}{
31+
{
32+
name: "from AWS",
33+
fields: fields{
34+
describeVpcCalls: []describeVpcCall{
35+
{
36+
input: &ec2sdk.DescribeVpcsInput{
37+
VpcIds: []*string{awssdk.String("vpc-2f09a348")},
38+
},
39+
output: &ec2sdk.DescribeVpcsOutput{
40+
Vpcs: []*ec2sdk.Vpc{{VpcId: awssdk.String("vpc-2f09a348")}},
41+
},
42+
err: nil,
43+
},
44+
},
45+
},
46+
want: &ec2sdk.Vpc{
47+
VpcId: awssdk.String("vpc-2f09a348"),
48+
},
49+
wantErr: false,
50+
},
51+
{
52+
name: "from cache",
53+
fields: fields{},
54+
want: &ec2sdk.Vpc{
55+
VpcId: awssdk.String("vpc-2f09a348"),
56+
},
57+
wantErr: false,
58+
},
59+
}
60+
61+
ctrl := gomock.NewController(t)
62+
defer ctrl.Finish()
63+
64+
ec2Client := services.NewMockEC2(ctrl)
65+
p := NewDefaultVPCInfoProvider(5, ec2Client, &log.NullLogger{})
66+
67+
for _, tt := range tests {
68+
t.Run(tt.name, func(t *testing.T) {
69+
for _, call := range tt.fields.describeVpcCalls {
70+
ec2Client.EXPECT().DescribeVpcsWithContext(gomock.Any(), call.input).Return(call.output, call.err)
71+
}
72+
73+
got, err := p.FetchVPCInfo(context.Background(), "vpc-2f09a348")
74+
if (err != nil) != tt.wantErr {
75+
t.Errorf("defaultVPCInfoProvider.FetchVPCInfo() error = %v, wantErr %v", err, tt.wantErr)
76+
return
77+
}
78+
if !reflect.DeepEqual(got, tt.want) {
79+
t.Errorf("defaultVPCInfoProvider.FetchVPCInfo() = %v, want %v", got, tt.want)
80+
}
81+
})
82+
}
83+
}

pkg/targetgroupbinding/networking_manager.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ package targetgroupbinding
33
import (
44
"context"
55
"fmt"
6+
"net"
7+
"strings"
8+
"sync"
9+
610
awssdk "github.com/aws/aws-sdk-go/aws"
711
"github.com/aws/aws-sdk-go/aws/awserr"
812
ec2sdk "github.com/aws/aws-sdk-go/service/ec2"
@@ -13,14 +17,11 @@ import (
1317
"k8s.io/apimachinery/pkg/types"
1418
"k8s.io/apimachinery/pkg/util/intstr"
1519
"k8s.io/apimachinery/pkg/util/sets"
16-
"net"
1720
elbv2api "sigs.k8s.io/aws-load-balancer-controller/apis/elbv2/v1beta1"
1821
"sigs.k8s.io/aws-load-balancer-controller/pkg/backend"
1922
"sigs.k8s.io/aws-load-balancer-controller/pkg/k8s"
2023
"sigs.k8s.io/aws-load-balancer-controller/pkg/networking"
2124
"sigs.k8s.io/controller-runtime/pkg/client"
22-
"strings"
23-
"sync"
2425
)
2526

2627
const (
@@ -40,6 +41,9 @@ type NetworkingManager interface {
4041

4142
// Cleanup reconcile network settings for TargetGroupBindings with zero endpoints.
4243
Cleanup(ctx context.Context, tgb *elbv2api.TargetGroupBinding) error
44+
45+
// VpcID returns the ID of the VPC the ELB is attached to.
46+
VpcID() string
4347
}
4448

4549
// NewDefaultNetworkingManager constructs defaultNetworkingManager.
@@ -119,6 +123,10 @@ func (m *defaultNetworkingManager) Cleanup(ctx context.Context, tgb *elbv2api.Ta
119123
return m.reconcileWithIngressPermissionsPerSG(ctx, tgb, nil)
120124
}
121125

126+
func (m *defaultNetworkingManager) VpcID() string {
127+
return m.vpcID
128+
}
129+
122130
func (m *defaultNetworkingManager) computeIngressPermissionsPerSGWithPodEndpoints(ctx context.Context, tgbNetworking elbv2api.TargetGroupBindingNetworking, endpoints []backend.PodEndpoint) (map[string][]networking.IPPermissionInfo, error) {
123131
pods := make([]k8s.PodInfo, 0, len(endpoints))
124132
podByPodKey := make(map[types.NamespacedName]k8s.PodInfo, len(endpoints))

0 commit comments

Comments
 (0)