Skip to content

Commit e04437e

Browse files
committed
Add support for multiple instance types in AWSManagedMachinePool
1 parent 1b94af9 commit e04437e

File tree

8 files changed

+116
-2
lines changed

8 files changed

+116
-2
lines changed

config/crd/bases/infrastructure.cluster.x-k8s.io_awsmanagedmachinepools.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -933,6 +933,16 @@ spec:
933933
instanceType:
934934
description: InstanceType specifies the AWS instance type
935935
type: string
936+
instanceTypes:
937+
description: |-
938+
InstanceTypes specifies a list of AWS instance types for the node group.
939+
When specified, this allows using multiple instance types which enhances
940+
the availability of Spot instances.
941+
At most one of InstanceType or InstanceTypes may be specified.
942+
See https://docs.aws.amazon.com/eks/latest/userguide/managed-node-groups.html#managed-node-group-capacity-types
943+
items:
944+
type: string
945+
type: array
936946
labels:
937947
additionalProperties:
938948
type: string

exp/api/v1beta1/conversion.go

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,14 @@ func (src *AWSManagedMachinePool) ConvertTo(dstRaw conversion.Hub) error {
149149
dst.Spec.RolePath = restored.Spec.RolePath
150150
dst.Spec.RolePermissionsBoundary = restored.Spec.RolePermissionsBoundary
151151

152+
// Restore InstanceType and InstanceTypes from the hub version
153+
// v1beta1 doesn't have InstanceTypes, but v1beta2 does
154+
// We preserve the exact state through the annotation-based conversion data
155+
// This includes cases where both fields are set (even though webhook would reject it)
156+
// to ensure fuzzy conversion tests pass
157+
dst.Spec.InstanceType = restored.Spec.InstanceType
158+
dst.Spec.InstanceTypes = restored.Spec.InstanceTypes
159+
152160
return nil
153161
}
154162

@@ -160,12 +168,26 @@ func (r *AWSManagedMachinePool) ConvertFrom(srcRaw conversion.Hub) error {
160168
return err
161169
}
162170

171+
// Preserve v1beta2 data through annotation
172+
// This ensures fuzzy conversion tests pass
163173
return utilconversion.MarshalData(src, r)
164174
}
165175

166176
// Convert_v1beta2_AWSManagedMachinePoolSpec_To_v1beta1_AWSManagedMachinePoolSpec is a conversion function.
167177
func Convert_v1beta2_AWSManagedMachinePoolSpec_To_v1beta1_AWSManagedMachinePoolSpec(in *expinfrav1.AWSManagedMachinePoolSpec, out *AWSManagedMachinePoolSpec, s apiconversion.Scope) error {
168-
return autoConvert_v1beta2_AWSManagedMachinePoolSpec_To_v1beta1_AWSManagedMachinePoolSpec(in, out, s)
178+
if err := autoConvert_v1beta2_AWSManagedMachinePoolSpec_To_v1beta1_AWSManagedMachinePoolSpec(in, out, s); err != nil {
179+
return err
180+
}
181+
182+
// Convert v1beta2 InstanceTypes or InstanceType to v1beta1 InstanceType
183+
// Prefer InstanceTypes if set (use first element), otherwise use InstanceType
184+
if len(in.InstanceTypes) > 0 {
185+
out.InstanceType = &in.InstanceTypes[0]
186+
} else if in.InstanceType != nil {
187+
out.InstanceType = in.InstanceType
188+
}
189+
190+
return nil
169191
}
170192

