Created
April 15, 2019 14:49
-
-
Save TristanV12/81daf5244b6d878418b0e6616e5365d0 to your computer and use it in GitHub Desktop.
AWS Lambda Function that backs up ec2 images and volumes from one primary region to a disaster recovery region
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import boto3 | |
| from datetime import date, timedelta | |
| import datetime | |
| ################### | |
| # Global Constants | |
| ################### | |
| TESTING = False # true if we are ignoring the time interval | |
| START_HOUR = 4 # start at 4 am | |
| HOURS_IN_DAY = 24 # unlikely to change | |
| TAG_NAME = "Backup" # all items to be backed up must have this tag name with the number of times PER DAY that they should be backed up | |
| EXPIRATION_DAYS = 8 # number of days it should take for images and volumes to expire (7 days, expires on the eighth) | |
| ##################### | |
| # Region Information | |
| ##################### | |
| primary_region = "us-east-1" # Primary region: N. Virginia | |
| dr_region = "us-east-2" # Disaster recovery region: Ohio | |
| ############### | |
| # Boto clients | |
| ############### | |
| source_client = boto3.client('ec2') | |
| destination_client = boto3.client('ec2', dr_region) | |
| ################################################################## | |
| # Function that finds all of the possible intervals for this hour | |
| ################################################################## | |
| def times_per_day(): | |
| print("Retrieving intervals...") | |
| hour = datetime.datetime.now().hour - 4 #get hour in EST | |
| adjusted_time = ((hour - START_HOUR) + HOURS_IN_DAY) % HOURS_IN_DAY | |
| # corner cases | |
| if adjusted_time == 0 or TESTING: | |
| output = list(range(1, HOURS_IN_DAY + 1)) | |
| print("Intervals retrieved:", output) | |
| output = list(map(str, output)) | |
| return output | |
| output = [] | |
| for i in range(1, adjusted_time): | |
| if HOURS_IN_DAY % i == 0 and adjusted_time % i == 0: | |
| output.append(str(HOURS_IN_DAY // i)) | |
| if HOURS_IN_DAY % adjusted_time == 0: | |
| output.append(str(HOURS_IN_DAY // adjusted_time)) | |
| print("Intervals retrieved:", output) | |
| return output | |
| ###################################### | |
| # Maps from reservations to instances | |
| ###################################### | |
| def instance_helper(reservation): | |
| return reservation["Instances"][0] | |
| ##################################### | |
| # Gets all current running instances | |
| ##################################### | |
| def get_instances(intervals): | |
| print("Getting instances...") | |
| output = source_client.describe_instances(Filters=[{'Name':'tag-key', 'Values':[TAG_NAME]}, {'Name':'tag-value', 'Values':intervals}])["Reservations"] | |
| if output != None: | |
| output = list(map(instance_helper, output)) | |
| else: | |
| output = [] | |
| output = items_in_interval(output, intervals) | |
| print("Got {:d} instances".format(len(output))) | |
| return output | |
| ################################################ | |
| # Helper function for getting a given tag value | |
| ################################################ | |
| def get_tag_value(key, tags): | |
| if tags == None: | |
| return "" | |
| for tag in tags: | |
| if tag["Key"] == key: | |
| return tag["Value"] | |
| return "" | |
| ############################################################## | |
| # Makes sure that a given interval contains the backup number | |
| # Double checks the filter function | |
| ############################################################## | |
| def items_in_interval(items, intervals): | |
| output = [] | |
| for item in items: | |
| if get_tag_value(TAG_NAME, item["Tags"]) in intervals: | |
| output.append(item) | |
| return output | |
| ################################################### | |
| # Creates images for all of the required instances | |
| ################################################### | |
| def create_images(instances): | |
| if len(instances) == 0: | |
| return | |
| now = datetime.datetime.now() | |
| datestring = date.isoformat(now) | |
| resources = [] | |
| for instance in instances: | |
| image = source_client.create_image(InstanceId=instance["InstanceId"], Name=get_tag_value("Name", instance["Tags"]) + "-" + datestring + "-" + str(now.hour), NoReboot=True) # make SURE NoReboot is true | |
| resources.append(image["ImageId"]) | |
| source_client.create_tags(Resources=[image["ImageId"]], Tags=instance["Tags"]) | |
| source_client.create_tags(Resources=resources, Tags=[{'Key':'Saved', 'Value':'False'}]) | |
| ################################# | |
| # Gets all of the unsaved images | |
| ################################# | |
| def get_unsaved_images(): | |
| print("Getting unsaved images...") | |
| output = source_client.describe_images(Filters=[{'Name':'tag-key', 'Values':['Saved']}, {'Name': 'tag-value', 'Values': ['False']}])["Images"] | |
| if output == None: | |
| output = [] | |
| newoutput = [] | |
| for item in output: | |
| if get_tag_value("Saved", item["Tags"]) == "False": | |
| newoutput.append(item) | |
| print("Found {:d} unsaved images".format(len(newoutput))) | |
| return newoutput | |
| ################################################## | |
| # Copies unsaved images back to the backup region | |
| ################################################## | |
| def copy_images(images): | |
| if len(images) == 0: | |
| return | |
| resources = [] | |
| for image in images: | |
| newimage = destination_client.copy_image(SourceImageId=image["ImageId"], SourceRegion=primary_region, Name=image["Name"]) | |
| destination_client.create_tags(Resources=[newimage["ImageId"]], Tags=image["Tags"]) | |
| source_client.create_tags(Resources=[image["ImageId"]], Tags=[{'Key':'Saved', 'Value':'True'}]) | |
| ############################################# | |
| # Gets all existing volumes (likely useless) | |
| ############################################# | |
| def get_volumes(intervals): | |
| print("Getting volumes...") | |
| output = source_client.describe_volumes(Filters=[{'Name':'tag-key', 'Values':[TAG_NAME]}, {'Name':'tag-value', 'Values':intervals}])["Volumes"] | |
| if output == None: | |
| output = [] | |
| print("Got {:d} volumes".format(len(output))) | |
| return output | |
| ######################################### | |
| # Generic way to expire a list of images | |
| ######################################### | |
| def expire_images(client, images): | |
| for image in images: | |
| client.deregister_image(ImageId=image["ImageId"]) | |
| for bdm in image["BlockDeviceMappings"]: | |
| client.delete_snapshot(SnapshotId=bdm["Ebs"]["SnapshotId"]) | |
| ###################################################### | |
| # Expire all images and snapshots past the given date | |
| ###################################################### | |
| def expire_unneeded_images(): | |
| unneeded_days_ago = date.isoformat(date.today() - timedelta(EXPIRATION_DAYS)) + '*' | |
| unneeded_images_source = source_client.describe_images(Filters=[{'Name':'creation-date', 'Values':[unneeded_days_ago]}, {'Name':'tag-key', 'Values':['Saved']}])["Images"] | |
| unneeded_images_destination = destination_client.describe_images(Filters=[{'Name':'creation-date', 'Values':[unneeded_days_ago]}, {'Name':'tag-key', 'Values':['Saved']}])["Images"] | |
| expire_images(source_client, unneeded_images_source) | |
| expire_images(destination_client, unneeded_images_destination) | |
| ################ | |
| # Main function | |
| ################ | |
| def lambda_handler(event, context): | |
| intervals = times_per_day() | |
| # Backup instances | |
| instances = get_instances(intervals) | |
| create_images(instances) | |
| # Copy new instances to the backup region | |
| unsaved_images = get_unsaved_images() | |
| copy_images(unsaved_images) | |
| # Delete expired images and snapshots | |
| expire_unneeded_images() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment