Skip to content

Commit

Permalink
feat: Set MinInstancesInService via CFN parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
akash1810 committed Oct 17, 2024
1 parent adbfb3b commit 0f62f2a
Show file tree
Hide file tree
Showing 8 changed files with 461 additions and 129 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ object AmiCloudFormationParameter
}

val amiLookupFn = getLatestAmi(pkg, target, reporter, resources.lookup)
val minInServiceParameterMap = getMinInServiceTagRequirements(pkg, target)

val unresolvedParameters = new CloudFormationParameters(
target = target,
Expand All @@ -61,7 +62,8 @@ object AmiCloudFormationParameter
latestImage = amiLookupFn,

// Not expecting any user parameters in this deployment type
userParameters = Map.empty
userParameters = Map.empty,
minInServiceParameterMap = minInServiceParameterMap
)

List(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package magenta.deployment_type

import magenta.Loggable
import magenta.{Loggable, Strategy}
import magenta.Strategy.{Dangerous, MostlyHarmless}
import magenta.artifact.S3Path
import magenta.deployment_type.CloudFormationDeploymentTypeParameters._
Expand Down Expand Up @@ -148,12 +148,15 @@ class CloudFormation(tagger: BuildTags)

val changeSetName = s"${target.stack.name}-${new DateTime().getMillis}"

val minInServiceParameterMap = getMinInServiceTagRequirements(pkg, target)

val unresolvedParameters = new CloudFormationParameters(
target,
stackTags,
userParams,
amiParameterMap,
amiLookupFn
amiLookupFn,
minInServiceParameterMap
)

val createNewStack = createStackIfAbsent(pkg, target, reporter)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package magenta.deployment_type

import magenta.deployment_type.CloudFormationDeploymentTypeParameters.CfnParam
import magenta.tasks.ASG
import magenta.tasks.ASG.TagMatch
import magenta.tasks.UpdateCloudFormationTask.{
CloudFormationStackLookupStrategy,
LookupByName,
LookupByTags
}
import magenta.{DeployReporter, DeployTarget, DeploymentPackage, Lookup}
import software.amazon.awssdk.services.autoscaling.AutoScalingClient

import java.time.Duration
import java.time.Duration.ofMinutes
Expand Down Expand Up @@ -104,6 +108,24 @@ trait CloudFormationDeploymentTypeParameters {
""".stripMargin
).default(false)

val minInstancesInServiceParameters = Param[Map[CfnParam, TagCriteria]](
"minInstancesInServiceParameters",
optional = true,
documentation = """Mapping between a CloudFormation parameter controlling the MinInstancesInService property of an ASG UpdatePolicy and the ASG.
|
|For example:
|```
| minInstancesInServiceParameters:
| MinInstancesInServiceForApi:
| App: my-api
| MinInstancesInServiceForFrontend:
| App: my-frontend
|```
|This instructs Riff-Raff that the CFN parameter `MinInstancesInServiceForApi` relates to an ASG tagged `App=my-api`.
|Additional requirements of `Stack=<STACK BEING DEPLOYED>`, `Stage=<STAGE BEING DEPLOYED>` and `aws:cloudformation:stack-name=<CFN STACK BEING DEPLOYED>` are automatically added.
""".stripMargin
)

val secondsToWaitForChangeSetCreation: Param[Duration] = Param
.waitingSecondsFor(
"secondsToWaitForChangeSetCreation",
Expand Down Expand Up @@ -191,4 +213,25 @@ trait CloudFormationDeploymentTypeParameters {
}
}
}

def getMinInServiceTagRequirements(
pkg: DeploymentPackage,
target: DeployTarget
): Map[CfnParam, List[TagMatch]] = {
minInstancesInServiceParameters.get(pkg) match {
case Some(params) =>
val stackStageTags = List(
TagMatch("Stack", target.stack.name),
TagMatch("Stage", target.parameters.stage.name)
)
params.map({ case (cfnParam, tagRequirements) =>
cfnParam -> {
tagRequirements
.map({ case (key, value) => TagMatch(key, value) })
.toList ++ stackStageTags
}
})
case _ => Map.empty
}
}
}
54 changes: 54 additions & 0 deletions magenta-lib/src/main/scala/magenta/tasks/AWS.scala
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,60 @@ object ASG {
)
}

def getMinInstancesInService(
tagRequirements: List[TagMatch],
client: AutoScalingClient,
reporter: DeployReporter
): Int = {
groupWithTags(tagRequirements, client, reporter) match {
case Some(asg) =>
(asg.desiredCapacity, asg.maxSize) match {
case (desired, max) =>
val seventyFivePercent: Int = (max * 0.75).toInt
val minInstancesInService = Math.min(
seventyFivePercent,
desired
)

if (2 * desired <= max) {
reporter.info(
s"""
|Deploying new instances all at once.
|
|Max=$max. Desired=$desired.
|Setting MinInstancesInService=$minInstancesInService.
|""".stripMargin
)
} else if (minInstancesInService < desired) {
reporter.warning(
s"""
|Deploying new instances slower than usual.
|
|Max=$max. Desired=$desired.
|Setting MinInstancesInService=$minInstancesInService.
|This is 75% of max capacity, and less than desired capacity, meaning availability will be impacted.
|""".stripMargin
)
} else {
reporter.warning(
s"""
|Deploying new instances slower than usual.
|
|Max=$max. Desired=$desired.
|Setting MinInstancesInService=$minInstancesInService.
|""".stripMargin
)
}

minInstancesInService
}
case _ =>
reporter.fail(
s"No autoscaling group found with tags $tagRequirements. Creating a new stack? Initially choose the ${Strategy.Dangerous} strategy."
)
}
}

def groupWithTags(
tagRequirements: List[TagRequirement],
client: AutoScalingClient,
Expand Down
Loading

0 comments on commit 0f62f2a

Please sign in to comment.