171193
func Convert_v1beta2_AWSMachinePoolStatus_To_v1beta1_AWSMachinePoolStatus(in *expinfrav1.AWSMachinePoolStatus, out *AWSMachinePoolStatus, s apiconversion.Scope) error {

exp/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.

exp/api/v1beta2/awsmanagedmachinepool_types.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,14 @@ type AWSManagedMachinePoolSpec struct {
180180
// +optional
181181
InstanceType *string `json:"instanceType,omitempty"`
182182

183+
// InstanceTypes specifies a list of AWS instance types for the node group.
184+
// When specified, this allows using multiple instance types which enhances
185+
// the availability of Spot instances.
186+
// At most one of InstanceType or InstanceTypes may be specified.
187+
// See https://docs.aws.amazon.com/eks/latest/userguide/managed-node-groups.html#managed-node-group-capacity-types
188+
// +optional
189+
InstanceTypes []string `json:"instanceTypes,omitempty"`
190+
183191
// Scaling specifies scaling for the ASG behind this pool
184192
// +optional
185193
Scaling *ManagedMachinePoolScaling `json:"scaling,omitempty"`

exp/api/v1beta2/awsmanagedmachinepool_webhook.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,20 @@ func (r *AWSManagedMachinePool) validateRemoteAccess() field.ErrorList {
124124
return allErrs
125125
}
126126

127+
func (r *AWSManagedMachinePool) validateInstanceTypes() field.ErrorList {
128+
var allErrs field.ErrorList
129+
130+
// InstanceType and InstanceTypes are mutually exclusive
131+
if r.Spec.InstanceType != nil && len(r.Spec.InstanceTypes) > 0 {
132+
allErrs = append(allErrs, field.Invalid(
133+
field.NewPath("spec", "InstanceTypes"),
134+
r.Spec.InstanceTypes,
135+
"cannot specify both instanceType and instanceTypes. Use instanceTypes for multiple instance types or instanceType for a single instance type"))
136+
}
137+
138+
return allErrs
139+
}
140+
127141
func (r *AWSManagedMachinePool) validateLaunchTemplate() field.ErrorList {
128142
var allErrs field.ErrorList
129143
if r.Spec.AWSLaunchTemplate == nil {
@@ -133,6 +147,9 @@ func (r *AWSManagedMachinePool) validateLaunchTemplate() field.ErrorList {
133147
if r.Spec.InstanceType != nil {
134148
allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "InstanceType"), r.Spec.InstanceType, "InstanceType cannot be specified when LaunchTemplate is specified"))
135149
}
150+
if len(r.Spec.InstanceTypes) > 0 {
151+
allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "InstanceTypes"), r.Spec.InstanceTypes, "InstanceTypes cannot be specified when LaunchTemplate is specified"))
152+
}
136153
if r.Spec.DiskSize != nil {
137154
allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "DiskSize"), r.Spec.DiskSize, "DiskSize cannot be specified when LaunchTemplate is specified"))
138155
}
@@ -171,6 +188,9 @@ func (*awsManagedMachinePoolWebhook) ValidateCreate(_ context.Context, obj runti
171188
if errs := r.validateNodegroupUpdateConfig(); len(errs) > 0 {
172189
allErrs = append(allErrs, errs...)
173190
}
191+
if errs := r.validateInstanceTypes(); len(errs) > 0 {
192+
allErrs = append(allErrs, errs...)
193+
}
174194
if errs := r.validateLaunchTemplate(); len(errs) > 0 {
175195
allErrs = append(allErrs, errs...)
176196
}
@@ -216,6 +236,9 @@ func (*awsManagedMachinePoolWebhook) ValidateUpdate(_ context.Context, oldObj, n
216236
if errs := r.validateNodegroupUpdateConfig(); len(errs) > 0 {
217237
allErrs = append(allErrs, errs...)
218238
}
239+
if errs := r.validateInstanceTypes(); len(errs) > 0 {
240+
allErrs = append(allErrs, errs...)
241+
}
219242
if errs := r.validateLaunchTemplate(); len(errs) > 0 {
220243
allErrs = append(allErrs, errs...)
221244
}
@@ -265,6 +288,8 @@ func (r *AWSManagedMachinePool) validateImmutable(old *AWSManagedMachinePool) fi
265288
appendErrorIfMutated(old.Spec.SubnetIDs, r.Spec.SubnetIDs, "subnetIDs")
266289
appendErrorIfSetAndMutated(old.Spec.RoleName, r.Spec.RoleName, "roleName")
267290
appendErrorIfMutated(old.Spec.DiskSize, r.Spec.DiskSize, "diskSize")
291+
appendErrorIfMutated(old.Spec.InstanceType, r.Spec.InstanceType, "instanceType")
292+
appendErrorIfMutated(old.Spec.InstanceTypes, r.Spec.InstanceTypes, "instanceTypes")
268293
appendErrorIfMutated(old.Spec.AMIType, r.Spec.AMIType, "amiType")
269294
appendErrorIfMutated(old.Spec.RemoteAccess, r.Spec.RemoteAccess, "remoteAccess")
270295
appendErrorIfSetAndMutated(old.Spec.CapacityType, r.Spec.CapacityType, "capacityType")

exp/api/v1beta2/zz_generated.deepcopy.go

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

pkg/cloud/services/eks/nodegroup.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,11 @@ func (s *NodegroupService) createNodegroup(ctx context.Context) (*ekstypes.Nodeg
225225
if managedPool.DiskSize != nil {
226226
input.DiskSize = managedPool.DiskSize
227227
}
228-
if managedPool.InstanceType != nil {
228+
// Support both InstanceTypes (preferred for multiple types) and InstanceType (for single type)
229+
// Webhook validation ensures they are mutually exclusive
230+
if len(managedPool.InstanceTypes) > 0 {
231+
input.InstanceTypes = managedPool.InstanceTypes
232+
} else if managedPool.InstanceType != nil {
229233
input.InstanceTypes = []string{aws.ToString(managedPool.InstanceType)}
230234
}
231235
if len(managedPool.Taints) > 0 {

test-awsmanagedmachinepool.yaml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
apiVersion: infrastructure.cluster.x-k8s.io/v1beta2
2+
kind: AWSManagedMachinePool
3+
metadata:
4+
name: test-pool-multiple-instance-types
5+
namespace: default
6+
spec:
7+
eksNodegroupName: test-nodegroup
8+
# Use instanceTypes (plural) to specify multiple instance types
9+
# This enhances availability when using Spot instances
10+
instanceTypes:
11+
- t3.medium
12+
- t3a.medium
13+
- t2.medium
14+
capacityType: spot
15+
scaling:
16+
minSize: 1
17+
maxSize: 10
18+
updateConfig:
19+
maxUnavailable: 1
20+
tags:
21+
usage: test
22+
---
23+
apiVersion: infrastructure.cluster.x-k8s.io/v1beta2
24+
kind: AWSManagedMachinePool
25+
metadata:
26+
name: test-pool-single-instance-type
27+
namespace: default
28+
spec:
29+
eksNodegroupName: test-nodegroup-single
30+
# Use instanceType (singular) for a single instance type (backward compatible)
31+
instanceType: t3.medium
32+
capacityType: on-demand
33+
scaling:
34+
minSize: 1
35+
maxSize: 5
36+
updateConfig:
37+
maxUnavailable: 1
38+
tags:
39+
usage: test

0 commit comments

Comments
 (0)