Skip to content

Commit

Permalink
Merge pull request #499 from lobziik/local-zone
Browse files Browse the repository at this point in the history
Restrict subnets only to subnets from regular availability zones in ELB auto-discovery procedure
  • Loading branch information
k8s-ci-robot authored Dec 17, 2022
2 parents 876bfa9 + c3e6626 commit 1afaa94
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 @@ -3516,6 +3533,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 @@ -3604,8 +3650,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 1afaa94

Please sign in to comment.