Skip to content

Commit

Permalink
Restrict subnets to subnets from regular availability zones
Browse files Browse the repository at this point in the history
Outpost, wavelength, and local zone don't support NLB or CLB for the moment.
This commit adds check for availability zone type and includes only
subnets from `availability-zone` typed zones for the auto-discovery.
  • Loading branch information
lobziik committed Dec 9, 2022
1 parent 3c03bb9 commit c3e6626
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/prerequisites.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ For the `aws-cloud-controller-manager` to be able to communicate to AWS APIs, yo
"ec2:DescribeSecurityGroups",
"ec2:DescribeSubnets",
"ec2:DescribeVolumes",
"ec2:DescribeAvailabilityZones",
"ec2:CreateSecurityGroup",
"ec2:CreateTags",
"ec2:CreateVolume",
Expand Down
58 changes: 58 additions & 0 deletions pkg/providers/v1/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,12 @@ const (
volumeDetachedStatus = "detached"
)

const (
localZoneType = "local-zone"
wavelengthZoneType = "wavelength-zone"
regularAvailabilityZoneType = "availability-zone"
)

// awsTagNameMasterRoles is a set of well-known AWS tag names that indicate the instance is a master
// The major consequence is that it is then not considered for AWS zone discovery for dynamic volume creation.
var awsTagNameMasterRoles = sets.NewString("kubernetes.io/role/master", "k8s.io/role/master")
Expand Down Expand Up @@ -365,6 +371,8 @@ type EC2 interface {

DescribeSubnets(*ec2.DescribeSubnetsInput) ([]*ec2.Subnet, error)

DescribeAvailabilityZones(request *ec2.DescribeAvailabilityZonesInput) ([]*ec2.AvailabilityZone, error)

CreateTags(*ec2.CreateTagsInput) (*ec2.CreateTagsOutput, error)
DeleteTags(input *ec2.DeleteTagsInput) (*ec2.DeleteTagsOutput, error)

Expand Down Expand Up @@ -1156,6 +1164,15 @@ func (s *awsSdkEC2) DescribeSubnets(request *ec2.DescribeSubnetsInput) ([]*ec2.S
return response.Subnets, nil
}

func (s *awsSdkEC2) DescribeAvailabilityZones(request *ec2.DescribeAvailabilityZonesInput) ([]*ec2.AvailabilityZone, error) {
// AZs are not paged
response, err := s.ec2.DescribeAvailabilityZones(request)
if err != nil {
return nil, fmt.Errorf("error listing AWS availability zones: %q", err)
}
return response.AvailabilityZones, err
}

func (s *awsSdkEC2) CreateSecurityGroup(request *ec2.CreateSecurityGroupInput) (*ec2.CreateSecurityGroupOutput, error) {
return s.ec2.CreateSecurityGroup(request)
}
Expand Down Expand Up @@ -3508,6 +3525,35 @@ func (c *Cloud) findSubnets() ([]*ec2.Subnet, error) {
return subnets, nil
}

// Returns a mapping between availability zone names and their types
// Zone will not be included in the map in case it was not found in AWS by name
func (c *Cloud) getZoneTypesByName(azNames []string) (map[string]string, error) {
if len(azNames) == 0 {
// if az names slice is empty, no need to make a request, return early with empty map
return map[string]string{}, nil
}
azFilter := newEc2Filter("zone-name", azNames...)
azRequest := &ec2.DescribeAvailabilityZonesInput{}
azRequest.Filters = []*ec2.Filter{azFilter}

azs, err := c.ec2.DescribeAvailabilityZones(azRequest)
if err != nil {
return nil, fmt.Errorf("error describe availability zones: %q", err)
}

azTypesMapping := make(map[string]string)
for _, az := range azs {
name := aws.StringValue(az.ZoneName)
zoneType := aws.StringValue(az.ZoneType)
if name == "" || zoneType == "" {
klog.Warningf("Ignoring zone with empty name/type: %v", az)
continue
}
azTypesMapping[name] = zoneType
}
return azTypesMapping, nil
}

// Finds the subnets to use for an ELB we are creating.
// Normal (Internet-facing) ELBs must use public subnets, so we skip private subnets.
// Internal ELBs can use public or private subnets, but if we have a private subnet we should prefer that.
Expand Down Expand Up @@ -3596,8 +3642,20 @@ func (c *Cloud) findELBSubnets(internalELB bool) ([]string, error) {

sort.Strings(azNames)

azTypesMapping, err := c.getZoneTypesByName(azNames)
if err != nil {
return nil, fmt.Errorf("error get availability zone types: %q", err)
}

var subnetIDs []string
for _, key := range azNames {
azType, found := azTypesMapping[key]
if found && azType != regularAvailabilityZoneType {
// take subnets only from zones with `availability-zone` type
// because another zone types (like local, wavelength and outpost zones)
// does not support NLB/CLB for the moment, only ALB.
continue
}
subnetIDs = append(subnetIDs, aws.StringValue(subnetsByAZ[key].SubnetId))
}

Expand Down
22 changes: 22 additions & 0 deletions pkg/providers/v1/aws_fakes.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,28 @@ func (ec2i *FakeEC2Impl) RemoveSubnets() {
ec2i.Subnets = ec2i.Subnets[:0]
}

// DescribeAvailabilityZones returns fake availability zones
// For every input returns a hardcoded list of fake availability zones for the moment
func (ec2i *FakeEC2Impl) DescribeAvailabilityZones(request *ec2.DescribeAvailabilityZonesInput) ([]*ec2.AvailabilityZone, error) {
var azs []*ec2.AvailabilityZone

fakeZones := [5]string{"az-local", "az-wavelength", "us-west-2a", "us-west-2b", "us-west-2c"}
for _, name := range fakeZones {
var zoneType *string
switch name {
case "az-local":
zoneType = aws.String(localZoneType)
case "az-wavelength":
zoneType = aws.String(wavelengthZoneType)
default:
zoneType = aws.String(regularAvailabilityZoneType)
}
zone := &ec2.AvailabilityZone{ZoneName: aws.String(name), ZoneType: zoneType, ZoneId: aws.String(name)}
azs = append(azs, zone)
}
return azs, nil
}

// CreateTags is a mock for CreateTags from EC2
func (ec2i *FakeEC2Impl) CreateTags(input *ec2.CreateTagsInput) (*ec2.CreateTagsOutput, error) {
for _, id := range input.Resources {
Expand Down
31 changes: 31 additions & 0 deletions pkg/providers/v1/aws_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -999,6 +999,26 @@ func Test_findELBSubnets(t *testing.T) {
AvailabilityZone: aws.String("us-west-2c"),
SubnetId: aws.String("subnet-notag"),
}
subnetLocalZone := &ec2.Subnet{
AvailabilityZone: aws.String("az-local"),
SubnetId: aws.String("subnet-in-local-zone"),
Tags: []*ec2.Tag{
{
Key: aws.String(c.tagging.clusterTagKey()),
Value: aws.String("owned"),
},
},
}
subnetWavelengthZone := &ec2.Subnet{
AvailabilityZone: aws.String("az-wavelength"),
SubnetId: aws.String("subnet-in-wavelength-zone"),
Tags: []*ec2.Tag{
{
Key: aws.String(c.tagging.clusterTagKey()),
Value: aws.String("owned"),
},
},
}

tests := []struct {
name string
Expand Down Expand Up @@ -1122,6 +1142,17 @@ func Test_findELBSubnets(t *testing.T) {
},
want: []string{"subnet-a0000001", "subnet-b0000001", "subnet-c0000001"},
},
{
name: "exclude subnets from local and wavelenght zones",
subnets: []*ec2.Subnet{
subnetA0000001,
subnetB0000001,
subnetC0000001,
subnetLocalZone,
subnetWavelengthZone,
},
want: []string{"subnet-a0000001", "subnet-b0000001", "subnet-c0000001"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down

0 comments on commit c3e6626

Please sign in to comment.