Skip to content

Commit 854da87

Browse files
authored
Support for setting MarketType/InstanceMarketOptions for use with "capacity-block" (#8207)
* Support for setting MarketType/InstanceMarketOptions for use with "capacity-block" Signed-off-by: Davanum Srinivas <[email protected]> * set CapacityType field of AWS::EKS::Nodegroup correctly Signed-off-by: Davanum Srinivas <[email protected]> * MNG capacity block needs a single instance type in launch template Signed-off-by: Davanum Srinivas <[email protected]> --------- Signed-off-by: Davanum Srinivas <[email protected]>
1 parent 7e25a4c commit 854da87

File tree

9 files changed

+327
-2
lines changed

9 files changed

+327
-2
lines changed

pkg/apis/eksctl.io/v1alpha5/assets/schema.json

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1194,6 +1194,21 @@
11941194
"x-intellij-html-description": "holds any arbitrary JSON/YAML documents, such as extra config parameters or IAM policies",
11951195
"default": "{}"
11961196
},
1197+
"InstanceMarketOptions": {
1198+
"properties": {
1199+
"marketType": {
1200+
"type": "string",
1201+
"description": "specifies the market type for the instances",
1202+
"x-intellij-html-description": "specifies the market type for the instances"
1203+
}
1204+
},
1205+
"preferredOrder": [
1206+
"marketType"
1207+
],
1208+
"additionalProperties": false,
1209+
"description": "describes the market (purchasing) option for the instances",
1210+
"x-intellij-html-description": "describes the market (purchasing) option for the instances"
1211+
},
11971212
"InstanceSelector": {
11981213
"properties": {
11991214
"cpuArchitecture": {
@@ -1419,6 +1434,11 @@
14191434
"iam": {
14201435
"$ref": "#/definitions/NodeGroupIAM"
14211436
},
1437+
"instanceMarketOptions": {
1438+
"$ref": "#/definitions/InstanceMarketOptions",
1439+
"description": "describes the market (purchasing) option for the instances",
1440+
"x-intellij-html-description": "describes the market (purchasing) option for the instances"
1441+
},
14221442
"instanceName": {
14231443
"type": "string"
14241444
},
@@ -1629,6 +1649,7 @@
16291649
"bottlerocket",
16301650
"enableDetailedMonitoring",
16311651
"capacityReservation",
1652+
"instanceMarketOptions",
16321653
"outpostARN",
16331654
"instanceTypes",
16341655
"spot",
@@ -1792,6 +1813,11 @@
17921813
"iam": {
17931814
"$ref": "#/definitions/NodeGroupIAM"
17941815
},
1816+
"instanceMarketOptions": {
1817+
"$ref": "#/definitions/InstanceMarketOptions",
1818+
"description": "describes the market (purchasing) option for the instances",
1819+
"x-intellij-html-description": "describes the market (purchasing) option for the instances"
1820+
},
17951821
"instanceName": {
17961822
"type": "string"
17971823
},
@@ -1999,6 +2025,7 @@
19992025
"bottlerocket",
20002026
"enableDetailedMonitoring",
20012027
"capacityReservation",
2028+
"instanceMarketOptions",
20022029
"outpostARN",
20032030
"instancesDistribution",
20042031
"asgMetricsCollection",

pkg/apis/eksctl.io/v1alpha5/types.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1753,6 +1753,10 @@ type NodeGroupBase struct {
17531753
// CapacityReservation defines reservation policy for a nodegroup
17541754
CapacityReservation *CapacityReservation `json:"capacityReservation,omitempty"`
17551755

1756+
// InstanceMarketOptions describes the market (purchasing) option for the instances
1757+
// +optional
1758+
InstanceMarketOptions *InstanceMarketOptions `json:"instanceMarketOptions,omitempty"`
1759+
17561760
// OutpostARN specifies the Outpost ARN in which the nodegroup should be created.
17571761
// +optional
17581762
OutpostARN string `json:"outpostARN,omitempty"`
@@ -1773,6 +1777,12 @@ type CapacityReservationTarget struct {
17731777
CapacityReservationResourceGroupARN *string `json:"capacityReservationResourceGroupARN,omitempty"`
17741778
}
17751779

1780+
// InstanceMarketOptions describes the market (purchasing) option for the instances
1781+
type InstanceMarketOptions struct {
1782+
// MarketType specifies the market type for the instances
1783+
MarketType *string `json:"marketType,omitempty"`
1784+
}
1785+
17761786
// Placement specifies placement group information
17771787
type Placement struct {
17781788
GroupName string `json:"groupName,omitempty"`

pkg/apis/eksctl.io/v1alpha5/validation.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -768,6 +768,18 @@ func validateNodeGroupBase(np NodePool, path string, controlPlaneOnOutposts bool
768768
return errors.New("only one of CapacityReservationID or CapacityReservationResourceGroupARN may be specified at a time")
769769
}
770770
}
771+
772+
if ng.InstanceMarketOptions != nil {
773+
if ng.InstanceMarketOptions.MarketType != nil {
774+
if *ng.InstanceMarketOptions.MarketType != "capacity-block" {
775+
return fmt.Errorf(`only accepted value is "capacity-block"; got "%s"`, *ng.InstanceMarketOptions.MarketType)
776+
}
777+
}
778+
}
779+
} else {
780+
if ng.InstanceMarketOptions != nil {
781+
return errors.New("instanceMarketOptions cannot be set without capacityReservation")
782+
}
771783
}
772784

773785
return nil

pkg/apis/eksctl.io/v1alpha5/validation_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2602,6 +2602,52 @@ var _ = Describe("ClusterConfig validation", func() {
26022602
})
26032603
})
26042604

2605+
Describe("Instance Market Options validation", func() {
2606+
var (
2607+
cfg *api.ClusterConfig
2608+
ng *api.NodeGroup
2609+
)
2610+
2611+
BeforeEach(func() {
2612+
cfg = api.NewClusterConfig()
2613+
ng = cfg.NewNodeGroup()
2614+
ng.Name = "ng"
2615+
})
2616+
2617+
When("InstanceMarketOptions is set", func() {
2618+
When("it is set to 'capacity-block'", func() {
2619+
It("does not fail", func() {
2620+
ng.CapacityReservation = &api.CapacityReservation{
2621+
CapacityReservationTarget: &api.CapacityReservationTarget{
2622+
CapacityReservationID: aws.String("id"),
2623+
},
2624+
}
2625+
ng.InstanceMarketOptions = &api.InstanceMarketOptions{MarketType: aws.String("capacity-block")}
2626+
Expect(api.ValidateNodeGroup(0, ng, cfg)).To(Succeed())
2627+
})
2628+
})
2629+
2630+
When("it is set to 'capacity-block' but Capacity Reservation not set", func() {
2631+
It("does fail", func() {
2632+
ng.InstanceMarketOptions = &api.InstanceMarketOptions{MarketType: aws.String("capacity-block")}
2633+
Expect(api.ValidateNodeGroup(0, ng, cfg)).To(MatchError(ContainSubstring(`instanceMarketOptions cannot be set without capacityReservation`)))
2634+
})
2635+
})
2636+
2637+
When("it is set to 'foobar'", func() {
2638+
It("does fail", func() {
2639+
ng.CapacityReservation = &api.CapacityReservation{
2640+
CapacityReservationTarget: &api.CapacityReservationTarget{
2641+
CapacityReservationID: aws.String("id"),
2642+
},
2643+
}
2644+
ng.InstanceMarketOptions = &api.InstanceMarketOptions{MarketType: aws.String("foobar")}
2645+
Expect(api.ValidateNodeGroup(0, ng, cfg)).To(MatchError(ContainSubstring(`only accepted value is "capacity-block"; got "foobar"`)))
2646+
})
2647+
})
2648+
})
2649+
})
2650+
26052651
DescribeTable("ToPodIdentityAssociationID", func(piaARN, expectedID, expectedErr string) {
26062652
piaID, err := api.ToPodIdentityAssociationID(piaARN)
26072653
if expectedErr != "" {

pkg/cfn/builder/managed_launch_template.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ func (m *ManagedNodeGroupResourceSet) makeLaunchTemplateData(ctx context.Context
3333
CapacityReservationResourceGroupArn: valueOrNil(mng.CapacityReservation.CapacityReservationTarget.CapacityReservationResourceGroupARN),
3434
}
3535
}
36+
if mng.InstanceMarketOptions != nil {
37+
launchTemplateData.InstanceMarketOptions = &gfnec2.LaunchTemplate_InstanceMarketOptions{
38+
MarketType: valueOrNil(mng.InstanceMarketOptions.MarketType),
39+
}
40+
}
3641
}
3742

3843
userData, err := m.bootstrapper.UserData()

pkg/cfn/builder/managed_launch_template_test.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,24 @@ API_SERVER_URL=https://test.com
266266
resourcesFilename: "spot.json",
267267
}),
268268

