Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fetch VPC ID from runtime using VPC tags provided via controller flags #3656

Merged
92 changes: 47 additions & 45 deletions docs/deploy/configurations.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/deploy/installation.md
Original file line number Diff line number Diff line change
@@ -37,7 +37,7 @@ You can set the IMDSv2 as follows:
aws ec2 modify-instance-metadata-options --http-put-response-hop-limit 2 --http-tokens required --region <region> --instance-id <instance-id>
```

Instead of depending on IMDSv2, you can specify the AWS Region and the VPC via the controller flags `--aws-region` and `--aws-vpc-id`.
Instead of depending on IMDSv2, you can specify the AWS Region via the controller flag `--aws-region`, and the AWS VPC via controller flag `--aws-vpc-id` or by specifying vpc tags via the flag `--aws-vpc-tags` and an optional flag `--aws-vpc-tag-key` if you have a different key for the tag other than "Name". When both flags `--aws-vpc-id` and `--aws-vpc-tags` are specified, the controller prioritizes `--aws-vpc-id`and ignores the other flag.

## Configure IAM

3 changes: 2 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
@@ -18,6 +18,7 @@ package main

import (
"os"

elbv2deploy "sigs.k8s.io/aws-load-balancer-controller/pkg/deploy/elbv2"

"github.com/go-logr/logr"
@@ -76,7 +77,7 @@ func main() {
}
ctrl.SetLogger(getLoggerWithLogLevel(controllerCFG.LogLevel))

cloud, err := aws.NewCloud(controllerCFG.AWSConfig, metrics.Registry)
cloud, err := aws.NewCloud(controllerCFG.AWSConfig, metrics.Registry, ctrl.Log)
if err != nil {
setupLog.Error(err, "unable to initialize AWS cloud")
os.Exit(1)
51 changes: 43 additions & 8 deletions pkg/aws/cloud.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package aws

import (
"context"
"fmt"
"net"
"os"
@@ -10,6 +11,7 @@ import (
"github.com/aws/aws-sdk-go/aws/endpoints"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/go-logr/logr"
"github.com/pkg/errors"
"github.com/prometheus/client_golang/prometheus"
amerrors "k8s.io/apimachinery/pkg/util/errors"
@@ -49,7 +51,7 @@ type Cloud interface {
}

// NewCloud constructs new Cloud implementation.
func NewCloud(cfg CloudConfig, metricsRegisterer prometheus.Registerer) (Cloud, error) {
func NewCloud(cfg CloudConfig, metricsRegisterer prometheus.Registerer, logger logr.Logger) (Cloud, error) {
hasIPv4 := true
addrs, err := net.InterfaceAddrs()
if err == nil {
@@ -112,14 +114,11 @@ func NewCloud(cfg CloudConfig, metricsRegisterer prometheus.Registerer) (Cloud,

ec2Service := services.NewEC2(sess)

if len(cfg.VpcID) == 0 {
vpcID, err := inferVPCID(metadata, ec2Service)
if err != nil {
return nil, errors.Wrap(err, "failed to introspect vpcID from EC2Metadata or Node name, specify --aws-vpc-id instead if EC2Metadata is unavailable")
}
cfg.VpcID = vpcID
vpcID, err := getVpcID(cfg, ec2Service, metadata, logger)
if err != nil {
return nil, errors.Wrap(err, "failed to get VPC ID")
}

cfg.VpcID = vpcID
return &defaultCloud{
cfg: cfg,
ec2: ec2Service,
@@ -132,6 +131,20 @@ func NewCloud(cfg CloudConfig, metricsRegisterer prometheus.Registerer) (Cloud,
}, nil
}

func getVpcID(cfg CloudConfig, ec2Service services.EC2, metadata services.EC2Metadata, logger logr.Logger) (string, error) {

if cfg.VpcID != "" {
logger.V(1).Info("vpcid is specified using flag --aws-vpc-id, controller will use the value", "vpc: ", cfg.VpcID)
return cfg.VpcID, nil
}

if cfg.VpcTags != nil {
return inferVPCIDFromTags(ec2Service, cfg.VpcNameTagKey, cfg.VpcTags[cfg.VpcNameTagKey])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jeswinkoshyninan I don't really get this part: why does this allow to pass a map with an arbitrary number of items for --aws-vpc-tags if we only ever pass a single key-value pair (the one with the key specified in aws-vpc-tag-key) as the tag filter? Shouldn't it rather just convert the map from --aws-vpc-tags into a list of tag filters, one for each item?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Related to #3889

}

return inferVPCID(metadata, ec2Service)
}

func inferVPCID(metadata services.EC2Metadata, ec2Service services.EC2) (string, error) {
var errList []error
vpcId, err := metadata.VpcID()
@@ -168,6 +181,28 @@ func inferVPCID(metadata services.EC2Metadata, ec2Service services.EC2) (string,
return "", amerrors.NewAggregate(errList)
}

func inferVPCIDFromTags(ec2Service services.EC2, VpcNameTagKey string, VpcNameTagValue string) (string, error) {
vpcs, err := ec2Service.DescribeVPCsAsList(context.Background(), &ec2.DescribeVpcsInput{
Filters: []*ec2.Filter{
{
Name: aws.String("tag:" + VpcNameTagKey),
Values: []*string{aws.String(VpcNameTagValue)},
},
},
})
if err != nil {
return "", fmt.Errorf("failed to fetch VPC ID with tag: %w", err)
}
if len(vpcs) == 0 {
return "", fmt.Errorf("no VPC exists with tag: %w", err)
}
if len(vpcs) > 1 {
return "", fmt.Errorf("multiple VPCs exists with tag: %w", err)
}

return *vpcs[0].VpcId, nil
}

var _ Cloud = &defaultCloud{}

type defaultCloud struct {
11 changes: 11 additions & 0 deletions pkg/aws/cloud_config.go
Original file line number Diff line number Diff line change
@@ -12,9 +12,12 @@ const (
flagAWSAPIEndpoints = "aws-api-endpoints"
flagAWSAPIThrottle = "aws-api-throttle"
flagAWSVpcID = "aws-vpc-id"
flagAWSVpcTags = "aws-vpc-tags"
flagAWSVpcCacheTTL = "aws-vpc-cache-ttl"
flagAWSMaxRetries = "aws-max-retries"
flagAWSVpcNameTagKey = "aws-vpc-tag-key"
defaultVpcID = ""
defaultVpcNameTagKey = "Name"
defaultRegion = ""
defaultAPIMaxRetries = 10
)
@@ -29,6 +32,12 @@ type CloudConfig struct {
// VpcID for the LoadBalancer resources.
VpcID string

// VPC tags List
VpcTags map[string]string

// VPC Name Tag Key, default "Name"
VpcNameTagKey string

// VPC cache TTL in minutes
VpcCacheTTL time.Duration

@@ -43,6 +52,8 @@ func (cfg *CloudConfig) BindFlags(fs *pflag.FlagSet) {
fs.StringVar(&cfg.Region, flagAWSRegion, defaultRegion, "AWS Region for the kubernetes cluster")
fs.Var(cfg.ThrottleConfig, flagAWSAPIThrottle, "throttle settings for AWS APIs, format: serviceID1:operationRegex1=rate:burst,serviceID2:operationRegex2=rate:burst")
fs.StringVar(&cfg.VpcID, flagAWSVpcID, defaultVpcID, "AWS VpcID for the LoadBalancer resources")
fs.StringToStringVar(&cfg.VpcTags, flagAWSVpcTags, nil, "AWS VPC tags List,format: tagkey1=tagvalue1,tagkey2=tagvalue2")
fs.StringVar(&cfg.VpcNameTagKey, flagAWSVpcNameTagKey, defaultVpcNameTagKey, "AWS tag key for identifying the VPC")
fs.IntVar(&cfg.MaxRetries, flagAWSMaxRetries, defaultAPIMaxRetries, "Maximum retries for AWS APIs")
fs.StringToStringVar(&cfg.AWSEndpoints, flagAWSAPIEndpoints, nil, "Custom AWS endpoint configuration, format: serviceID1=URL1,serviceID2=URL2")
}
23 changes: 19 additions & 4 deletions pkg/aws/services/ec2.go
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@ package services

import (
"context"

"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
@@ -10,17 +11,20 @@ import (
type EC2 interface {
ec2iface.EC2API

// wrapper to DescribeInstancesPagesWithContext API, which aggregates paged results into list.
// DescribeInstancesAsList wraps the DescribeInstancesPagesWithContext API, which aggregates paged results into list.
DescribeInstancesAsList(ctx context.Context, input *ec2.DescribeInstancesInput) ([]*ec2.Instance, error)

// wrapper to DescribeNetworkInterfacesPagesWithContext API, which aggregates paged results into list.
// DescribeNetworkInterfacesAsList wraps the DescribeNetworkInterfacesPagesWithContext API, which aggregates paged results into list.
DescribeNetworkInterfacesAsList(ctx context.Context, input *ec2.DescribeNetworkInterfacesInput) ([]*ec2.NetworkInterface, error)

// wrapper to DescribeSecurityGroupsPagesWithContext API, which aggregates paged results into list.
// DescribeSecurityGroupsAsList wraps the DescribeSecurityGroupsPagesWithContext API, which aggregates paged results into list.
DescribeSecurityGroupsAsList(ctx context.Context, input *ec2.DescribeSecurityGroupsInput) ([]*ec2.SecurityGroup, error)

// wrapper to DescribeSubnetsPagesWithContext API, which aggregates paged results into list.
// DescribeSubnetsAsList wraps the DescribeSubnetsPagesWithContext API, which aggregates paged results into list.
DescribeSubnetsAsList(ctx context.Context, input *ec2.DescribeSubnetsInput) ([]*ec2.Subnet, error)

// DescribeVPCsAsList wraps the DescribeVpcsPagesWithContext API, which aggregates paged results into list.
DescribeVPCsAsList(ctx context.Context, input *ec2.DescribeVpcsInput) ([]*ec2.Vpc, error)
}

// NewEC2 constructs new EC2 implementation.
@@ -79,3 +83,14 @@ func (c *defaultEC2) DescribeSubnetsAsList(ctx context.Context, input *ec2.Descr
}
return result, nil
}

func (c *defaultEC2) DescribeVPCsAsList(ctx context.Context, input *ec2.DescribeVpcsInput) ([]*ec2.Vpc, error) {
var result []*ec2.Vpc
if err := c.DescribeVpcsPagesWithContext(ctx, input, func(output *ec2.DescribeVpcsOutput, _ bool) bool {
result = append(result, output.Vpcs...)
return true
}); err != nil {
return nil, err
}
return result, nil
}
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.

6 changes: 3 additions & 3 deletions test/framework/framework.go
Original file line number Diff line number Diff line change
@@ -55,18 +55,18 @@ func InitFramework() (*Framework, error) {
return nil, err
}

logger, loggerReporter := utils.NewGinkgoLogger()

cloud, err := aws.NewCloud(aws.CloudConfig{
Region: globalOptions.AWSRegion,
VpcID: globalOptions.AWSVPCID,
MaxRetries: 3,
ThrottleConfig: throttle.NewDefaultServiceOperationsThrottleConfig(),
}, nil)
}, nil, logger)
if err != nil {
return nil, err
}

logger, loggerReporter := utils.NewGinkgoLogger()

f := &Framework{
Options: globalOptions,
RestCfg: restCfg,