Skip to content

Commit

Permalink
feat(autoscaling): terminate instances instead of stopping them
Browse files Browse the repository at this point in the history
Previously, when instances were stopped, they remained in a suspended state, incurring unnecessary costs and resource usage. This commit introduces the functionality to terminate instances directly, ensuring efficient resource management and cost optimization.

When the autoscaling will be resume, instances will be recreated by
the autoscaling service.
  • Loading branch information
diodonfrost committed Jun 13, 2023
1 parent 938bd78 commit 1f6f6f6
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 29 deletions.
30 changes: 26 additions & 4 deletions examples/autoscaling-scheduler-terminate-instances/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ resource "aws_subnet" "this" {
cidr_block = "10.0.1.0/24"
}

resource "aws_launch_configuration" "this" {
name = "web_config"
resource "aws_launch_template" "this" {
name_prefix = "web_config"
image_id = data.aws_ami.ubuntu.id
instance_type = "t2.micro"
}
Expand All @@ -41,8 +41,19 @@ resource "aws_autoscaling_group" "scheduled" {
health_check_type = "EC2"
desired_capacity = 1
force_delete = true
launch_configuration = aws_launch_configuration.this.name
vpc_zone_identifier = [aws_subnet.this.id]
mixed_instances_policy {
instances_distribution {
on_demand_base_capacity = 0
on_demand_percentage_above_base_capacity = 25
spot_allocation_strategy = "capacity-optimized"
}
launch_template {
launch_template_specification {
launch_template_id = aws_launch_template.this.id
}
}
}

tag {
key = "tostop"
Expand All @@ -66,8 +77,19 @@ resource "aws_autoscaling_group" "not_scheduled" {
health_check_type = "EC2"
desired_capacity = 1
force_delete = true
launch_configuration = aws_launch_configuration.this.name
vpc_zone_identifier = [aws_subnet.this.id]
mixed_instances_policy {
instances_distribution {
on_demand_base_capacity = 0
on_demand_percentage_above_base_capacity = 25
spot_allocation_strategy = "capacity-optimized"
}
launch_template {
launch_template_specification {
launch_template_id = aws_launch_template.this.id
}
}
}

tag {
key = "tostop"
Expand Down
23 changes: 12 additions & 11 deletions main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -270,17 +270,18 @@ resource "aws_lambda_function" "this" {

environment {
variables = {
AWS_REGIONS = var.aws_regions == null ? data.aws_region.current.name : join(", ", var.aws_regions)
SCHEDULE_ACTION = var.schedule_action
TAG_KEY = local.scheduler_tag["key"]
TAG_VALUE = local.scheduler_tag["value"]
DOCUMENTDB_SCHEDULE = tostring(var.documentdb_schedule)
EC2_SCHEDULE = tostring(var.ec2_schedule)
ECS_SCHEDULE = tostring(var.ecs_schedule)
RDS_SCHEDULE = tostring(var.rds_schedule)
REDSHIFT_SCHEDULE = tostring(var.redshift_schedule)
AUTOSCALING_SCHEDULE = tostring(var.autoscaling_schedule)
CLOUDWATCH_ALARM_SCHEDULE = tostring(var.cloudwatch_alarm_schedule)
AWS_REGIONS = var.aws_regions == null ? data.aws_region.current.name : join(", ", var.aws_regions)
SCHEDULE_ACTION = var.schedule_action
TAG_KEY = local.scheduler_tag["key"]
TAG_VALUE = local.scheduler_tag["value"]
DOCUMENTDB_SCHEDULE = tostring(var.documentdb_schedule)
EC2_SCHEDULE = tostring(var.ec2_schedule)
ECS_SCHEDULE = tostring(var.ecs_schedule)
RDS_SCHEDULE = tostring(var.rds_schedule)
REDSHIFT_SCHEDULE = tostring(var.redshift_schedule)
AUTOSCALING_SCHEDULE = tostring(var.autoscaling_schedule)
AUTOSCALING_TERMINATE_INSTANCES = tostring(var.autoscaling_terminate_instances)
CLOUDWATCH_ALARM_SCHEDULE = tostring(var.cloudwatch_alarm_schedule)
}
}

Expand Down
14 changes: 10 additions & 4 deletions package/scheduler/autoscaling_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def __init__(self, region_name=None) -> None:
self.asg = boto3.client("autoscaling")
self.waiter = AwsWaiters(region_name=region_name)

def stop(self, aws_tags: List[Dict]) -> None:
def stop(self, aws_tags: List[Dict], terminate_instances=False) -> None:
"""Aws autoscaling suspend function.
Suspend autoscaling group and stop its instances
Expand All @@ -42,6 +42,8 @@ def stop(self, aws_tags: List[Dict]) -> None:
]
}
]
:param bool terminate_instances:
Terminate autoscaling instances if True
"""
tag_key = aws_tags[0]["Key"]
tag_value = "".join(aws_tags[0]["Values"])
Expand All @@ -55,11 +57,15 @@ def stop(self, aws_tags: List[Dict]) -> None:
except ClientError as exc:
ec2_exception("instance", asg_name, exc)

# Stop autoscaling instance
# Stop or Terminate autoscaling instances
for instance_id in instance_id_list:
try:
self.ec2.stop_instances(InstanceIds=[instance_id])
print(f"Stop autoscaling instances {instance_id}")
if terminate_instances:
self.ec2.terminate_instances(InstanceIds=[instance_id])
print(f"Terminate autoscaling instances {instance_id}")
else:
self.ec2.stop_instances(InstanceIds=[instance_id])
print(f"Stop autoscaling instances {instance_id}")
except ClientError as exc:
ec2_exception("autoscaling group", instance_id, exc)

Expand Down
29 changes: 19 additions & 10 deletions package/scheduler/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,27 @@ def lambda_handler(event, context):
schedule_action = os.getenv("SCHEDULE_ACTION")
aws_regions = os.getenv("AWS_REGIONS").replace(" ", "").split(",")
format_tags = [{"Key": os.getenv("TAG_KEY"), "Values": [os.getenv("TAG_VALUE")]}]

_strategy = {}
_strategy[AutoscalingScheduler] = os.getenv("AUTOSCALING_SCHEDULE")
_strategy[DocumentDBScheduler] = os.getenv("DOCUMENTDB_SCHEDULE")
_strategy[InstanceScheduler] = os.getenv("EC2_SCHEDULE")
_strategy[EcsScheduler] = os.getenv("ECS_SCHEDULE")
_strategy[RdsScheduler] = os.getenv("RDS_SCHEDULE")
_strategy[RedshiftScheduler] = os.getenv("REDSHIFT_SCHEDULE")
_strategy[CloudWatchAlarmScheduler] = os.getenv("CLOUDWATCH_ALARM_SCHEDULE")
autoscaling_terminate_instances = strtobool(
os.getenv("AUTOSCALING_TERMINATE_INSTANCES")
)

_strategy = {
AutoscalingScheduler: os.getenv("AUTOSCALING_SCHEDULE"),
DocumentDBScheduler: os.getenv("DOCUMENTDB_SCHEDULE"),
InstanceScheduler: os.getenv("EC2_SCHEDULE"),
EcsScheduler: os.getenv("ECS_SCHEDULE"),
RdsScheduler: os.getenv("RDS_SCHEDULE"),
RedshiftScheduler: os.getenv("REDSHIFT_SCHEDULE"),
CloudWatchAlarmScheduler: os.getenv("CLOUDWATCH_ALARM_SCHEDULE"),
}

for service, to_schedule in _strategy.items():
if strtobool(to_schedule):
for aws_region in aws_regions:
strategy = service(aws_region)
getattr(strategy, schedule_action)(aws_tags=format_tags)
if service == AutoscalingScheduler and autoscaling_terminate_instances:
getattr(strategy, schedule_action)(
aws_tags=format_tags, terminate_instances=True
)
else:
getattr(strategy, schedule_action)(aws_tags=format_tags)
6 changes: 6 additions & 0 deletions variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ variable "autoscaling_schedule" {
default = false
}

variable "autoscaling_terminate_instances" {
description = "Terminate instances when autoscaling group is scheduled to stop"
type = bool
default = false
}

variable "ec2_schedule" {
description = "Enable scheduling on ec2 resources"
type = any
Expand Down

0 comments on commit 1f6f6f6

Please sign in to comment.