From 554bc25dd6e0ba56b54da70c4b75c523d68a524a Mon Sep 17 00:00:00 2001 From: revarawat Date: Mon, 27 Nov 2023 23:25:03 -0500 Subject: [PATCH] adds schedule scaling policy for ecs --- python/ecs-schedulescaling/README.md | 18 +++++ python/ecs-schedulescaling/app.py | 10 +++ python/ecs-schedulescaling/cdk.json | 65 ++++++++++++++++ python/ecs-schedulescaling/requirements.txt | 2 + .../schedulescaling/__init__.py | 0 .../schedulescaling/schedulescaling_stack.py | 74 +++++++++++++++++++ 6 files changed, 169 insertions(+) create mode 100644 python/ecs-schedulescaling/README.md create mode 100644 python/ecs-schedulescaling/app.py create mode 100644 python/ecs-schedulescaling/cdk.json create mode 100644 python/ecs-schedulescaling/requirements.txt create mode 100644 python/ecs-schedulescaling/schedulescaling/__init__.py create mode 100644 python/ecs-schedulescaling/schedulescaling/schedulescaling_stack.py diff --git a/python/ecs-schedulescaling/README.md b/python/ecs-schedulescaling/README.md new file mode 100644 index 000000000..dfa9d1320 --- /dev/null +++ b/python/ecs-schedulescaling/README.md @@ -0,0 +1,18 @@ + +# Schedule scaling for ecs + +This project contains code to deploy ecs cluster with the ability to scale fargate tasks based on day-night schedule. + +The `cdk.json` contains context variables for schedule along with minimum and maximum capacity count for day and night schedule. + +Use below command to view application auto scaling scheduled actions: +``` +aws application-autoscaling describe-scheduled-actions \ + --service-namespace ecs +``` + +Use below command to view descriptive information about the scaling activities: +``` +aws application-autoscaling describe-scaling-activities \ + --service-namespace ecs +``` \ No newline at end of file diff --git a/python/ecs-schedulescaling/app.py b/python/ecs-schedulescaling/app.py new file mode 100644 index 000000000..ad1490c5f --- /dev/null +++ b/python/ecs-schedulescaling/app.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python3 + +import aws_cdk as cdk + +from schedulescaling.schedulescaling_stack import SchedulescalingStack + +app = cdk.App() +SchedulescalingStack(app, "SchedulescalingStack") + +app.synth() diff --git a/python/ecs-schedulescaling/cdk.json b/python/ecs-schedulescaling/cdk.json new file mode 100644 index 000000000..0d3ee033a --- /dev/null +++ b/python/ecs-schedulescaling/cdk.json @@ -0,0 +1,65 @@ +{ + "app": "python3 app.py", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "requirements*.txt", + "source.bat", + "**/__init__.py", + "python/__pycache__", + "tests" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/core:validateSnapshotRemovalPolicy": true, + "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true, + "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true, + "@aws-cdk/aws-route53-patters:useCertificate": true, + "@aws-cdk/customresources:installLatestAwsSdkDefault": false, + "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true, + "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true, + "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true, + "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true, + "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true, + "@aws-cdk/aws-redshift:columnId": true, + "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true, + "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true, + "@aws-cdk/aws-apigateway:requestValidatorUniqueId": true, + "@aws-cdk/aws-kms:aliasNameRef": true, + "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true, + "@aws-cdk/core:includePrefixInUniqueNameGeneration": true, + "@aws-cdk/aws-efs:denyAnonymousAccess": true, + "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true, + "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true, + "@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true, + "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true, + "@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true, + "@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true, + "@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true, + "scaling":true, + "daytime": [{"cron":"cron(00 08 * * ? *)"},{"min":2},{"max":3}], + "nightime": [{"cron":"cron(00 18 * * ? *)"},{"min":1},{"max":2}] + } +} diff --git a/python/ecs-schedulescaling/requirements.txt b/python/ecs-schedulescaling/requirements.txt new file mode 100644 index 000000000..5df6c2f33 --- /dev/null +++ b/python/ecs-schedulescaling/requirements.txt @@ -0,0 +1,2 @@ +aws-cdk-lib==2.111.0 +constructs>=10.0.0,<11.0.0 diff --git a/python/ecs-schedulescaling/schedulescaling/__init__.py b/python/ecs-schedulescaling/schedulescaling/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python/ecs-schedulescaling/schedulescaling/schedulescaling_stack.py b/python/ecs-schedulescaling/schedulescaling/schedulescaling_stack.py new file mode 100644 index 000000000..75f939c7d --- /dev/null +++ b/python/ecs-schedulescaling/schedulescaling/schedulescaling_stack.py @@ -0,0 +1,74 @@ +from aws_cdk import Duration, Stack +from aws_cdk import aws_applicationautoscaling as appscaling +from aws_cdk import aws_ec2 as ec2 +from aws_cdk import aws_ecs as ecs +from aws_cdk import aws_ecs_patterns as ecs_patterns +from aws_cdk import aws_iam as iam +from aws_cdk import aws_sns_subscriptions as subs +from constructs import Construct + + +class SchedulescalingStack(Stack): + def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None: + super().__init__(scope, construct_id, **kwargs) + + scaling = self.node.try_get_context("scaling") + dayschedule = self.node.try_get_context("daytime") + nightschedule = self.node.try_get_context("nightime") + + min_capacity = 1 + max_capacity = 1 + + # daytime scaling schedule(UTC) + day_schedule = dayschedule[0]["cron"] + day_min = dayschedule[1]["min"] + day_max = dayschedule[2]["max"] + + # nighttime scaling schedule(UTC) + night_schedule = nightschedule[0]["cron"] + night_min = nightschedule[1]["min"] + night_max = nightschedule[2]["max"] + + vpc = ec2.Vpc(self, "ecsVpc", max_azs=2) + + ecs_cluster = ecs.Cluster( + self, + id="ecscluster", + vpc=vpc, + container_insights=True, + enable_fargate_capacity_providers=True, + ) + + # create task definition + task_definition = ecs.FargateTaskDefinition(self, "taskdef", cpu=256) + image = ecs.ContainerImage.from_registry("amazon/amazon-ecs-sample") + container = task_definition.add_container(id="ecs-con-task", image=image) + container.add_port_mappings(ecs.PortMapping(container_port=8080)) + + # define service + service = ecs.FargateService( + self, "FargateService", cluster=ecs_cluster, task_definition=task_definition + ) + # define autoscaling + if scaling: + target = appscaling.ScalableTarget( + self, + "ScalableTarget", + service_namespace=appscaling.ServiceNamespace.ECS, + min_capacity=min_capacity, + max_capacity=max_capacity, + resource_id=f"service/{ecs_cluster.cluster_name}/{service.service_name}", + scalable_dimension="ecs:service:DesiredCount", + ) + target.scale_on_schedule( + "daytime", + schedule=appscaling.Schedule.expression(day_schedule), + min_capacity=day_min, + max_capacity=day_max, + ) + target.scale_on_schedule( + "nighttime", + schedule=appscaling.Schedule.expression(night_schedule), + min_capacity=night_min, + max_capacity=night_max, + )