Skip to content

Commit 04d4286

Browse files
committed
api: add spec field to configure target group ipType
The field targetGroupIPType is added to the loadbalancer spec to allow configuring ip address type of target group for API load balancers. This field is not applicable to Classic Load Balancers (CLB). This commit also defines a new network status field to determine the ip type of API load balancers.
1 parent e5e3ad3 commit 04d4286

13 files changed

+1395
-91
lines changed

api/v1beta1/awscluster_conversion.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ func (src *AWSCluster) ConvertTo(dstRaw conversion.Hub) error {
156156
func restoreControlPlaneLoadBalancerStatus(restored, dst *infrav1.LoadBalancer) {
157157
dst.ARN = restored.ARN
158158
dst.LoadBalancerType = restored.LoadBalancerType
159+
dst.LoadBalancerIPAddressType = restored.LoadBalancerIPAddressType
159160
dst.ELBAttributes = restored.ELBAttributes
160161
dst.ELBListeners = restored.ELBListeners
161162
dst.Name = restored.Name
@@ -193,6 +194,7 @@ func restoreControlPlaneLoadBalancer(restored, dst *infrav1.AWSLoadBalancerSpec)
193194
dst.Scheme = restored.Scheme
194195
dst.CrossZoneLoadBalancing = restored.CrossZoneLoadBalancing
195196
dst.Subnets = restored.Subnets
197+
dst.TargetGroupIPType = restored.TargetGroupIPType
196198
}
197199

198200
// ConvertFrom converts the v1beta1 AWSCluster receiver to a v1beta1 AWSCluster.

api/v1beta1/zz_generated.conversion.go

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

api/v1beta2/awscluster_types.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,15 @@ type AWSLoadBalancerSpec struct {
253253
// PreserveClientIP lets the user control if preservation of client ips must be retained or not.
254254
// If this is enabled 6443 will be opened to 0.0.0.0/0.
255255
PreserveClientIP bool `json:"preserveClientIP,omitempty"`
256+
257+
// TargetGroupIPType sets the IP address type for the target group.
258+
// Valid values are ipv4 and ipv6. If not specified, defaults to ipv4 unless
259+
// the VPC has IPv6 enabled, in which case it defaults to ipv6.
260+
// This applies to the API server target group.
261+
// This field cannot be set if LoadBalancerType is classic or disabled.
262+
// +kubebuilder:validation:Enum=ipv4;ipv6
263+
// +optional
264+
TargetGroupIPType *TargetGroupIPType `json:"targetGroupIPType,omitempty"`
256265
}
257266

258267
// AdditionalListenerSpec defines the desired state of an
@@ -272,6 +281,14 @@ type AdditionalListenerSpec struct {
272281
// HealthCheck sets the optional custom health check configuration to the API target group.
273282
// +optional
274283
HealthCheck *TargetGroupHealthCheckAdditionalSpec `json:"healthCheck,omitempty"`
284+
285+
// TargetGroupIPType sets the IP address type for the target group.
286+
// Valid values are ipv4 and ipv6. If not specified, defaults to ipv4 unless
287+
// the VPC has IPv6 enabled, in which case it defaults to ipv6.
288+
// This field cannot be set if LoadBalancerType is classic or disabled.
289+
// +kubebuilder:validation:Enum=ipv4;ipv6
290+
// +optional
291+
TargetGroupIPType *TargetGroupIPType `json:"targetGroupIPType,omitempty"`
275292
}
276293

277294
// AWSClusterStatus defines the observed state of AWSCluster.

api/v1beta2/awscluster_webhook.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,14 @@ func (r *AWSCluster) validateControlPlaneLoadBalancerUpdate(oldlb, newlb *AWSLoa
238238
)
239239
}
240240
}
241+
242+
// TargetGroupIPType is immutable after creation.
243+
if !cmp.Equal(oldlb.TargetGroupIPType, newlb.TargetGroupIPType) {
244+
allErrs = append(allErrs,
245+
field.Forbidden(field.NewPath("spec", "controlPlaneLoadBalancer", "targetGroupIPType"),
246+
"field is immutable and cannot be changed after target group creation"),
247+
)
248+
}
241249
}
242250

243251
return allErrs
@@ -467,6 +475,33 @@ func (r *AWSCluster) validateControlPlaneLBs() (admission.Warnings, field.ErrorL
467475
if r.Spec.ControlPlaneLoadBalancer.DisableHostsRewrite {
468476
allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "controlPlaneLoadBalancer", "disableHostsRewrite"), r.Spec.ControlPlaneLoadBalancer.DisableHostsRewrite, "cannot disable hosts rewrite if the LoadBalancer reconciliation is disabled"))
469477
}
478+
479+
if r.Spec.ControlPlaneLoadBalancer.TargetGroupIPType != nil {
480+
allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "controlPlaneLoadBalancer", "targetGroupIPType"), r.Spec.ControlPlaneLoadBalancer.TargetGroupIPType, "cannot set target group IP type if the LoadBalancer reconciliation is disabled"))
481+
}
482+
}
483+
484+
if r.Spec.ControlPlaneLoadBalancer != nil {
485+
basePath := field.NewPath("spec", "controlPlaneLoadBalancer")
486+
if r.Spec.ControlPlaneLoadBalancer.TargetGroupIPType != nil {
487+
allErrs = append(allErrs, r.validateTargetGroupIPType(basePath.Child("targetGroupIPType"), r.Spec.ControlPlaneLoadBalancer.TargetGroupIPType, r.Spec.ControlPlaneLoadBalancer)...)
488+
}
489+
for i, listener := range r.Spec.ControlPlaneLoadBalancer.AdditionalListeners {
490+
if listener.TargetGroupIPType != nil {
491+
allErrs = append(allErrs, r.validateTargetGroupIPType(basePath.Child("additionalListeners").Index(i).Child("targetGroupIPType"), listener.TargetGroupIPType, r.Spec.ControlPlaneLoadBalancer)...)
492+
}
493+
}
494+
}
495+
if r.Spec.SecondaryControlPlaneLoadBalancer != nil {
496+
basePath := field.NewPath("spec", "secondaryControlPlaneLoadBalancer")
497+
if r.Spec.SecondaryControlPlaneLoadBalancer.TargetGroupIPType != nil {
498+
allErrs = append(allErrs, r.validateTargetGroupIPType(basePath.Child("targetGroupIPType"), r.Spec.SecondaryControlPlaneLoadBalancer.TargetGroupIPType, r.Spec.SecondaryControlPlaneLoadBalancer)...)
499+
}
500+
for i, listener := range r.Spec.SecondaryControlPlaneLoadBalancer.AdditionalListeners {
501+
if listener.TargetGroupIPType != nil {
502+
allErrs = append(allErrs, r.validateTargetGroupIPType(basePath.Child("additionalListeners").Index(i).Child("targetGroupIPType"), listener.TargetGroupIPType, r.Spec.SecondaryControlPlaneLoadBalancer)...)
503+
}
504+
}
470505
}
471506

472507
return allWarnings, allErrs
@@ -488,3 +523,20 @@ func (r *AWSCluster) validateIngressRules(path *field.Path, rules []IngressRule)
488523
}
489524
return allErrs
490525
}
526+
527+
// validateTargetGroupIPType validates that the target group IP type is compatible
528+
// with the load balancer type and VPC configuration.
529+
func (r *AWSCluster) validateTargetGroupIPType(path *field.Path, targetGroupIPType *TargetGroupIPType, lbSpec *AWSLoadBalancerSpec) field.ErrorList {
530+
var allErrs field.ErrorList
531+
532+
if targetGroupIPType != nil {
533+
if lbSpec.LoadBalancerType == LoadBalancerTypeClassic {
534+
allErrs = append(allErrs, field.Invalid(path, targetGroupIPType, "targetGroupIPType cannot be used with classic load balancer types"))
535+
}
536+
if TargetGroupIPTypeIPv6.Equals(targetGroupIPType) && !r.Spec.NetworkSpec.VPC.IsIPv6Enabled() {
537+
allErrs = append(allErrs, field.Invalid(path, targetGroupIPType, "targetGroupIPType IPv6 requires IPv6 to be enabled on the VPC. Set spec.network.vpc.ipv6 to enable IPv6"))
538+
}
539+
}
540+
541+
return allErrs
542+
}

