Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions api/v1beta1/azurecluster_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,43 @@ func (*AzureClusterWebhook) ValidateUpdate(_ context.Context, oldRaw, newObj run
allErrs = append(allErrs, err)
}

// Validate availability zones are immutable for load balancers
if c.Spec.NetworkSpec.APIServerLB != nil && old.Spec.NetworkSpec.APIServerLB != nil {
if !webhookutils.EnsureStringSlicesAreEquivalent(
c.Spec.NetworkSpec.APIServerLB.AvailabilityZones,
old.Spec.NetworkSpec.APIServerLB.AvailabilityZones) {
allErrs = append(allErrs,
field.Invalid(
field.NewPath("spec", "networkSpec", "apiServerLB", "availabilityZones"),
c.Spec.NetworkSpec.APIServerLB.AvailabilityZones,
"field is immutable"))
}
}

if c.Spec.NetworkSpec.NodeOutboundLB != nil && old.Spec.NetworkSpec.NodeOutboundLB != nil {
if !webhookutils.EnsureStringSlicesAreEquivalent(
c.Spec.NetworkSpec.NodeOutboundLB.AvailabilityZones,
old.Spec.NetworkSpec.NodeOutboundLB.AvailabilityZones) {
allErrs = append(allErrs,
field.Invalid(
field.NewPath("spec", "networkSpec", "nodeOutboundLB", "availabilityZones"),
c.Spec.NetworkSpec.NodeOutboundLB.AvailabilityZones,
"field is immutable"))
}
}

if c.Spec.NetworkSpec.ControlPlaneOutboundLB != nil && old.Spec.NetworkSpec.ControlPlaneOutboundLB != nil {
if !webhookutils.EnsureStringSlicesAreEquivalent(
c.Spec.NetworkSpec.ControlPlaneOutboundLB.AvailabilityZones,
old.Spec.NetworkSpec.ControlPlaneOutboundLB.AvailabilityZones) {
allErrs = append(allErrs,
field.Invalid(
field.NewPath("spec", "networkSpec", "controlPlaneOutboundLB", "availabilityZones"),
c.Spec.NetworkSpec.ControlPlaneOutboundLB.AvailabilityZones,
"field is immutable"))
}
}

allErrs = append(allErrs, c.validateSubnetUpdate(old)...)

if len(allErrs) == 0 {
Expand Down
8 changes: 8 additions & 0 deletions api/v1beta1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,14 @@ type LoadBalancerSpec struct {
// BackendPool describes the backend pool of the load balancer.
// +optional
BackendPool BackendPool `json:"backendPool,omitempty"`
// AvailabilityZones is a list of availability zones for the load balancer.
// When specified for an internal load balancer, the frontend IP configuration
// will be zone-redundant across the specified zones.
// For public load balancers, this should be set on the associated public IP addresses instead.
// +optional
// +listType=set
// +kubebuilder:validation:MaxItems=3
AvailabilityZones []string `json:"availabilityZones,omitempty"`

LoadBalancerClassSpec `json:",inline"`
}
Expand Down
5 changes: 5 additions & 0 deletions api/v1beta1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions azure/scope/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@ func (s *ClusterScope) LBSpecs() []azure.ResourceSpecGetter {
IdleTimeoutInMinutes: s.APIServerLB().IdleTimeoutInMinutes,
AdditionalTags: s.AdditionalTags(),
AdditionalPorts: s.AdditionalAPIServerLBPorts(),
AvailabilityZones: s.APIServerLB().AvailabilityZones,
}

if s.APIServerLB().FrontendIPs != nil {
Expand Down Expand Up @@ -301,6 +302,7 @@ func (s *ClusterScope) LBSpecs() []azure.ResourceSpecGetter {
IdleTimeoutInMinutes: s.APIServerLB().IdleTimeoutInMinutes,
AdditionalTags: s.AdditionalTags(),
AdditionalPorts: s.AdditionalAPIServerLBPorts(),
AvailabilityZones: s.APIServerLB().AvailabilityZones,
}

privateIPFound := false
Expand Down Expand Up @@ -348,6 +350,7 @@ func (s *ClusterScope) LBSpecs() []azure.ResourceSpecGetter {
IdleTimeoutInMinutes: s.NodeOutboundLB().IdleTimeoutInMinutes,
Role: infrav1.NodeOutboundRole,
AdditionalTags: s.AdditionalTags(),
AvailabilityZones: s.NodeOutboundLB().AvailabilityZones,
})
}

Expand All @@ -369,6 +372,7 @@ func (s *ClusterScope) LBSpecs() []azure.ResourceSpecGetter {
IdleTimeoutInMinutes: s.ControlPlaneOutboundLB().IdleTimeoutInMinutes,
Role: infrav1.ControlPlaneOutboundRole,
AdditionalTags: s.AdditionalTags(),
AvailabilityZones: s.ControlPlaneOutboundLB().AvailabilityZones,
})
}

Expand Down
24 changes: 24 additions & 0 deletions azure/services/loadbalancers/loadbalancers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,30 @@ var (
APIServerPort: 6443,
}

fakeInternalAPILBSpecWithZones = LBSpec{
Name: "my-private-lb",
ResourceGroup: "my-rg",
SubscriptionID: "123",
ClusterName: "my-cluster",
Location: "my-location",
Role: infrav1.APIServerRole,
Type: infrav1.Internal,
SKU: infrav1.SKUStandard,
SubnetName: "my-cp-subnet",
BackendPoolName: "my-private-lb-backendPool",
IdleTimeoutInMinutes: ptr.To[int32](4),
AvailabilityZones: []string{"1", "2", "3"},
FrontendIPConfigs: []infrav1.FrontendIP{
{
Name: "my-private-lb-frontEnd",
FrontendIPClass: infrav1.FrontendIPClass{
PrivateIPAddress: "10.0.0.10",
},
},
},
APIServerPort: 6443,
}

