Skip to content

Commit a64bed0

Browse files
authored
Merge pull request #4882 from mtulio/CORS-2899-edge-zones
✨ Introduce edge subnets to support AWS Local Zones
2 parents 810bbf4 + 2011294 commit a64bed0

19 files changed

+2377
-75
lines changed

api/v1beta1/awscluster_conversion.go

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,14 +104,19 @@ func (src *AWSCluster) ConvertTo(dstRaw conversion.Hub) error {
104104
dst.Spec.NetworkSpec.VPC.EmptyRoutesDefaultVPCSecurityGroup = restored.Spec.NetworkSpec.VPC.EmptyRoutesDefaultVPCSecurityGroup
105105
dst.Spec.NetworkSpec.VPC.PrivateDNSHostnameTypeOnLaunch = restored.Spec.NetworkSpec.VPC.PrivateDNSHostnameTypeOnLaunch
106106

107-
// Restore SubnetSpec.ResourceID field, if any.
107+
// Restore SubnetSpec.ResourceID, SubnetSpec.ParentZoneName, and SubnetSpec.ZoneType fields, if any.
108108
for _, subnet := range restored.Spec.NetworkSpec.Subnets {
109-
if len(subnet.ResourceID) == 0 {
110-
continue
111-
}
112109
for i, dstSubnet := range dst.Spec.NetworkSpec.Subnets {
113110
if dstSubnet.ID == subnet.ID {
114-
dstSubnet.ResourceID = subnet.ResourceID
111+
if len(subnet.ResourceID) > 0 {
112+
dstSubnet.ResourceID = subnet.ResourceID
113+
}
114+
if subnet.ParentZoneName != nil {
115+
dstSubnet.ParentZoneName = subnet.ParentZoneName
116+
}
117+
if subnet.ZoneType != nil {
118+
dstSubnet.ZoneType = subnet.ZoneType
119+
}
115120
dstSubnet.DeepCopyInto(&dst.Spec.NetworkSpec.Subnets[i])
116121
}
117122
}

api/v1beta1/zz_generated.conversion.go

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/v1beta2/awscluster_webhook.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,11 @@ func (r *AWSCluster) validateNetwork() field.ErrorList {
248248
if subnet.IsIPv6 || subnet.IPv6CidrBlock != "" {
249249
allErrs = append(allErrs, field.Invalid(field.NewPath("subnets"), r.Spec.NetworkSpec.Subnets, "IPv6 cannot be used with unmanaged clusters at this time."))
250250
}
251+
if subnet.ZoneType != nil && subnet.IsEdge() {
252+
if subnet.ParentZoneName == nil {
253+
allErrs = append(allErrs, field.Invalid(field.NewPath("subnets"), r.Spec.NetworkSpec.Subnets, "ParentZoneName must be set when ZoneType is 'local-zone'."))
254+
}
255+
}
251256
}
252257

253258
if r.Spec.NetworkSpec.VPC.CidrBlock != "" && r.Spec.NetworkSpec.VPC.IPAMPool != nil {

api/v1beta2/network_types.go

Lines changed: 121 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ import (
2020
"fmt"
2121
"sort"
2222
"time"
23+
24+
"github.com/aws/aws-sdk-go/aws"
25+
"github.com/aws/aws-sdk-go/service/ec2"
26+
"k8s.io/utils/ptr"
2327
)
2428

2529
const (
@@ -37,6 +41,11 @@ const (
3741
DefaultAPIServerHealthThresholdCount = 5
3842
// DefaultAPIServerUnhealthThresholdCount the API server unhealthy check threshold count.
3943
DefaultAPIServerUnhealthThresholdCount = 3
44+
45+
// ZoneTypeAvailabilityZone defines the regular AWS zones in the Region.
46+
ZoneTypeAvailabilityZone ZoneType = "availability-zone"
47+
// ZoneTypeLocalZone defines the AWS zone type in Local Zone infrastructure.
48+
ZoneTypeLocalZone ZoneType = "local-zone"
4049
)
4150

4251
// NetworkStatus encapsulates AWS networking resources.
@@ -508,6 +517,39 @@ type SubnetSpec struct {
508517

509518
// Tags is a collection of tags describing the resource.
510519
Tags Tags `json:"tags,omitempty"`
520+
521+
// ZoneType defines the type of the zone where the subnet is created.
522+
//
523+
// The valid values are availability-zone, and local-zone.
524+
//
525+
// Subnet with zone type availability-zone (regular) is always selected to create cluster
526+
// resources, like Load Balancers, NAT Gateways, Contol Plane nodes, etc.
527+
//
528+
// Subnet with zone type local-zone is not eligible to automatically create
529+
// regular cluster resources.
530+
//
531+
// The public subnet in availability-zone or local-zone is associated with regular public
532+
// route table with default route entry to a Internet Gateway.
533+
//
534+
// The private subnet in the availability-zone is associated with a private route table with
535+
// the default route entry to a NAT Gateway created in that zone.
536+
//
537+
// The private subnet in the local-zone is associated with a private route table with
538+
// the default route entry re-using the NAT Gateway in the Region (preferred from the
539+
// parent zone, the zone type availability-zone in the region, or first table available).
540+
//
541+
// +kubebuilder:validation:Enum=availability-zone;local-zone
542+
// +optional
543+
ZoneType *ZoneType `json:"zoneType,omitempty"`
544+
545+
// ParentZoneName is the zone name where the current subnet's zone is tied when
546+
// the zone is a Local Zone.
547+
//
548+
// The subnets in Local Zone locations consume the ParentZoneName to determine the correct
549+
// private route table to egress traffic to the internet.
550+
//
551+
// +optional
552+
ParentZoneName *string `json:"parentZoneName,omitempty"`
511553
}
512554

513555
// GetResourceID returns the identifier for this subnet,
@@ -524,6 +566,39 @@ func (s *SubnetSpec) String() string {
524566
return fmt.Sprintf("id=%s/az=%s/public=%v", s.GetResourceID(), s.AvailabilityZone, s.IsPublic)
525567
}
526568

569+
// IsEdge returns the true when the subnet is created in the edge zone,
570+
// Local Zones.
571+
func (s *SubnetSpec) IsEdge() bool {
572+
return s.ZoneType != nil && *s.ZoneType == ZoneTypeLocalZone
573+
}
574+
575+
// SetZoneInfo updates the subnets with zone information.
576+
func (s *SubnetSpec) SetZoneInfo(zones []*ec2.AvailabilityZone) error {
577+
zoneInfo := func(zoneName string) *ec2.AvailabilityZone {
578+
for _, zone := range zones {
579+
if aws.StringValue(zone.ZoneName) == zoneName {
580+
return zone
581+
}
582+
}
583+
return nil
584+
}
585+
586+
zone := zoneInfo(s.AvailabilityZone)
587+
if zone == nil {
588+
if len(s.AvailabilityZone) > 0 {
589+
return fmt.Errorf("unable to update zone information for subnet '%v' and zone '%v'", s.ID, s.AvailabilityZone)
590+
}
591+
return fmt.Errorf("unable to update zone information for subnet '%v'", s.ID)
592+
}
593+
if zone.ZoneType != nil {
594+
s.ZoneType = ptr.To(ZoneType(*zone.ZoneType))
595+
}
596+
if zone.ParentZoneName != nil {
597+
s.ParentZoneName = zone.ParentZoneName
598+
}
599+
return nil
600+
}
601+
527602
// Subnets is a slice of Subnet.
528603
// +listType=map
529604
// +listMapKey=id
@@ -541,6 +616,22 @@ func (s Subnets) ToMap() map[string]*SubnetSpec {
541616

542617
// IDs returns a slice of the subnet ids.
543618
func (s Subnets) IDs() []string {
619+
res := []string{}
620+
for _, subnet := range s {
621+
// Prevent returning edge zones (Local Zone) to regular Subnet IDs.
622+
// Edge zones should not deploy control plane nodes, and does not support Nat Gateway and
623+
// Network Load Balancers. Any resource for the core infrastructure should not consume edge
624+
// zones.
625+
if subnet.IsEdge() {
626+
continue
627+
}
628+
res = append(res, subnet.GetResourceID())
629+
}
630+
return res
631+
}
632+
633+
// IDsWithEdge returns a slice of the subnet ids.
634+
func (s Subnets) IDsWithEdge() []string {
544635
res := []string{}
545636
for _, subnet := range s {
546637
res = append(res, subnet.GetResourceID())
@@ -581,21 +672,29 @@ func (s Subnets) FindEqual(spec *SubnetSpec) *SubnetSpec {
581672
// FilterPrivate returns a slice containing all subnets marked as private.
582673
func (s Subnets) FilterPrivate() (res Subnets) {
583674
for _, x := range s {
675+
// Subnets in AWS Local Zones or Wavelength should not be used by core infrastructure.
676+
if x.IsEdge() {
677+
continue
678+
}
584679
if !x.IsPublic {
585680
res = append(res, x)
586681
}
587682
}
588-
return
683+
return res
589684
}
590685

591686
// FilterPublic returns a slice containing all subnets marked as public.
592687
func (s Subnets) FilterPublic() (res Subnets) {
593688
for _, x := range s {
689+
// Subnets in AWS Local Zones or Wavelength should not be used by core infrastructure.
690+
if x.IsEdge() {
691+
continue
692+
}
594693
if x.IsPublic {
595694
res = append(res, x)
596695
}
597696
}
598-
return
697+
return res
599698
}
600699

601700
// FilterByZone returns a slice containing all subnets that live in the availability zone specified.
@@ -605,22 +704,32 @@ func (s Subnets) FilterByZone(zone string) (res Subnets) {
605704
res = append(res, x)
606705
}
607706
}
608-
return
707+
return res
609708
}
610709

611710
// GetUniqueZones returns a slice containing the unique zones of the subnets.
612711
func (s Subnets) GetUniqueZones() []string {
613712
keys := make(map[string]bool)
614713
zones := []string{}
615714
for _, x := range s {
616-
if _, value := keys[x.AvailabilityZone]; !value {
715+
if _, value := keys[x.AvailabilityZone]; len(x.AvailabilityZone) > 0 && !value {
617716
keys[x.AvailabilityZone] = true
618717
zones = append(zones, x.AvailabilityZone)
619718
}
620719
}
621720
return zones
622721
}
623722

723+
// SetZoneInfo updates the subnets with zone information.
724+
func (s Subnets) SetZoneInfo(zones []*ec2.AvailabilityZone) error {
725+
for i := range s {
726+
if err := s[i].SetZoneInfo(zones); err != nil {
727+
return err
728+
}
729+
}
730+
return nil
731+
}
732+
624733
// CNISpec defines configuration for CNI.
625734
type CNISpec struct {
626735
// CNIIngressRules specify rules to apply to control plane and worker node security groups.
@@ -835,3 +944,11 @@ func (i *IngressRule) Equals(o *IngressRule) bool {
835944

836945
return true
837946
}
947+
948+
// ZoneType defines listener AWS Availability Zone type.
949+
type ZoneType string
950+
951+
// String returns the string representation for the zone type.
952+
func (z ZoneType) String() string {
953+
return string(z)
954+
}

0 commit comments

Comments
 (0)