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
5 changes: 5 additions & 0 deletions api/v1beta1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,11 @@ const (
Public = LBType("Public")
)

const (
// IPv6 is the value for IPv6 address version.
IPv6 = "IPv6"
)

// FrontendIP defines a load balancer frontend IP configuration.
type FrontendIP struct {
// +kubebuilder:validation:MinLength=1
Expand Down
5 changes: 5 additions & 0 deletions api/v1beta1/types_class.go
Original file line number Diff line number Diff line change
Expand Up @@ -535,4 +535,9 @@ type SecurityGroupClass struct {
type FrontendIPClass struct {
// +optional
PrivateIPAddress string `json:"privateIP,omitempty"`
// IPVersion specifies the IP version for this frontend IP. Valid values are "IPv4" and "IPv6".
// Defaults to "IPv4" if not specified.
// +kubebuilder:validation:Enum=IPv4;IPv6
// +optional
IPVersion string `json:"ipVersion,omitempty"`
}
10 changes: 5 additions & 5 deletions azure/scope/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,8 @@ func (s *ClusterScope) PublicIPSpecs() []azure.ResourceSpecGetter {
Name: ip.PublicIP.Name,
ResourceGroup: s.ResourceGroup(),
ClusterName: s.ClusterName(),
DNSName: "", // Set to default value
IsIPv6: false, // Set to default value
DNSName: "", // Set to default value
IsIPv6: ip.IPVersion == infrav1.IPv6,
Location: s.Location(),
ExtendedLocation: s.ExtendedLocation(),
FailureDomains: s.FailureDomains(),
Expand All @@ -179,7 +179,7 @@ func (s *ClusterScope) PublicIPSpecs() []azure.ResourceSpecGetter {
Name: s.APIServerPublicIP().Name,
ResourceGroup: s.ResourceGroup(),
DNSName: s.APIServerPublicIP().DNSName,
IsIPv6: false, // Currently azure requires an IPv4 lb rule to enable IPv6
IsIPv6: s.APIServerLB().FrontendIPs[0].IPVersion == infrav1.IPv6,
ClusterName: s.ClusterName(),
Location: s.Location(),
ExtendedLocation: s.ExtendedLocation(),
Expand All @@ -199,8 +199,8 @@ func (s *ClusterScope) PublicIPSpecs() []azure.ResourceSpecGetter {
Name: ip.PublicIP.Name,
ResourceGroup: s.ResourceGroup(),
ClusterName: s.ClusterName(),
DNSName: "", // Set to default value
IsIPv6: false, // Set to default value
DNSName: "", // Set to default value
IsIPv6: ip.IPVersion == infrav1.IPv6,
Location: s.Location(),
ExtendedLocation: s.ExtendedLocation(),
FailureDomains: s.FailureDomains(),
Expand Down
52 changes: 52 additions & 0 deletions azure/services/loadbalancers/loadbalancers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,58 @@ var (
},
}

fakeInternalAPILBSpecIPv6 = LBSpec{
Name: "my-private-lb",
ResourceGroup: "my-rg",
SubscriptionID: "123",
ClusterName: "my-cluster",
Location: "my-location",
VNetName: "my-vnet",
VNetResourceGroup: "my-rg",
Role: infrav1.APIServerRole,
Type: infrav1.Internal,
SKU: infrav1.SKUStandard,
SubnetName: "my-cp-subnet",
BackendPoolName: "my-private-lb-backendPool",
IdleTimeoutInMinutes: ptr.To[int32](4),
FrontendIPConfigs: []infrav1.FrontendIP{
{
Name: "my-private-lb-frontEnd-ipv6",
FrontendIPClass: infrav1.FrontendIPClass{
IPVersion: infrav1.IPv6,
},
},
},
APIServerPort: 6443,
}

fakePublicAPILBSpecIPv6 = LBSpec{
Name: "my-publiclb",
ResourceGroup: "my-rg",
SubscriptionID: "123",
ClusterName: "my-cluster",
Location: "my-location",
Role: infrav1.APIServerRole,
Type: infrav1.Public,
SKU: infrav1.SKUStandard,
SubnetName: "my-cp-subnet",
BackendPoolName: "my-publiclb-backendPool",
IdleTimeoutInMinutes: ptr.To[int32](4),
FrontendIPConfigs: []infrav1.FrontendIP{
{
Name: "my-publiclb-frontEnd-ipv6",
FrontendIPClass: infrav1.FrontendIPClass{
IPVersion: infrav1.IPv6,
},
PublicIP: &infrav1.PublicIPSpec{
Name: "my-publicip-ipv6",
DNSName: "my-cluster.12345.mydomain.com",
},
},
},
APIServerPort: 6443,
}

internalError = &azcore.ResponseError{
RawResponse: &http.Response{
Body: io.NopCloser(strings.NewReader("#: Internal Server Error: StatusCode=500")),
Expand Down
5 changes: 5 additions & 0 deletions azure/services/loadbalancers/spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,11 @@ func getFrontendIPConfigs(lbSpec LBSpec) ([]*armnetwork.FrontendIPConfiguration,
},
PrivateIPAddress: ptr.To(ipConfig.PrivateIPAddress),
}
if ipConfig.IPVersion == infrav1.IPv6 {
properties.PrivateIPAddressVersion = ptr.To(armnetwork.IPVersionIPv6)
properties.PrivateIPAllocationMethod = ptr.To(armnetwork.IPAllocationMethodDynamic)
properties.PrivateIPAddress = nil
}
} else {
properties = armnetwork.FrontendIPConfigurationPropertiesFormat{
PublicIPAddress: &armnetwork.PublicIPAddress{
Expand Down
30 changes: 30 additions & 0 deletions azure/services/loadbalancers/spec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,36 @@ func TestParameters(t *testing.T) {
},
expectedError: "",
},
{
name: "internal API load balancer with IPv6 frontend",
spec: &fakeInternalAPILBSpecIPv6,
existing: nil,
expect: func(g *WithT, result any) {
g.Expect(result).To(BeAssignableToTypeOf(armnetwork.LoadBalancer{}))
lb := result.(armnetwork.LoadBalancer)
g.Expect(lb.Properties.FrontendIPConfigurations).To(HaveLen(1))
frontendIP := lb.Properties.FrontendIPConfigurations[0]
g.Expect(*frontendIP.Name).To(Equal("my-private-lb-frontEnd-ipv6"))
g.Expect(*frontendIP.Properties.PrivateIPAllocationMethod).To(Equal(armnetwork.IPAllocationMethodDynamic))
g.Expect(*frontendIP.Properties.PrivateIPAddressVersion).To(Equal(armnetwork.IPVersionIPv6))
g.Expect(frontendIP.Properties.PrivateIPAddress).To(BeNil())
},
expectedError: "",
},
{
name: "public API load balancer with IPv6 frontend",
spec: &fakePublicAPILBSpecIPv6,
existing: nil,
expect: func(g *WithT, result any) {
g.Expect(result).To(BeAssignableToTypeOf(armnetwork.LoadBalancer{}))
lb := result.(armnetwork.LoadBalancer)
g.Expect(lb.Properties.FrontendIPConfigurations).To(HaveLen(1))
frontendIP := lb.Properties.FrontendIPConfigurations[0]
g.Expect(*frontendIP.Name).To(Equal("my-publiclb-frontEnd-ipv6"))
g.Expect(*frontendIP.Properties.PublicIPAddress.ID).To(Equal("/subscriptions/123/resourceGroups/my-rg/providers/Microsoft.Network/publicIPAddresses/my-publicip-ipv6"))
},
expectedError: "",
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
Expand Down
44 changes: 44 additions & 0 deletions docs/book/src/self-managed/api-server-endpoint.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,50 @@ Note that `dns` is the FQDN associated to your public IP address (look for "DNS

When you BYO api server IP, CAPZ does not manage its lifecycle, ie. the IP will not get deleted as part of cluster deletion.

### Frontend IP Version

By default, frontend IPs are IPv4. You can configure individual frontend IPs to use IPv6 by setting the `ipVersion` field to `"IPv6"`.

For a public load balancer with an IPv6 frontend:

```yaml
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: AzureCluster
metadata:
name: my-cluster
namespace: default
spec:
location: eastus
networkSpec:
apiServerLB:
type: Public
frontendIPs:
- name: lb-public-ip-frontend-ipv6
ipVersion: IPv6
publicIP:
name: my-public-ipv6
```

For an internal load balancer with an IPv6 frontend:

```yaml
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: AzureCluster
metadata:
name: my-private-cluster
namespace: default
spec:
location: eastus
networkSpec:
apiServerLB:
type: Internal
frontendIPs:
- name: lb-private-ip-frontend-ipv6
ipVersion: IPv6
```

Note that IPv6 internal frontend IPs use dynamic allocation and do not support specifying a `privateIP` address.

### Load Balancer SKU

At this time, CAPZ only supports Azure Standard Load Balancers. See [SKU comparison](https://learn.microsoft.com/azure/load-balancer/skus#skus) for more information on Azure Load Balancers SKUs.
Loading