Skip to content

Commit

Permalink
feat(ecs): add instance warmup period prop (aws#28194)
Browse files Browse the repository at this point in the history
Closes aws#28190.

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
msambol authored Nov 30, 2023
1 parent 073958f commit 990fab3
Show file tree
Hide file tree
Showing 11 changed files with 395 additions and 324 deletions.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,6 @@
"VpcPublicSubnet1Subnet5C2D37C4": {
"Type": "AWS::EC2::Subnet",
"Properties": {
"VpcId": {
"Ref": "Vpc8378EB38"
},
"AvailabilityZone": {
"Fn::Select": [
0,
Expand All @@ -44,21 +41,24 @@
"Key": "Name",
"Value": "integ-default-capacity-provider/Vpc/PublicSubnet1"
}
]
],
"VpcId": {
"Ref": "Vpc8378EB38"
}
}
},
"VpcPublicSubnet1RouteTable6C95E38E": {
"Type": "AWS::EC2::RouteTable",
"Properties": {
"VpcId": {
"Ref": "Vpc8378EB38"
},
"Tags": [
{
"Key": "Name",
"Value": "integ-default-capacity-provider/Vpc/PublicSubnet1"
}
]
],
"VpcId": {
"Ref": "Vpc8378EB38"
}
}
},
"VpcPublicSubnet1RouteTableAssociation97140677": {
Expand All @@ -75,12 +75,12 @@
"VpcPublicSubnet1DefaultRoute3DA9E72A": {
"Type": "AWS::EC2::Route",
"Properties": {
"RouteTableId": {
"Ref": "VpcPublicSubnet1RouteTable6C95E38E"
},
"DestinationCidrBlock": "0.0.0.0/0",
"GatewayId": {
"Ref": "VpcIGWD7BA715C"
},
"RouteTableId": {
"Ref": "VpcPublicSubnet1RouteTable6C95E38E"
}
},
"DependsOn": [
Expand All @@ -102,15 +102,15 @@
"VpcPublicSubnet1NATGateway4D7517AA": {
"Type": "AWS::EC2::NatGateway",
"Properties": {
"SubnetId": {
"Ref": "VpcPublicSubnet1Subnet5C2D37C4"
},
"AllocationId": {
"Fn::GetAtt": [
"VpcPublicSubnet1EIPD7E02669",
"AllocationId"
]
},
"SubnetId": {
"Ref": "VpcPublicSubnet1Subnet5C2D37C4"
},
"Tags": [
{
"Key": "Name",
Expand All @@ -126,9 +126,6 @@
"VpcPublicSubnet2Subnet691E08A3": {
"Type": "AWS::EC2::Subnet",
"Properties": {
"VpcId": {
"Ref": "Vpc8378EB38"
},
"AvailabilityZone": {
"Fn::Select": [
1,
Expand All @@ -152,21 +149,24 @@
"Key": "Name",
"Value": "integ-default-capacity-provider/Vpc/PublicSubnet2"
}
]
],
"VpcId": {
"Ref": "Vpc8378EB38"
}
}
},
"VpcPublicSubnet2RouteTable94F7E489": {
"Type": "AWS::EC2::RouteTable",
"Properties": {
"VpcId": {
"Ref": "Vpc8378EB38"
},
"Tags": [
{
"Key": "Name",
"Value": "integ-default-capacity-provider/Vpc/PublicSubnet2"
}
]
],
"VpcId": {
"Ref": "Vpc8378EB38"
}
}
},
"VpcPublicSubnet2RouteTableAssociationDD5762D8": {
Expand All @@ -183,12 +183,12 @@
"VpcPublicSubnet2DefaultRoute97F91067": {
"Type": "AWS::EC2::Route",
"Properties": {
"RouteTableId": {
"Ref": "VpcPublicSubnet2RouteTable94F7E489"
},
"DestinationCidrBlock": "0.0.0.0/0",
"GatewayId": {
"Ref": "VpcIGWD7BA715C"
},
"RouteTableId": {
"Ref": "VpcPublicSubnet2RouteTable94F7E489"
}
},
"DependsOn": [
Expand All @@ -210,15 +210,15 @@
"VpcPublicSubnet2NATGateway9182C01D": {
"Type": "AWS::EC2::NatGateway",
"Properties": {
"SubnetId": {
"Ref": "VpcPublicSubnet2Subnet691E08A3"
},
"AllocationId": {
"Fn::GetAtt": [
"VpcPublicSubnet2EIP3C605A87",
"AllocationId"
]
},
"SubnetId": {
"Ref": "VpcPublicSubnet2Subnet691E08A3"
},
"Tags": [
{
"Key": "Name",
Expand All @@ -234,9 +234,6 @@
"VpcPrivateSubnet1Subnet536B997A": {
"Type": "AWS::EC2::Subnet",
"Properties": {
"VpcId": {
"Ref": "Vpc8378EB38"
},
"AvailabilityZone": {
"Fn::Select": [
0,
Expand All @@ -260,21 +257,24 @@
"Key": "Name",
"Value": "integ-default-capacity-provider/Vpc/PrivateSubnet1"
}
]
],
"VpcId": {
"Ref": "Vpc8378EB38"
}
}
},
"VpcPrivateSubnet1RouteTableB2C5B500": {
"Type": "AWS::EC2::RouteTable",
"Properties": {
"VpcId": {
"Ref": "Vpc8378EB38"
},
"Tags": [
{
"Key": "Name",
"Value": "integ-default-capacity-provider/Vpc/PrivateSubnet1"
}
]
],
"VpcId": {
"Ref": "Vpc8378EB38"
}
}
},
"VpcPrivateSubnet1RouteTableAssociation70C59FA6": {
Expand All @@ -291,21 +291,18 @@
"VpcPrivateSubnet1DefaultRouteBE02A9ED": {
"Type": "AWS::EC2::Route",
"Properties": {
"RouteTableId": {
"Ref": "VpcPrivateSubnet1RouteTableB2C5B500"
},
"DestinationCidrBlock": "0.0.0.0/0",
"NatGatewayId": {
"Ref": "VpcPublicSubnet1NATGateway4D7517AA"
},
"RouteTableId": {
"Ref": "VpcPrivateSubnet1RouteTableB2C5B500"
}
}
},
"VpcPrivateSubnet2Subnet3788AAA1": {
"Type": "AWS::EC2::Subnet",
"Properties": {
"VpcId": {
"Ref": "Vpc8378EB38"
},
"AvailabilityZone": {
"Fn::Select": [
1,
Expand All @@ -329,21 +326,24 @@
"Key": "Name",
"Value": "integ-default-capacity-provider/Vpc/PrivateSubnet2"
}
]
],
"VpcId": {
"Ref": "Vpc8378EB38"
}
}
},
"VpcPrivateSubnet2RouteTableA678073B": {
"Type": "AWS::EC2::RouteTable",
"Properties": {
"VpcId": {
"Ref": "Vpc8378EB38"
},
"Tags": [
{
"Key": "Name",
"Value": "integ-default-capacity-provider/Vpc/PrivateSubnet2"
}
]
],
"VpcId": {
"Ref": "Vpc8378EB38"
}
}
},
"VpcPrivateSubnet2RouteTableAssociationA89CAD56": {
Expand All @@ -360,12 +360,12 @@
"VpcPrivateSubnet2DefaultRoute060D2087": {
"Type": "AWS::EC2::Route",
"Properties": {
"RouteTableId": {
"Ref": "VpcPrivateSubnet2RouteTableA678073B"
},
"DestinationCidrBlock": "0.0.0.0/0",
"NatGatewayId": {
"Ref": "VpcPublicSubnet2NATGateway9182C01D"
},
"RouteTableId": {
"Ref": "VpcPrivateSubnet2RouteTableA678073B"
}
}
},
Expand All @@ -383,11 +383,11 @@
"VpcVPCGWBF912B6E": {
"Type": "AWS::EC2::VPCGatewayAttachment",
"Properties": {
"VpcId": {
"Ref": "Vpc8378EB38"
},
"InternetGatewayId": {
"Ref": "VpcIGWD7BA715C"
},
"VpcId": {
"Ref": "Vpc8378EB38"
}
}
},
Expand Down Expand Up @@ -627,8 +627,6 @@
"ASG46ED3070": {
"Type": "AWS::AutoScaling::AutoScalingGroup",
"Properties": {
"MaxSize": "1",
"MinSize": "1",
"LaunchTemplate": {
"LaunchTemplateId": {
"Ref": "ASGLaunchTemplate0CA92847"
Expand All @@ -640,6 +638,8 @@
]
}
},
"MaxSize": "1",
"MinSize": "1",
"Tags": [
{
"Key": "Name",
Expand Down Expand Up @@ -792,12 +792,6 @@
"Code": {
"ZipFile": "import boto3, json, os, time\n\necs = boto3.client('ecs')\nautoscaling = boto3.client('autoscaling')\n\n\ndef lambda_handler(event, context):\n print(json.dumps(dict(event, ResponseURL='...')))\n cluster = os.environ['CLUSTER']\n snsTopicArn = event['Records'][0]['Sns']['TopicArn']\n lifecycle_event = json.loads(event['Records'][0]['Sns']['Message'])\n instance_id = lifecycle_event.get('EC2InstanceId')\n if not instance_id:\n print('Got event without EC2InstanceId: %s', json.dumps(dict(event, ResponseURL='...')))\n return\n\n instance_arn = container_instance_arn(cluster, instance_id)\n print('Instance %s has container instance ARN %s' % (lifecycle_event['EC2InstanceId'], instance_arn))\n\n if not instance_arn:\n return\n\n task_arns = container_instance_task_arns(cluster, instance_arn)\n\n if task_arns:\n print('Instance ARN %s has task ARNs %s' % (instance_arn, ', '.join(task_arns)))\n\n while has_tasks(cluster, instance_arn, task_arns):\n time.sleep(10)\n\n try:\n print('Terminating instance %s' % instance_id)\n autoscaling.complete_lifecycle_action(\n LifecycleActionResult='CONTINUE',\n **pick(lifecycle_event, 'LifecycleHookName', 'LifecycleActionToken', 'AutoScalingGroupName'))\n except Exception as e:\n # Lifecycle action may have already completed.\n print(str(e))\n\n\ndef container_instance_arn(cluster, instance_id):\n \"\"\"Turn an instance ID into a container instance ARN.\"\"\"\n arns = ecs.list_container_instances(cluster=cluster, filter='ec2InstanceId==' + instance_id)['containerInstanceArns']\n if not arns:\n return None\n return arns[0]\n\ndef container_instance_task_arns(cluster, instance_arn):\n \"\"\"Fetch tasks for a container instance ARN.\"\"\"\n arns = ecs.list_tasks(cluster=cluster, containerInstance=instance_arn)['taskArns']\n return arns\n\ndef has_tasks(cluster, instance_arn, task_arns):\n \"\"\"Return True if the instance is running tasks for the given cluster.\"\"\"\n instances = ecs.describe_container_instances(cluster=cluster, containerInstances=[instance_arn])['containerInstances']\n if not instances:\n return False\n instance = instances[0]\n\n if instance['status'] == 'ACTIVE':\n # Start draining, then try again later\n set_container_instance_to_draining(cluster, instance_arn)\n return True\n\n task_count = None\n\n if task_arns:\n # Fetch details for tasks running on the container instance\n tasks = ecs.describe_tasks(cluster=cluster, tasks=task_arns)['tasks']\n if tasks:\n # Consider any non-stopped tasks as running\n task_count = sum(task['lastStatus'] != 'STOPPED' for task in tasks) + instance['pendingTasksCount']\n\n if not task_count:\n # Fallback to instance task counts if detailed task information is unavailable\n task_count = instance['runningTasksCount'] + instance['pendingTasksCount']\n\n print('Instance %s has %s tasks' % (instance_arn, task_count))\n\n return task_count > 0\n\ndef set_container_instance_to_draining(cluster, instance_arn):\n ecs.update_container_instances_state(\n cluster=cluster,\n containerInstances=[instance_arn], status='DRAINING')\n\n\ndef pick(dct, *keys):\n \"\"\"Pick a subset of a dict.\"\"\"\n return {k: v for k, v in dct.items() if k in keys}\n"
},
"Role": {
"Fn::GetAtt": [
"ASGDrainECSHookFunctionServiceRoleC12963BB",
"Arn"
]
},
"Environment": {
"Variables": {
"CLUSTER": {
Expand All @@ -806,6 +800,12 @@
}
},
"Handler": "index.lambda_handler",
"Role": {
"Fn::GetAtt": [
"ASGDrainECSHookFunctionServiceRoleC12963BB",
"Arn"
]
},
"Runtime": "python3.9",
"Tags": [
{
Expand Down Expand Up @@ -839,15 +839,15 @@
"ASGDrainECSHookFunctionTopicD6FC59F7": {
"Type": "AWS::SNS::Subscription",
"Properties": {
"Protocol": "lambda",
"TopicArn": {
"Ref": "ASGLifecycleHookDrainHookTopicA8AD4ACB"
},
"Endpoint": {
"Fn::GetAtt": [
"ASGDrainECSHookFunction5F24CF4D",
"Arn"
]
},
"Protocol": "lambda",
"TopicArn": {
"Ref": "ASGLifecycleHookDrainHookTopicA8AD4ACB"
}
}
},
Expand Down Expand Up @@ -914,9 +914,9 @@
"AutoScalingGroupName": {
"Ref": "ASG46ED3070"
},
"LifecycleTransition": "autoscaling:EC2_INSTANCE_TERMINATING",
"DefaultResult": "CONTINUE",
"HeartbeatTimeout": 300,
"LifecycleTransition": "autoscaling:EC2_INSTANCE_TERMINATING",
"NotificationTargetARN": {
"Ref": "ASGLifecycleHookDrainHookTopicA8AD4ACB"
},
Expand All @@ -940,6 +940,7 @@
"Ref": "ASG46ED3070"
},
"ManagedScaling": {
"InstanceWarmupPeriod": 301,
"Status": "ENABLED",
"TargetCapacity": 100
},
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 990fab3

Please sign in to comment.