AWS – Stop Start Instances with Lambda function based on Cloudwatch

This is my customized solution to stop and start AWS EC2 instances. There is solution from Amazon – AWS EC2 Scheduler: https://s3.amazonaws.com/solutions-reference/ec2-scheduler/latest/ec2-scheduler.pdf

Example picked from: https://github.com/sangitaccount/AWS/blob/master/cfn-templates/AWS_EC2_ShutStart.template

As mentioned in AWS document – https://aws.amazon.com/premiumsupport/knowledge-center/start-stop-lambda-cloudwatch/  I wanted to stop my development instances during night ( 11 PM )and start them in the morning ( 10 AM ).

ISSUE 1: In the AWS example – Instances are hardcoded but I need to find a way to pull the instance id’s dynamically to stop/start them.

To make this work I’ve tagged my instances which need “Restart – EveryDay”.

Instance_Tags.JPG

Now with in the Lambda script instances_stop script – add the below function to get the instance ids

def get_instances(instances):

client = boto3.client('ec2')
 response = client.describe_tags(Filters=[{ 'Name': 'resource-type', 'Values': ['instance'] , 'Name': 'value', 'Values': ['EveryDay'] }])
 for i in range(len(response['Tags'])):
 if response['Tags'][i]['ResourceType'] == 'instance':
 instances.append(str(response['Tags'][i]['ResourceId']))

 

Updated IAM policy document to include “ec2:Desc*” along with “ec2:Stop*” and “ec2:Start*” so that Lambda service can fetch the EC2 Instance tags.

ISSUE 2: One of the instances is Bastion host – and as I stop and start public ip address is changing so need to login into console to find the public ip address and update my putty config. Elastic IP is an option but I do get billed if the instance is not running state. If I release and assign new EIP then I will be in the same situation to find the new public ip.

To work around – I’ve already got a public domain registered in AWS route53 so updated script to put a CNAME record to instance public ip address hostname. Now with in the Lambda script instances_start script – add the below function to update CNAME

def bastion_host(bastion):

client = boto3.client('ec2')
 response = client.describe_instances(Filters=[{ 'Name': 'tag:Name', 'Values': ['Bastion'] }])
 while not response['Reservations'][0]['Instances'][0]['PublicDnsName']:
 time.sleep(15)
 return response['Reservations'][0]['Instances'][0]['PublicDnsName']

def update_rr(bastion):

client = boto3.client('route53')
 response = client.list_hosted_zones()
 for i in range(len( response['HostedZones'])):
 if response['HostedZones'][i]['Name'] == zone:
 update_response = client.change_resource_record_sets( HostedZoneId=response['HostedZones'][i]['Id'], ChangeBatch={'Changes': [{ 'Action': 'UPSERT', 'ResourceRecordSet': { 'Name': source, 'Type': 'CNAME', 'TTL': 300, 'ResourceRecords': [{'Value': bastion }] } } ]})
 print update_response

 

Update IAM policy document to include route53 permissions so that Lambda service can list zones and update resource record.

 {
 "Effect": "Allow",
 "Action": [
 "route53:ChangeResourceRecordSets",
 "route53:GetHostedZone",
 "route53:ListHostedZones",
 "route53:ListResourceRecordSets"
 ],
 "Resource": "*"
 }

With the changes in place and using the CF template I am able to stop and start the instances at scheduled intervals and not to worry about the public ip for access.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s