From d0d2ed85e020dbe6e1295bcfaaebc1ca77a37e9c Mon Sep 17 00:00:00 2001 From: yashdeep Date: Thu, 6 Nov 2025 14:31:53 +0530 Subject: [PATCH 1/2] feat(stack): add spot instance support via MixedInstancesPolicy Add support for EC2 Spot instances in the Auto Scaling Group using MixedInstancesPolicy. This allows users to configure spot instance allocation strategy and on-demand capacity settings. New environment variables: - SEMAPHORE_AGENT_ON_DEMAND_BASE_CAPACITY: Minimum on-demand instances (default: 0) - SEMAPHORE_AGENT_ON_DEMAND_PERCENTAGE_ABOVE_BASE: Percentage of on-demand instances above base capacity (default: 100) - SEMAPHORE_AGENT_SPOT_ALLOCATION_STRATEGY: Spot allocation strategy (optional, e.g., 'price-capacity-optimized') The Auto Scaling Group now uses MixedInstancesPolicy instead of a simple launch template, enabling spot instance support while maintaining backward compatibility with existing configurations. Signed-off-by: yashdeep --- lib/argument-store.js | 3 +++ lib/aws-semaphore-agent-stack.js | 33 ++++++++++++++++++++++++++------ 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/lib/argument-store.js b/lib/argument-store.js index 3cc4f79..c4b3118 100644 --- a/lib/argument-store.js +++ b/lib/argument-store.js @@ -43,6 +43,9 @@ class ArgumentStore { "SEMAPHORE_AGENT_OVERPROVISION_FACTOR": "0", "SEMAPHORE_AGENT_USE_IPV6": "false", "SEMAPHORE_AGENT_ALLOW_PUBLIC_SUBNET": "false", + "SEMAPHORE_AGENT_ON_DEMAND_BASE_CAPACITY": "0", + "SEMAPHORE_AGENT_ON_DEMAND_PERCENTAGE_ABOVE_BASE": "100", + "SEMAPHORE_AGENT_SPOT_ALLOCATION_STRATEGY": "", } static validOverprovisionStrategies = ["none", "number", "percentage"] diff --git a/lib/aws-semaphore-agent-stack.js b/lib/aws-semaphore-agent-stack.js index b18968f..e42179d 100644 --- a/lib/aws-semaphore-agent-stack.js +++ b/lib/aws-semaphore-agent-stack.js @@ -470,11 +470,11 @@ class AwsSemaphoreAgentStack extends Stack { createAutoScalingGroup(launchTemplate) { const cfnLaunchTemplate = launchTemplate.node.defaultChild - let autoScalingGroup = new CfnAutoScalingGroup(this, 'autoScalingGroup', { - launchTemplate: { - launchTemplateId: cfnLaunchTemplate.ref, - version: cfnLaunchTemplate.attrLatestVersionNumber - }, + const onDemandBaseCapacity = this.argumentStore.get("SEMAPHORE_AGENT_ON_DEMAND_BASE_CAPACITY"); + const onDemandPercentageAboveBase = this.argumentStore.get("SEMAPHORE_AGENT_ON_DEMAND_PERCENTAGE_ABOVE_BASE"); + const spotAllocationStrategy = this.argumentStore.get("SEMAPHORE_AGENT_SPOT_ALLOCATION_STRATEGY"); + + let autoScalingGroupProps = { minSize: this.argumentStore.get("SEMAPHORE_AGENT_ASG_MIN_SIZE"), maxSize: this.argumentStore.get("SEMAPHORE_AGENT_ASG_MAX_SIZE"), cooldown: "60", @@ -485,7 +485,28 @@ class AwsSemaphoreAgentStack extends Stack { propagateAtLaunch: true } ], - }); + }; + + const instancesDistribution = { + onDemandBaseCapacity: onDemandBaseCapacity, + onDemandPercentageAboveBaseCapacity: onDemandPercentageAboveBase + }; + + if (spotAllocationStrategy != "") { + instancesDistribution.spotAllocationStrategy = spotAllocationStrategy; + } + + autoScalingGroupProps.mixedInstancesPolicy = { + instancesDistribution: instancesDistribution, + launchTemplate: { + launchTemplateSpecification: { + launchTemplateId: cfnLaunchTemplate.ref, + version: cfnLaunchTemplate.attrLatestVersionNumber + } + } + }; + + let autoScalingGroup = new CfnAutoScalingGroup(this, 'autoScalingGroup', autoScalingGroupProps); const maxInstanceLifetime = this.argumentStore.getAsNumber("SEMAPHORE_AGENT_ASG_MAX_INSTANCE_LIFETIME"); if (maxInstanceLifetime !== 0) { From 26b0a756afd29053fd87d773e3eeda3539603be0 Mon Sep 17 00:00:00 2001 From: yashdeep Date: Thu, 6 Nov 2025 14:54:42 +0530 Subject: [PATCH 2/2] converting onDemandBaseCapacity and onDemandPercentageAboveBase to numbers Signed-off-by: yashdeep --- lib/aws-semaphore-agent-stack.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/aws-semaphore-agent-stack.js b/lib/aws-semaphore-agent-stack.js index e42179d..e7a0678 100644 --- a/lib/aws-semaphore-agent-stack.js +++ b/lib/aws-semaphore-agent-stack.js @@ -470,8 +470,8 @@ class AwsSemaphoreAgentStack extends Stack { createAutoScalingGroup(launchTemplate) { const cfnLaunchTemplate = launchTemplate.node.defaultChild - const onDemandBaseCapacity = this.argumentStore.get("SEMAPHORE_AGENT_ON_DEMAND_BASE_CAPACITY"); - const onDemandPercentageAboveBase = this.argumentStore.get("SEMAPHORE_AGENT_ON_DEMAND_PERCENTAGE_ABOVE_BASE"); + const onDemandBaseCapacity = this.argumentStore.getAsNumber("SEMAPHORE_AGENT_ON_DEMAND_BASE_CAPACITY"); + const onDemandPercentageAboveBase = this.argumentStore.getAsNumber("SEMAPHORE_AGENT_ON_DEMAND_PERCENTAGE_ABOVE_BASE"); const spotAllocationStrategy = this.argumentStore.get("SEMAPHORE_AGENT_SPOT_ALLOCATION_STRATEGY"); let autoScalingGroupProps = {