269+
Entry("With Capacity Block instances", &mngCase{
270+
ng: &api.ManagedNodeGroup{
271+
NodeGroupBase: &api.NodeGroupBase{
272+
Name: "cb-test",
273+
CapacityReservation: &api.CapacityReservation{
274+
CapacityReservationTarget: &api.CapacityReservationTarget{
275+
CapacityReservationID: aws.String("res-id"),
276+
},
277+
},
278+
InstanceMarketOptions: &api.InstanceMarketOptions{
279+
MarketType: aws.String("capacity-block"),
280+
},
281+
},
282+
InstanceTypes: []string{"p5en.48xlarge"},
283+
},
284+
resourcesFilename: "capacity_block.json",
285+
}),
286+
269287
Entry("With node repair enabled", &mngCase{
270288
ng: &api.ManagedNodeGroup{
271289
NodeGroupBase: &api.NodeGroupBase{

pkg/cfn/builder/managed_nodegroup.go

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,14 @@ func (m *ManagedNodeGroupResourceSet) AddAllResources(ctx context.Context) error
130130
managedResource.CapacityType = gfnt.NewString("SPOT")
131131
}
132132

133+
isCapacityBlockEnabled := false
134+
if m.nodeGroup.InstanceMarketOptions != nil &&
135+
m.nodeGroup.InstanceMarketOptions.MarketType != nil &&
136+
*m.nodeGroup.InstanceMarketOptions.MarketType == "capacity-block" {
137+
isCapacityBlockEnabled = true
138+
managedResource.CapacityType = gfnt.NewString("CAPACITY_BLOCK")
139+
}
140+
133141
if m.nodeGroup.ReleaseVersion != "" {
134142
managedResource.ReleaseVersion = gfnt.NewString(m.nodeGroup.ReleaseVersion)
135143
}
@@ -167,7 +175,14 @@ func (m *ManagedNodeGroupResourceSet) AddAllResources(ctx context.Context) error
167175
}
168176

169177
if launchTemplateData.InstanceType == "" {
170-
managedResource.InstanceTypes = gfnt.NewStringSlice(instanceTypes...)
178+
if isCapacityBlockEnabled {
179+
if len(instanceTypes) > 1 {
180+
return errors.New("when using capacity type CAPACITY_BLOCK please specify only one instance type")
181+
}
182+
launchTemplateData.InstanceType = ec2types.InstanceType(instanceTypes[0])
183+
} else {
184+
managedResource.InstanceTypes = gfnt.NewStringSlice(instanceTypes...)
185+
}
171186
}
172187
} else {
173188
launchTemplateData, err := m.makeLaunchTemplateData(ctx)
@@ -177,7 +192,15 @@ func (m *ManagedNodeGroupResourceSet) AddAllResources(ctx context.Context) error
177192
if launchTemplateData.ImageId == nil {
178193
managedResource.AmiType = makeAMIType()
179194
}
180-
managedResource.InstanceTypes = gfnt.NewStringSlice(instanceTypes...)
195+
196+
if isCapacityBlockEnabled {
197+
if len(instanceTypes) > 1 {
198+
return errors.New("cannot specify multiple instance types when using capacity block")
199+
}
200+
launchTemplateData.InstanceType = gfnt.NewString(instanceTypes[0])
201+
} else {
202+
managedResource.InstanceTypes = gfnt.NewStringSlice(instanceTypes...)
203+
}
181204

182205
ltRef := m.newResource("LaunchTemplate", &gfnec2.LaunchTemplate{
183206
LaunchTemplateName: gfnt.MakeFnSubString(fmt.Sprintf("${%s}", gfnt.StackName)),

pkg/cfn/builder/nodegroup.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,11 @@ func newLaunchTemplateData(ctx context.Context, n *NodeGroupResourceSet) (*gfnec
470470
CapacityReservationResourceGroupArn: valueOrNil(ng.CapacityReservation.CapacityReservationTarget.CapacityReservationResourceGroupARN),
471471
}
472472
}
473+
if ng.InstanceMarketOptions != nil {
474+
launchTemplateData.InstanceMarketOptions = &gfnec2.LaunchTemplate_InstanceMarketOptions{
475+
MarketType: valueOrNil(ng.InstanceMarketOptions.MarketType),
476+
}
477+
}
473478
}
474479

475480
if err := buildNetworkInterfaces(ctx, launchTemplateData, ng.InstanceTypeList(), api.IsEnabled(ng.EFAEnabled), n.securityGroups, n.ec2API); err != nil {

0 commit comments

Comments
 (0)