fakeNodeOutboundLBSpec = LBSpec{
Name: "my-cluster",
ResourceGroup: "my-rg",
Expand Down
12 changes: 12 additions & 0 deletions azure/services/loadbalancers/spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ type LBSpec struct {
IdleTimeoutInMinutes *int32
AdditionalTags map[string]string
AdditionalPorts []infrav1.LoadBalancerPort
AvailabilityZones []string
}

// ResourceName returns the name of the load balancer.
Expand Down Expand Up @@ -167,6 +168,16 @@ func (s *LBSpec) Parameters(_ context.Context, existing interface{}) (parameters
func getFrontendIPConfigs(lbSpec LBSpec) ([]*armnetwork.FrontendIPConfiguration, []*armnetwork.SubResource) {
frontendIPConfigurations := make([]*armnetwork.FrontendIPConfiguration, 0)
frontendIDs := make([]*armnetwork.SubResource, 0)

// Convert availability zones to []*string for Azure SDK
var zones []*string
if len(lbSpec.AvailabilityZones) > 0 {
zones = make([]*string, len(lbSpec.AvailabilityZones))
for i, zone := range lbSpec.AvailabilityZones {
zones[i] = ptr.To(zone)
}
}

for _, ipConfig := range lbSpec.FrontendIPConfigs {
var properties armnetwork.FrontendIPConfigurationPropertiesFormat
if lbSpec.Type == infrav1.Internal {
Expand All @@ -187,6 +198,7 @@ func getFrontendIPConfigs(lbSpec LBSpec) ([]*armnetwork.FrontendIPConfiguration,
frontendIPConfigurations = append(frontendIPConfigurations, &armnetwork.FrontendIPConfiguration{
Properties: &properties,
Name: ptr.To(ipConfig.Name),
Zones: zones,
})
frontendIDs = append(frontendIDs, &armnetwork.SubResource{
ID: ptr.To(azure.FrontendIPConfigID(lbSpec.SubscriptionID, lbSpec.ResourceGroup, lbSpec.Name, ipConfig.Name)),
Expand Down
16 changes: 16 additions & 0 deletions azure/services/loadbalancers/spec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,22 @@ func TestParameters(t *testing.T) {
},
expectedError: "",
},
{
name: "internal load balancer with availability zones",
spec: &fakeInternalAPILBSpecWithZones,
existing: nil,
expect: func(g *WithT, result interface{}) {
g.Expect(result).To(BeAssignableToTypeOf(armnetwork.LoadBalancer{}))
lb := result.(armnetwork.LoadBalancer)
// Verify zones are set on frontend IP configuration
g.Expect(lb.Properties.FrontendIPConfigurations).To(HaveLen(1))
g.Expect(lb.Properties.FrontendIPConfigurations[0].Zones).To(HaveLen(3))
g.Expect(*lb.Properties.FrontendIPConfigurations[0].Zones[0]).To(Equal("1"))
g.Expect(*lb.Properties.FrontendIPConfigurations[0].Zones[1]).To(Equal("2"))
g.Expect(*lb.Properties.FrontendIPConfigurations[0].Zones[2]).To(Equal("3"))
},
expectedError: "",
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,17 @@ spec:
description: APIServerLB is the configuration for the control-plane
load balancer.
properties:
availabilityZones:
description: |-
AvailabilityZones is a list of availability zones for the load balancer.
When specified for an internal load balancer, the frontend IP configuration
will be zone-redundant across the specified zones.
For public load balancers, this should be set on the associated public IP addresses instead.
items:
type: string
maxItems: 3
type: array
x-kubernetes-list-type: set
backendPool:
description: BackendPool describes the backend pool of the
load balancer.
Expand Down Expand Up @@ -772,6 +783,17 @@ spec:
ControlPlaneOutboundLB is the configuration for the control-plane outbound load balancer.
This is different from APIServerLB, and is used only in private clusters (optionally) for enabling outbound traffic.
properties:
availabilityZones:
description: |-
AvailabilityZones is a list of availability zones for the load balancer.
When specified for an internal load balancer, the frontend IP configuration
will be zone-redundant across the specified zones.
For public load balancers, this should be set on the associated public IP addresses instead.
items:
type: string
maxItems: 3
type: array
x-kubernetes-list-type: set
backendPool:
description: BackendPool describes the backend pool of the
load balancer.
Expand Down Expand Up @@ -854,6 +876,17 @@ spec:
description: NodeOutboundLB is the configuration for the node
outbound load balancer.
properties:
availabilityZones:
description: |-
AvailabilityZones is a list of availability zones for the load balancer.
When specified for an internal load balancer, the frontend IP configuration
will be zone-redundant across the specified zones.
For public load balancers, this should be set on the associated public IP addresses instead.
items:
type: string
maxItems: 3
type: array
x-kubernetes-list-type: set
backendPool:
description: BackendPool describes the backend pool of the
load balancer.
Expand Down
1 change: 1 addition & 0 deletions docs/book/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
- [Externally managed Azure infrastructure](./self-managed/externally-managed-azure-infrastructure.md)
- [Failure Domains](./self-managed/failure-domains.md)
- [Flatcar](./self-managed/flatcar.md)
- [Load Balancer Zone Redundancy](./self-managed/load-balancer-zone-redundancy.md)
- [GPU-enabled Clusters](./self-managed/gpu.md)
- [IPv6](./self-managed/ipv6.md)
- [Machine Pools (VMSS)](./self-managed/machinepools.md)
Expand Down
Loading