api/v1beta2/awscluster_webhook_test.go

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -890,6 +890,111 @@ func TestAWSClusterValidateCreate(t *testing.T) {
890890
},
891891
wantErr: true,
892892
},
893+
{
894+
name: "rejects targetGroupIPType when LoadBalancer is disabled",
895+
cluster: &AWSCluster{
896+
Spec: AWSClusterSpec{
897+
ControlPlaneLoadBalancer: &AWSLoadBalancerSpec{
898+
TargetGroupIPType: &TargetGroupIPTypeIPv4,
899+
LoadBalancerType: LoadBalancerTypeDisabled,
900+
},
901+
},
902+
},
903+
wantErr: true,
904+
},
905+
{
906+
name: "rejects targetGroupIPType with Classic Load Balancer",
907+
cluster: &AWSCluster{
908+
Spec: AWSClusterSpec{
909+
ControlPlaneLoadBalancer: &AWSLoadBalancerSpec{
910+
LoadBalancerType: LoadBalancerTypeClassic,
911+
TargetGroupIPType: &TargetGroupIPTypeIPv4,
912+
},
913+
},
914+
},
915+
wantErr: true,
916+
},
917+
{
918+
name: "accepts targetGroupIPType IPv4 with Network Load Balancer",
919+
cluster: &AWSCluster{
920+
Spec: AWSClusterSpec{
921+
ControlPlaneLoadBalancer: &AWSLoadBalancerSpec{
922+
LoadBalancerType: LoadBalancerTypeNLB,
923+
TargetGroupIPType: &TargetGroupIPTypeIPv4,
924+
},
925+
},
926+
},
927+
wantErr: false,
928+
},
929+
{
930+
name: "rejects targetGroupIPType IPv6 with VPC IPv6 disabled",
931+
cluster: &AWSCluster{
932+
Spec: AWSClusterSpec{
933+
ControlPlaneLoadBalancer: &AWSLoadBalancerSpec{
934+
LoadBalancerType: LoadBalancerTypeNLB,
935+
TargetGroupIPType: &TargetGroupIPTypeIPv6,
936+
},
937+
NetworkSpec: NetworkSpec{},
938+
},
939+
},
940+
wantErr: true,
941+
},
942+
{
943+
name: "accepts targetGroupIPType IPv6 with NLB and VPC IPv6 enabled",
944+
cluster: &AWSCluster{
945+
Spec: AWSClusterSpec{
946+
ControlPlaneLoadBalancer: &AWSLoadBalancerSpec{
947+
LoadBalancerType: LoadBalancerTypeNLB,
948+
TargetGroupIPType: &TargetGroupIPTypeIPv6,
949+
},
950+
NetworkSpec: NetworkSpec{
951+
VPC: VPCSpec{
952+
IPv6: &IPv6{
953+
CidrBlock: "2001:db8::/56",
954+
},
955+
},
956+
},
957+
},
958+
},
959+
wantErr: false,
960+
},
961+
{
962+
name: "rejects additionalListener targetGroupIPType with Classic Load Balancer",
963+
cluster: &AWSCluster{
964+
Spec: AWSClusterSpec{
965+
ControlPlaneLoadBalancer: &AWSLoadBalancerSpec{
966+
LoadBalancerType: LoadBalancerTypeClassic,
967+
AdditionalListeners: []AdditionalListenerSpec{
968+
{
969+
Port: 22623,
970+
Protocol: ELBProtocolTCP,
971+
TargetGroupIPType: &TargetGroupIPTypeIPv4,
972+
},
973+
},
974+
},
975+
},
976+
},
977+
wantErr: true,
978+
},
979+
{
980+
name: "rejects additionalListener targetGroupIPType IPv6 with VPC IPv6 disabled",
981+
cluster: &AWSCluster{
982+
Spec: AWSClusterSpec{
983+
ControlPlaneLoadBalancer: &AWSLoadBalancerSpec{
984+
LoadBalancerType: LoadBalancerTypeNLB,
985+
AdditionalListeners: []AdditionalListenerSpec{
986+
{
987+
Port: 8443,
988+
Protocol: ELBProtocolTCP,
989+
TargetGroupIPType: &TargetGroupIPTypeIPv6,
990+
},
991+
},
992+
},
993+
NetworkSpec: NetworkSpec{},
994+
},
995+
},
996+
wantErr: true,
997+
},
893998
}
894999
for _, tt := range tests {
8951000
t.Run(tt.name, func(t *testing.T) {
@@ -1348,6 +1453,53 @@ func TestAWSClusterValidateUpdate(t *testing.T) {
13481453
},
13491454
wantErr: true,
13501455
},
1456+
{
1457+
name: "should failed if controlPlaneLoadBalancer targetGroupIPType is changed",
1458+
oldCluster: &AWSCluster{
1459+
Spec: AWSClusterSpec{
1460+
ControlPlaneLoadBalancer: &AWSLoadBalancerSpec{
1461+
LoadBalancerType: LoadBalancerTypeNLB,
1462+
TargetGroupIPType: &TargetGroupIPTypeIPv4,
1463+
},
1464+
},
1465+
},
1466+
newCluster: &AWSCluster{
1467+
Spec: AWSClusterSpec{
1468+
ControlPlaneLoadBalancer: &AWSLoadBalancerSpec{
1469+
LoadBalancerType: LoadBalancerTypeNLB,
1470+
TargetGroupIPType: &TargetGroupIPTypeIPv6,
1471+
},
1472+
NetworkSpec: NetworkSpec{
1473+
VPC: VPCSpec{
1474+
IPv6: &IPv6{
1475+
CidrBlock: "2001:db8::/56",
1476+
},
1477+
},
1478+
},
1479+
},
1480+
},
1481+
wantErr: true,
1482+
},
1483+
{
1484+
name: "should pass controlPlaneLoadBalancer targetGroupIPType is the same on update",
1485+
oldCluster: &AWSCluster{
1486+
Spec: AWSClusterSpec{
1487+
ControlPlaneLoadBalancer: &AWSLoadBalancerSpec{
1488+
LoadBalancerType: LoadBalancerTypeNLB,
1489+
TargetGroupIPType: &TargetGroupIPTypeIPv4,
1490+
},
1491+
},
1492+
},
1493+
newCluster: &AWSCluster{
1494+
Spec: AWSClusterSpec{
1495+
ControlPlaneLoadBalancer: &AWSLoadBalancerSpec{
1496+
LoadBalancerType: LoadBalancerTypeNLB,
1497+
TargetGroupIPType: &TargetGroupIPTypeIPv4,
1498+
},
1499+
},
1500+
},
1501+
wantErr: false,
1502+
},
13511503
}
13521504
for _, tt := range tests {
13531505
t.Run(tt.name, func(t *testing.T) {

api/v1beta2/network_types.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,53 @@ var (
218218
TargetGroupAttributeUnhealthyDrainingIntervalSeconds = "target_health_state.unhealthy.draining_interval_seconds"
219219
)
220220

221+
// TargetGroupIPType defines the IP address type for target groups.
222+
type TargetGroupIPType string
223+
224+
var (
225+
// TargetGroupIPTypeIPv4 defines the IPv4 address type for target groups.
226+
TargetGroupIPTypeIPv4 = TargetGroupIPType("ipv4")
227+
228+
// TargetGroupIPTypeIPv6 defines the IPv6 address type for target groups.
229+
TargetGroupIPTypeIPv6 = TargetGroupIPType("ipv6")
230+
)
231+
232+
func (t TargetGroupIPType) String() string {
233+
return string(t)
234+
}
235+
236+
// Equals returns true if two TargetGroupIPType are equal.
237+
func (t TargetGroupIPType) Equals(other *TargetGroupIPType) bool {
238+
if other == nil {
239+
return false
240+
}
241+
242+
return t == *other
243+
}
244+
245+
// LoadBalancerIPAddressType defines the IP address type for load balancers.
246+
type LoadBalancerIPAddressType string
247+
248+
// Enum values for LoadBalancerIPAddressType
249+
const (
250+
LoadBalancerIPAddressTypeIPv4 = LoadBalancerIPAddressType("ipv4")
251+
LoadBalancerIPAddressTypeDualstack = LoadBalancerIPAddressType("dualstack")
252+
LoadBalancerIPAddressTypeDualstackWithoutPublicIPv4 = LoadBalancerIPAddressType("dualstack-without-public-ipv4")
253+
)
254+
255+
func (t LoadBalancerIPAddressType) String() string {
256+
return string(t)
257+
}
258+
259+
// Equals returns true if two LoadBalancerIPAddressType are equal.
260+
func (t LoadBalancerIPAddressType) Equals(other *LoadBalancerIPAddressType) bool {
261+
if other == nil {
262+
return false
263+
}
264+
265+
return t == *other
266+
}
267+
221268
// LoadBalancerAttribute defines a set of attributes for a V2 load balancer.
222269
type LoadBalancerAttribute string
223270

@@ -243,6 +290,8 @@ type TargetGroupSpec struct {
243290
VpcID string `json:"vpcId"`
244291
// HealthCheck is the elb health check associated with the load balancer.
245292
HealthCheck *TargetGroupHealthCheck `json:"targetGroupHealthCheck,omitempty"`
293+
// IPType is the IP address type for the target group.
294+
IPType TargetGroupIPType `json:"ipType,omitempty"`
246295
}
247296

248297
// Listener defines an AWS network load balancer listener.
@@ -298,6 +347,10 @@ type LoadBalancer struct {
298347
// LoadBalancerType sets the type for a load balancer. The default type is classic.
299348
// +kubebuilder:validation:Enum:=classic;elb;alb;nlb
300349
LoadBalancerType LoadBalancerType `json:"loadBalancerType,omitempty"`
350+
351+
// LoadBalancerIPAddressType specifies the IP address type for the load balancer.
352+
// +kubebuilder:validation:Enum:=ipv4;dualstack;dualstack-without-public-ipv4
353+
LoadBalancerIPAddressType LoadBalancerIPAddressType `json:"loadBalancerIPAddressType,omitempty"`
301354
}
302355

303356
// IsUnmanaged returns true if the Classic ELB is unmanaged.

api/v1beta2/zz_generated.deepcopy.go

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

0 commit comments

Comments
 (0)