Skip to content

Commit 2011294

Browse files
committed
✨ edge subnets/API: support edge subnets for Local Zones
This change introduce support of required network components to deploy subnets on AWS Local Zones infrastructure. The SubnetSpec API is introducing the field ZoneType and ParentZoneName to handle the zone information for the subnet, discovered when reconciling the subnet. ✨ edge subnets/API/gen: introduce edge subnets for Local Zones Generate API changes to suppoer edge subnets for Local Zones. ✨ edge subnets/API/test: added unit to Local Zones Testing new methods and workflow added to the API to SubnetSpec (zone information). ✨ edge subnets/docs: added guide subnets on Local and Wavelength zones Create a dedicated document, "topic", with instructions to deploy network infrastructure (subnets, gateways and route tables) in "edge zones" - Local Zone and Wavelength Zone infrastructure.
1 parent fe58fe7 commit 2011294

11 files changed

+975
-9
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)