https://s3.amazonaws.com/solutions-reference/ec2-scheduler/latest/ec2-scheduler.pdf
https://github.com/awslabs/ec2-scheduler
{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "(SO0002) - EC2 Scheduler: This template installs an opt-in version of the EC2 Scheduler for automatically starting and stopping EC2 instances.",
"Parameters": {
"Schedule": {
"Description": "Schedule for CWE Scheduled Expression (e.g. rate([5 minutes|1 hour|1 day]) or cron(0 17 ? * MON-FRI *))",
"Type": "String",
"Default": "rate(5 minutes)"
},
"DefaultStartTime": {
"Description": "Default Start Time (UTC, 24-hour format)",
"Type": "String",
"Default": "0800"
},
"DefaultStopTime": {
"Description": "Default Start Time (UTC, 24-hour format)",
"Type": "String",
"Default": "1800"
},
"DefaultDaysActive": {
"Description": "Enter 'all', 'weekdays', or any combination of days ('mon', 'tue', 'wed', 'thu', 'fri', 'sat', or 'sun') comma separated",
"Type": "String",
"Default": "all"
},
"CustomTagName": {
"Description": "Custom Tag Name",
"Type": "String",
"Default": "scheduler:ec2-startstop"
},
"DynamoDBTableName": {
"Description": "DynamoDB Table Name",
"Type": "String",
"Default": "EC2-Scheduler"
},
"ReadCapacityUnits": {
"ConstraintDescription": "should be between 5 and 10000",
"Default": "1",
"Description": "Provisioned read throughput",
"MaxValue": "10000",
"MinValue": "1",
"Type": "Number"
},
"WriteCapacityUnits": {
"ConstraintDescription": "should be between 5 and 10000",
"Default": "1",
"Description": "Provisioned write throughput",
"MaxValue": "10000",
"MinValue": "1",
"Type": "Number"
},
"SendAnonymousData": {
"Description": "Send anonymous data to AWS",
"Type": "String",
"Default": "Yes",
"AllowedValues": [
"Yes",
"No"
]
},
"CloudWatchMetrics": {
"Description": "Create CloudWatch Custom Metric",
"Type": "String",
"Default": "Enabled",
"AllowedValues": [
"Enabled",
"Disabled"
]
}
},
"Metadata": {
"AWS::CloudFormation::Interface": {
"ParameterGroups": [
{
"Label": {
"default": "Tag Configuration"
},
"Parameters": [
"CustomTagName"
]
},
{
"Label": {
"default": "CloudWatch Event Schedule Configuration"
},
"Parameters": [
"Schedule"
]
},
{
"Label": {
"default": "Default Value Configuration"
},
"Parameters": [
"DefaultStartTime",
"DefaultStopTime",
"DefaultDaysActive"
]
},
{
"Label": {
"default": "DynamoDB Configuration"
},
"Parameters": [
"DynamoDBTableName",
"ReadCapacityUnits",
"WriteCapacityUnits"
]
},
{
"Label": {
"default": "CloudWatch Custom Metric"
},
"Parameters": [
"CloudWatchMetrics"
]
},
{
"Label": {
"default": "Anonymous Metrics Request"
},
"Parameters": [
"SendAnonymousData"
]
}
]
}
},
"Resources": {
"ec2SchedulerRole": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
},
"Path": "/",
"Policies": [
{
"PolicyName": "ec2SchedulerPermissions",
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:*:*:log-group:/aws/lambda/*"
},
{
"Effect": "Allow",
"Action": [
"dynamodb:GetItem"
],
"Resource": [
"arn:aws:dynamodb:*:*:table/*"
]
},
{
"Effect": "Allow",
"Action": [
"ec2:StartInstances",
"ec2:StopInstances",
"ec2:DescribeRegions",
"ec2:DescribeInstances",
"cloudwatch:PutMetricData",
"cloudformation:DescribeStacks"
],
"Resource": "*"
}
]
}
}
]
}
},
"ec2SchedulerOptIn": {
"Type": "AWS::Lambda::Function",
"Properties": {
"Handler": "ec2-scheduler.lambda_handler",
"Role": {
"Fn::GetAtt": [
"ec2SchedulerRole",
"Arn"
]
},
"Description": "EC2 Scheduler Lambda function for automatically starting and stopping EC2 instances.",
"Code": {
"S3Bucket": {
"Fn::Join": [
"",
[
"solutions-",
{
"Ref": "AWS::Region"
}
]
]
},
"S3Key": "ec2-scheduler/v1/ec2-scheduler.zip"
},
"Runtime": "python2.7",
"Timeout": "300"
}
},
"CreateParamDDB": {
"Properties": {
"AttributeDefinitions": [
{
"AttributeName": "SolutionName",
"AttributeType": "S"
}
],
"KeySchema": [
{
"AttributeName": "SolutionName",
"KeyType": "HASH"
}
],
"ProvisionedThroughput": {
"ReadCapacityUnits": {
"Ref": "ReadCapacityUnits"
},
"WriteCapacityUnits": {
"Ref": "WriteCapacityUnits"
}
},
"TableName": {
"Ref": "DynamoDBTableName"
}
},
"Type": "AWS::DynamoDB::Table"
},
"SolutionHelperRole": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
},
"Path": "/",
"Policies": [
{
"PolicyName": "Solution_Helper_Permissions",
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:*:*:log-group:/aws/lambda/*"
},
{
"Effect": "Allow",
"Action": [
"dynamodb:PutItem"
],
"Resource": [
"arn:aws:dynamodb:*:*:table/*"
]
},
{
"Effect": "Allow",
"Action": [
"lambda:AddPermission",
"lambda:CreateFunction",
"lambda:DeleteFunction",
"lambda:GetFunction",
"lambda:UpdateFunctionCode",
"lambda:UpdateFunctionConfiguration",
"s3:GetObject",
"events:DeleteRule",
"events:DisableRule",
"events:EnableRule",
"events:PutEvents",
"events:PutRule",
"events:PutTargets",
"events:RemoveTargets",
"events:ListTargetsByRule",
"iam:PassRole"
],
"Resource": "*"
}
]
}
}
]
}
},
"SolutionHelper": {
"Type": "AWS::Lambda::Function",
"Properties": {
"Handler": "solution-helper.lambda_handler",
"Role": {
"Fn::GetAtt": [
"SolutionHelperRole",
"Arn"
]
},
"Description": "This function creates a CloudFormation custom lambda resource that writes parameters into DynamoDB table.",
"Code": {
"S3Bucket": {
"Fn::Join": [
"",
[
"solutions-",
{
"Ref": "AWS::Region"
}
]
]
},
"S3Key": "library/solution-helper/v1/solution-helper.zip"
},
"Runtime": "python2.7",
"Timeout": "120"
}
},
"PutDdbData": {
"Type": "Custom::PutDDBData",
"Properties": {
"ServiceToken": {
"Fn::GetAtt": [
"SolutionHelper",
"Arn"
]
},
"StoreInDDB": {
"Fn::Join": [
"",
[
"{ 'TableName' : '",
{
"Ref": "CreateParamDDB"
},
"', ",
"'Item': {",
"'CustomTagName': {'S': '",
{
"Ref": "CustomTagName"
},
"'},",
"'SolutionName': {'S': 'EC2Scheduler'},",
"'DefaultStartTime': {'S': '",
{
"Ref": "DefaultStartTime"
},
"'},",
"'DefaultStopTime': {'S': '",
{
"Ref": "DefaultStopTime"
},
"'},",
"'SendAnonymousData': {'S': '",
{
"Ref": "SendAnonymousData"
},
"'},",
"'CloudWatchMetrics': {'S': '",
{
"Ref": "CloudWatchMetrics"
},
"'},",
"'UUID': {'S': '",
{
"Fn::GetAtt": [
"CreateUniqueID",
"UUID"
]
},
"'},",
"'DefaultDaysActive': {'S': '",
{
"Ref": "DefaultDaysActive"
},
"'}",
"}",
"}"
]
]
},
"DependsOn": [
"CreateUniqueID",
"CreateParamDDB"
]
}
},
"CreateUniqueID": {
"Type": "Custom::CreateUUID",
"Properties": {
"ServiceToken": {
"Fn::GetAtt": [
"SolutionHelper",
"Arn"
]
},
"Region": {
"Ref": "AWS::Region"
},
"CreateUniqueID": "true",
"DependsOn": [
"SolutionHelper"
]
}
},
"ScheduledRule": {
"Type": "AWS::Events::Rule",
"Properties": {
"Description": "Rule to trigger EC2Scheduler function on a schedule",
"ScheduleExpression": {
"Ref": "Schedule"
},
"State": "ENABLED",
"Targets": [
{
"Arn": {
"Fn::GetAtt": [
"ec2SchedulerOptIn",
"Arn"
]
},
"Id": "TargetFunctionV1"
}
]
}
},
"PermissionForEventsToInvokeLambda": {
"Type": "AWS::Lambda::Permission",
"Properties": {
"FunctionName": {
"Ref": "ec2SchedulerOptIn"
},
"Action": "lambda:InvokeFunction",
"Principal": "events.amazonaws.com",
"SourceArn": {
"Fn::GetAtt": [
"ScheduledRule",
"Arn"
]
}
}
}
},
"Outputs": {
"UUID": {
"Description": "Newly created random UUID.",
"Value": {
"Fn::GetAtt": [
"CreateUniqueID",
"UUID"
]
}
},
"DDBTableName": {
"Description": "DynamoDB Table Name",
"Value": {
"Ref": "CreateParamDDB"
}
}
}
}
######################################################################################################################
# Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. #
# #
# Licensed under the Amazon Software License (the "License"). You may not use this file except in compliance #
# with the License. A copy of the License is located at #
# #
# http://aws.amazon.com/asl/ #
# #
# or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES #
# OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions #
# and limitations under the License. #
######################################################################################################################
import boto3
import datetime
import json
from urllib2 import Request
from collections import Counter
def putCloudWatchMetric(region, instance_id, instance_state):
cw = boto3.client('cloudwatch')
cw.put_metric_data(
Namespace='EC2Scheduler',
MetricData=[{
'MetricName': instance_id,
'Value': instance_state,
'Unit': 'Count',
'Dimensions': [
{
'Name': 'Region',
'Value': region
}
]
}]
)
def lambda_handler(event, context):
print "Running EC2 Scheduler"
ec2 = boto3.client('ec2')
cf = boto3.client('cloudformation')
outputs = {}
stack_name = context.invoked_function_arn.split(':')[6].rsplit('-', 2)[0]
response = cf.describe_stacks(StackName=stack_name)
for e in response['Stacks'][0]['Outputs']:
outputs[e['OutputKey']] = e['OutputValue']
ddbTableName = outputs['DDBTableName']
awsRegions = ec2.describe_regions()['Regions']
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table(ddbTableName)
response = table.get_item(
Key={
'SolutionName': 'EC2Scheduler'
}
)
item = response['Item']
# Reading Default Values from DynamoDB
customTagName = str(item['CustomTagName'])
customTagLen = len(customTagName)
defaultStartTime = str(item['DefaultStartTime'])
defaultStopTime = str(item['DefaultStopTime'])
defaultTimeZone = 'utc'
defaultDaysActive = str(item['DefaultDaysActive'])
sendData = str(item['SendAnonymousData']).lower()
createMetrics = str(item['CloudWatchMetrics']).lower()
UUID = str(item['UUID'])
TimeNow = datetime.datetime.utcnow().isoformat()
TimeStamp = str(TimeNow)
# Declare Dicts
regionDict = {}
allRegionDict = {}
regionsLabelDict = {}
postDict = {}
for region in awsRegions:
try:
# Create connection to the EC2 using Boto3 resources interface
ec2 = boto3.resource('ec2', region_name=region['RegionName'])
awsregion = region['RegionName']
now = datetime.datetime.now().strftime("%H%M")
nowMax = datetime.datetime.now() - datetime.timedelta(minutes=45)
nowMax = nowMax.strftime("%H%M")
nowDay = datetime.datetime.today().strftime("%a").lower()
# Declare Lists
startList = []
stopList = []
runningStateList = []
stoppedStateList = []
# List all instances
instances = ec2.instances.all()
print "Creating", region['RegionName'], "instance lists..."
for i in instances:
for t in i.tags:
if t['Key'][:customTagLen] == customTagName:
ptag = t['Value'].split(";")
# Split out Tag & Set Variables to default
default1 = 'default'
default2 = 'true'
startTime = defaultStartTime
stopTime = defaultStopTime
timeZone = defaultTimeZone
daysActive = defaultDaysActive
state = i.state['Name']
itype = i.instance_type
# Post current state of the instances
if createMetrics == 'enabled':
if state == "running":
putCloudWatchMetric(region['RegionName'], i.instance_id, 1)
if state == "stopped":
putCloudWatchMetric(region['RegionName'], i.instance_id, 0)
# Parse tag-value
if len(ptag) >= 1:
if ptag[0].lower() in (default1, default2):
startTime = defaultStartTime
else:
startTime = ptag[0]
stopTime = ptag[0]
if len(ptag) >= 2:
stopTime = ptag[1]
if len(ptag) >= 3:
timeZone = ptag[2].lower()
if len(ptag) >= 4:
daysActive = ptag[3].lower()
isActiveDay = False
# Days Interpreter
if daysActive == "all":
isActiveDay = True
elif daysActive == "weekdays":
weekdays = ['mon', 'tue', 'wed', 'thu', 'fri']
if (nowDay in weekdays):
isActiveDay = True
else:
daysActive = daysActive.split(",")
for d in daysActive:
if d.lower() == nowDay:
isActiveDay = True
# Append to start list
if startTime >= str(nowMax) and startTime <= str(now) and \
isActiveDay == True and state == "stopped":
startList.append(i.instance_id)
print i.instance_id, " added to START list"
if createMetrics == 'enabled':
putCloudWatchMetric(region['RegionName'], i.instance_id, 1)
# Append to stop list
if stopTime >= str(nowMax) and stopTime <= str(now) and \
isActiveDay == True and state == "running":
stopList.append(i.instance_id)
print i.instance_id, " added to STOP list"
if createMetrics == 'enabled':
putCloudWatchMetric(region['RegionName'], i.instance_id, 0)
if state == 'running':
runningStateList.append(itype)
if state == 'stopped':
stoppedStateList.append(itype)
# Execute Start and Stop Commands
if startList:
print "Starting", len(startList), "instances", startList
ec2.instances.filter(InstanceIds=startList).start()
else:
print "No Instances to Start"
if stopList:
print "Stopping", len(stopList) ,"instances", stopList
ec2.instances.filter(InstanceIds=stopList).stop()
else:
print "No Instances to Stop"
# Built payload for each region
if sendData == "yes":
countRunDict = {}
typeRunDict = {}
countStopDict = {}
typeStopDict = {}
runDictType = {}
stopDictType = {}
runDict = dict(Counter(runningStateList))
for k, v in runDict.iteritems():
countRunDict['Count'] = v
typeRunDict[k] = countRunDict['Count']
stopDict = dict(Counter(stoppedStateList))
for k, v in stopDict.iteritems():
countStopDict['Count'] = v
typeStopDict[k] = countStopDict['Count']
runDictType['instance_type'] = typeRunDict
stopDictType['instance_type'] = typeStopDict
typeStateSum = {}
typeStateSum['running'] = runDictType
typeStateSum['stopped'] = stopDictType
StateSum = {}
StateSum['instance_state'] = typeStateSum
regionDict[awsregion] = StateSum
allRegionDict.update(regionDict)
except Exception as e:
print ("Exception: "+str(e))
continue
# Build payload for the account
if sendData == "yes":
regionsLabelDict['regions'] = allRegionDict
postDict['Data'] = regionsLabelDict
postDict['TimeStamp'] = TimeStamp
postDict['Solution'] = 'SO0002'
postDict['UUID'] = UUID
# API Gateway URL to make HTTP POST call
url = 'https://metrics.awssolutionsbuilder.com/generic'
data=json.dumps(postDict)
headers = {'content-type': 'application/json'}
req = Request(url, data, headers)
No comments:
Post a Comment