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
Expand Up @@ -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". Note that if you specify flags `--aws-vpc-id` and `--aws-vpc-tags`, then value given to `--aws-vpc-id` will be taken by controller.
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: reword a bit

Note that if both flags --aws-vpc-id and --aws-vpc-tags are specified, the controller uses the value in --aws-vpc-id to fetch the VPC info and ignores the other flag.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

refactor the sentence. thanks


## Configure IAM

Expand Down
50 changes: 43 additions & 7 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"
Expand All @@ -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"
Expand Down Expand Up @@ -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)
if err != nil {
return nil, errors.Wrap(err, "failed to get VPC ID")
}

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

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

logger := logr.Logger{}
if cfg.VpcID != "" {
logger.V(1).Info("vpcid is specified using flag --aws-vpc-id, controller will use the value %s", 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?

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()
Expand Down Expand Up @@ -168,6 +182,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 {
Expand Down
11 changes: 11 additions & 0 deletions pkg/aws/cloud_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand All @@ -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

Expand All @@ -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
Expand Up @@ -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"
Expand All @@ -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.
Expand Down Expand Up @@ -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.