diff --git a/code-server-stack.yaml b/code-server-stack.yaml index c5d6f77..bd7885d 100644 --- a/code-server-stack.yaml +++ b/code-server-stack.yaml @@ -1,7 +1,3 @@ -#------------------------------------------------------ -# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: MIT-0 -#------------------------------------------------------ AWSTemplateFormatVersion: "2010-09-09" Description: deploy a vscode-server on an ec2 #------------------------------------------------------ @@ -263,6 +259,100 @@ Resources: ViewerProtocolPolicy: allow-all OriginRequestPolicyId: !Ref OriginRequestPolicyId CachePolicyId: !Ref VSCodeServerCloudFrontCachePolicy +#-------------------- +# Lambda function resources +#-------------------- + LambdaExecutionRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + Service: lambda.amazonaws.com + Action: sts:AssumeRole + Policies: + - PolicyName: LambdaEC2Policy + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - ec2:DescribeInstances + - ec2:StopInstances + Resource: !Sub 'arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:instance/${VSCodeServer}' + - Effect: Allow + Action: + - cloudwatch:GetMetricStatistics + Resource: '*' + - PolicyName: LambdaLogsPolicy + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - logs:CreateLogGroup + - logs:CreateLogStream + - logs:PutLogEvents + Resource: arn:aws:logs:*:*:* + + IdleEC2CheckerFunction: + Type: AWS::Lambda::Function + Properties: + Handler: index.handler + Role: !GetAtt LambdaExecutionRole.Arn + Code: + ZipFile: | + import boto3 + import datetime + import os + + def handler(event, context): + ec2 = boto3.client('ec2') + cloudwatch = boto3.client('cloudwatch') + + target_instance_id = os.environ['TARGET_INSTANCE_ID'] + + # Check CPU utilization for the last 30 minutes + response = cloudwatch.get_metric_statistics( + Namespace='AWS/EC2', + MetricName='CPUUtilization', + Dimensions=[{'Name': 'InstanceId', 'Value': target_instance_id}], + StartTime=datetime.datetime.utcnow() - datetime.timedelta(minutes=30), + EndTime=datetime.datetime.utcnow(), + Period=300, + Statistics=['Average'] + ) + + datapoints = response['Datapoints'] + if datapoints and max(point['Average'] for point in datapoints) <= 10: + print(f"Stopping idle instance: {target_instance_id}") + ec2.stop_instances(InstanceIds=[target_instance_id]) + + Runtime: python3.12 + Timeout: 60 + Environment: + Variables: + TARGET_INSTANCE_ID: !Ref VSCodeServer + + ScheduledRule: + Type: AWS::Events::Rule + Properties: + Description: "ScheduledRule" + ScheduleExpression: "rate(30 minutes)" + State: "ENABLED" + Targets: + - Arn: !GetAtt IdleEC2CheckerFunction.Arn + Id: "TargetFunctionV1" + + PermissionForEventsToInvokeLambda: + Type: AWS::Lambda::Permission + Properties: + FunctionName: !Ref IdleEC2CheckerFunction + Action: "lambda:InvokeFunction" + Principal: "events.amazonaws.com" + SourceArn: !GetAtt ScheduledRule.Arn #------------------------------------------------------ # Exported output #------------------------------------------------------ diff --git a/lambda-stop-idle-ec2.yaml b/lambda-stop-idle-ec2.yaml new file mode 100644 index 0000000..ef94028 --- /dev/null +++ b/lambda-stop-idle-ec2.yaml @@ -0,0 +1,101 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: 'Lambda function to stop idle EC2 instances' + +Parameters: + TargetEC2InstanceId: + Type: AWS::EC2::Instance::Id + Description: The ID of the EC2 instance to monitor for idleness + +Resources: + LambdaExecutionRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + Service: lambda.amazonaws.com + Action: sts:AssumeRole + Policies: + - PolicyName: LambdaEC2Policy + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - ec2:DescribeInstances + - ec2:StopInstances + Resource: !Sub 'arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:instance/${TargetEC2InstanceId}' + - Effect: Allow + Action: + - cloudwatch:GetMetricStatistics + Resource: '*' + - PolicyName: LambdaLogsPolicy + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - logs:CreateLogGroup + - logs:CreateLogStream + - logs:PutLogEvents + Resource: arn:aws:logs:*:*:* + + IdleEC2CheckerFunction: + Type: AWS::Lambda::Function + Properties: + Handler: index.handler + Role: !GetAtt LambdaExecutionRole.Arn + Code: + ZipFile: | + import boto3 + import datetime + import os + + def handler(event, context): + ec2 = boto3.client('ec2') + cloudwatch = boto3.client('cloudwatch') + + target_instance_id = os.environ['TARGET_INSTANCE_ID'] + + # Check CPU utilization for the last 30 minutes + response = cloudwatch.get_metric_statistics( + Namespace='AWS/EC2', + MetricName='CPUUtilization', + Dimensions=[{'Name': 'InstanceId', 'Value': target_instance_id}], + StartTime=datetime.datetime.utcnow() - datetime.timedelta(minutes=30), + EndTime=datetime.datetime.utcnow(), + Period=300, + Statistics=['Average'] + ) + + datapoints = response['Datapoints'] + if datapoints and max(point['Average'] for point in datapoints) <= 10: + print(f"Stopping idle instance: {target_instance_id}") + ec2.stop_instances(InstanceIds=[target_instance_id]) + + Runtime: python3.12 + Timeout: 60 + Environment: + Variables: + TARGET_INSTANCE_ID: !Ref TargetEC2InstanceId + + ScheduledRule: + Type: AWS::Events::Rule + Properties: + Description: "ScheduledRule" + ScheduleExpression: "rate(30 minutes)" + State: "ENABLED" + Targets: + - Arn: !GetAtt IdleEC2CheckerFunction.Arn + Id: "TargetFunctionV1" + + PermissionForEventsToInvokeLambda: + Type: AWS::Lambda::Permission + Properties: + FunctionName: !Ref IdleEC2CheckerFunction + Action: "lambda:InvokeFunction" + Principal: "events.amazonaws.com" + SourceArn: !GetAtt ScheduledRule.Arn +