Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
AWS Lambda AMI Backups
# Automated AMI Backups
#
# @author Bobby Kozora
#
# This script will search for all instances having a tag with the name "backup"
# and value "Backup" on it. As soon as we have the instances list, we loop
# through each instance
# and create an AMI of it. Also, it will look for a "Retention" tag key which
# will be used as a retention policy number in days. If there is no tag with
# that name, it will use a 7 days default value for each AMI.
#
# After creating the AMI it creates a "DeleteOn" tag on the AMI indicating when
# it will be deleted using the Retention value and another Lambda function
import boto3
import collections
import datetime
import sys
import pprint
ec = boto3.client('ec2')
#image = ec.Image('id')
def lambda_handler(event, context):
reservations = ec.describe_instances(Filters=[
{
'Name': 'tag-key',
'Values': ['backup', 'Backup']
},
]).get('Reservations', [])
instances = sum([[i for i in r['Instances']] for r in reservations], [])
print("Found %d instances that need backing up" % len(instances))
to_tag = collections.defaultdict(list)
for instance in instances:
try:
retention_days = [
int(t.get('Value')) for t in instance['Tags']
if t['Key'] == 'Retention'
][0]
except IndexError:
retention_days = 7
#for dev in instance['BlockDeviceMappings']:
# if dev.get('Ebs', None) is None:
# continue
# vol_id = dev['Ebs']['VolumeId']
# print "Found EBS volume %s on instance %s" % (
# vol_id, instance['InstanceId'])
#snap = ec.create_snapshot(
# VolumeId=vol_id,
#)
#create_image(instance_id, name, description=None, no_reboot=False, block_device_mapping=None, dry_run=False)
# DryRun, InstanceId, Name, Description, NoReboot, BlockDeviceMappings
create_time = datetime.datetime.now()
create_fmt = create_time.strftime('%Y-%m-%d')
AMIid = ec.create_image(
InstanceId=instance['InstanceId'],
Name="Lambda - " + instance['InstanceId'] + " from " +
create_fmt,
Description="Lambda created AMI of instance " +
instance['InstanceId'] + " from " + create_fmt,
NoReboot=True,
DryRun=False)
pprint.pprint(instance)
#sys.exit()
#break
#to_tag[retention_days].append(AMIid)
to_tag[retention_days].append(AMIid['ImageId'])
print("Retaining AMI %s of instance %s for %d days" % (
AMIid['ImageId'],
instance['InstanceId'],
retention_days,
))
print(to_tag.keys())
for retention_days in to_tag.keys():
delete_date = datetime.date.today() + datetime.timedelta(
days=retention_days)
delete_fmt = delete_date.strftime('%m-%d-%Y')
print("Will delete %d AMIs on %s" %
(len(to_tag[retention_days]), delete_fmt))
#break
ec.create_tags(Resources=to_tag[retention_days],
Tags=[
{
'Key': 'DeleteOn',
'Value': delete_fmt
},
])
@somu4git

This comment has been minimized.

Copy link

somu4git commented Sep 2, 2016

hi I am trying to test run this script manually from cli (python lambdaAMIBackups.py) , its not returning anything. and not creating AMIs.
Am I doing it wrong ?

@stefan-lipinski

This comment has been minimized.

Copy link

stefan-lipinski commented Sep 21, 2016

In line 47 there is "finally:" missing. Without it, the script will not make a backup if the "Retention" tag is in place, because it will not execute the part after "except IndexError:".

@Fzomerdijk

This comment has been minimized.

Copy link

Fzomerdijk commented Jan 31, 2017

Hi thanks for this nice script. I have made a small enhancement on the script, so it also supports instances with multiple EBS volumes attached.

On line 42 i have added a new empty list, that will hold all instances that where already done :
inDone = []

And the line 67 should be surrounded by a check. Just replace the line 67 with:

            if str(instance['InstanceId']) not in inDone:

              AMIid = ec.create_image(InstanceId=instance['InstanceId'], Name="Lambda - " + instance['InstanceId'] + " from " + create_fmt, Description="Lambda created AMI of instance " + instance['InstanceId'] + " from " + create_fmt, NoReboot=True, DryRun=False)

              inDone.insert(0,str(instance['InstanceId']))

              print "Created AMI %s of instance %s " % (AMIid['ImageId'], instance['InstanceId'])

            else:

              print "We already got an AMI of instance %s " % (instance['InstanceId'])

Thanks again!
Regards, Frank

@shekhargoyal-akamai

This comment has been minimized.

Copy link

shekhargoyal-akamai commented Feb 16, 2017

Hi Guys,
Thanks for the script and the comments. When I am executing the Lambda function for an instance on the same day again, it fails since the AMI-ID already exists with that name. Can someone guide with an improvement to this please.
Thanks in adv.
Shekhar

@dcloud9

This comment has been minimized.

Copy link

dcloud9 commented Feb 22, 2017

You need to Deregister the AMI and might as well remove the snapshots. AMI names (not Name tags) should be unique.

@antopj

This comment has been minimized.

Copy link

antopj commented Mar 1, 2017

Thank you for the nice script. :)

and to avoid error saying "duplicate AMI" if we manually run the function,
edit following variable
create_fmt = create_time.strftime('%Y-%m-%d')
To
create_fmt = create_time.strftime('%Y-%m-%d--%H-%M-%S')

how to add instance "Name" tag to AMIs ?
thanks again :)

@SilvesterRaj

This comment has been minimized.

Copy link

SilvesterRaj commented Mar 2, 2017

Can some one help me ,i have configured the code and it is executing .
But AMI is not triggering

Thanks,
Silvester

@SilvesterRaj

This comment has been minimized.

Copy link

SilvesterRaj commented Mar 2, 2017

Finally Worked

@antopj

This comment has been minimized.

Copy link

antopj commented Mar 2, 2017

Hi,

is it possible to create AMI with NAME tag same as of Instance NAME tag ?

means instance have tag "Name: server.example.com" so AMI created also have same tag ?

thanks again

@prashantswe39

This comment has been minimized.

Copy link

prashantswe39 commented Mar 5, 2017

@stefan-lipinski what patch need to put at line 47 as you said.

@shekhargoyal-akamai

This comment has been minimized.

Copy link

shekhargoyal-akamai commented Mar 6, 2017

This lambda function only creates the AMI but DOES NOT delete them after the expiry of retention period!
Has anyone got the lambda function to de-register the AMI and delete its associated snapshots based on the DeleteOn tag?

Please advise.

@jrcorray

This comment has been minimized.

Copy link

jrcorray commented Mar 6, 2017

shekhargoyal-akamai The cleanup function is here: https://gist.github.com/bkozora/d4f1cf0e5cf26acdd377

@shekhargoyal-akamai

This comment has been minimized.

Copy link

shekhargoyal-akamai commented Mar 7, 2017

Thanks guys, I will give this a go..

@Subash1986

This comment has been minimized.

Copy link

Subash1986 commented Mar 8, 2017

Hi @bkozora & @stefan-lipinski : Thanks for the script, I tried this script today and I am getting error at Line no. 48:

for instance in instances:
try:
retention_days = [
int(t.get('Value')) for t in instance['Tags']
if t['Key'] == 'Retention'][0]
except IndexError:
retention_days = 7

This is not moving to next line (for finding volumes) as there is a syntax error for try: condition in the for loop statement (Line no. 48) can you correct me what should be the valid syntax there to overcome this issue please? I removed the Tag Name for instance (Retention) it worked and able to create AMI but if i drop the Retention tag Name again i am getting the error.

@jrcorray

This comment has been minimized.

Copy link

jrcorray commented Mar 21, 2017

@Subash1986 this is what @stefan-lipinski was talking about above. I changed mine to the following for lines 40-50 (just add the finally)

for instance in instances:
    try:
        retention_days = [
            int(t.get('Value')) for t in instance['Tags']
            if t['Key'] == 'Retention'][0]
    except IndexError:
        retention_days = 7

    finally:
@satyaprakashn

This comment has been minimized.

Copy link

satyaprakashn commented May 6, 2017

Hi,
How can we exclude particular volumes while creating AMI??

Please help me on this.

thanks,
Prakash.

@raakey

This comment has been minimized.

Copy link

raakey commented May 19, 2017

Does this script will help to Clear corresponding snapshots ?????

@simo385

This comment has been minimized.

Copy link

simo385 commented Jun 1, 2017

I think the script doesn't work because the indent from row 58 to 82 is wrong.

@johnalejandro001

This comment has been minimized.

Copy link

johnalejandro001 commented Jun 29, 2017

I have a doubt in line 25 - 31 the tag is been defined, but I don't see what is the value. Can someone please suggest on this.

@AkshatGhai123

This comment has been minimized.

Copy link

AkshatGhai123 commented Dec 12, 2017

hi all, i need to modify the script so as to keep last 2 backups and delete all the previous ones, i.e everytime a third backup is made for the same instance, the last one must be deleted with the snapshot too, any suggestions?

@niranjan4it

This comment has been minimized.

Copy link

niranjan4it commented Jan 2, 2018

Hi All,

I just want to clear on this script about ..This lambda function will work properly if we are using ELB with multiple instances ?
If this function is not working with ELB then please share me its changes or new script with ELB support AMI backups.

Thanks
Niranjan

@pratibhadeepti

This comment has been minimized.

Copy link

pratibhadeepti commented Jan 9, 2018

Hello everyone, I need to add one more thing in this script to alert the user with email notification about the description of the created ami. Pls help me how can we do that. will really appreciate your efforts for the change.

@muralisheri

This comment has been minimized.

Copy link

muralisheri commented Jan 16, 2018

I am not able to create AMI for the multiple Instances. I have tried by modifying the line 25 as below
reservations = ec.describe_instances(
Filters=[
{'Name': 'instance-id', 'Values': ['i-87bexxxx', 'i-a500xxxx']},
]
).get(
'Reservations', []
)

Any help is appreciated.

@muralisheri

This comment has been minimized.

Copy link

muralisheri commented Jan 18, 2018

never mind. I got the solution :)

@kmenzel

This comment has been minimized.

Copy link

kmenzel commented Jan 29, 2018

I've copied the raw code. I've added the finally: on line 50, and I just get a timeout every time I run the test on Lambda, even when I max out the time limit to 5 minutes. Am I missing something that is obvious? I really don't know Python very well, so feel free to treat me like the newb I am when it comes to it. ;-)

Nevermind. Solved. If you get this issue, I have no idea WHY, but even when your instances are in a VPC, do NOT enable the Lambda script for VPC.

@hemantkarole

This comment has been minimized.

Copy link

hemantkarole commented Mar 29, 2018

When i am adding retention tag on instance tag after that backup is not happening as soon as i removed that retention tag backup is working
please let know incase any one has answer.

@mcalr3

This comment has been minimized.

Copy link

mcalr3 commented Apr 9, 2018

Thanks to @bkozora for creating these scripts. They are both performing the desired function.

Other than outputting the result of each Lambda execution to Cloudwatch logs, is there any way to output specific lines in the log to an SNS topic or send by email using SES?

I am looking for a daily email with a summary of AMIs created, along with any errors in the log.
I have created a Cloudwatch alarm which sends an email if any error in running the lambda function but this only sends if the function itself fails, not if any resulting actions performed such as snapshots fail.

@sstalon

This comment has been minimized.

Copy link

sstalon commented Apr 10, 2018

For those who want to add the Instance Name into the AMI this is the code I used.
snapshot_name = 'N/A'
if 'Tags' in instance:
for tags in instance['Tags']:
if tags["Key"] == 'Name':
snapshot_name = tags["Value"]
print "Tagging snapshot with Name: %s" % (snapshot_name)

Than

I moddifed:
AMIid = ec.create_image(InstanceId=instance['InstanceId'], Name="Lambda - " + instance['InstanceId'] + " from " + create_fmt, Description="Lambda created AMI of instance " + instance['InstanceId'] + " from " + create_fmt, NoReboot=True, DryRun=False)

and added snapshot_name into that. Depending where you want to added it example

"Lambda - " + instance['InstanceId'] + " " + snapshot_name + " from " + create_fmt etc etc...

@sstalon

This comment has been minimized.

Copy link

sstalon commented Apr 10, 2018

Does anyone know how would would tag the individual snapshots that are attached to the AMI with names or descriptions?

@ramesher

This comment has been minimized.

Copy link

ramesher commented Apr 11, 2018

I want to take all ec2 intance tags and add into AMI when it takes. Is it possible?

@sstalon

This comment has been minimized.

Copy link

sstalon commented Apr 11, 2018

@ramesher
Yes, look at my post above. Use that as example to create the tags you want,
and than at the end you need to modify this line to add your additional tags accordingly

ec.create_tags(
Resources=to_tag[retention_days],
Tags=[
{'Key': 'DeleteOn', 'Value': delete_fmt},
]
)

@sstalon

This comment has been minimized.

Copy link

sstalon commented Apr 11, 2018

@ramesher

Or to make it simple this is a example

ec.create_tags(
Resources=to_tag[retention_days],
Tags=[
{'Key': 'DeleteOn', 'Value': delete_fmt},
{'Key': 'Account', 'Value': 'Gov'},
{'Key': 'Environment', 'Value': 'Test'}
]
)

@ramesher

This comment has been minimized.

Copy link

ramesher commented Apr 12, 2018

Thank you. Let me check and update you. Is it possible to take instance type and SG also?

@sstalon

This comment has been minimized.

Copy link

sstalon commented Apr 12, 2018

@ramesher
of course, you just have to figure out how to query that into a variable.
like example for me i want a name to my AMI.
so so i just use my code of

instance_name = 'N/A'
if 'Tags' in instance:
for tags in instance['Tags']:
if tags["Key"] == 'Name':
instance_name = tags["Value"]
print "Tagging snapshot with Name: %s" % (instance_name)

and than modify my tag code to have this

ec.create_tags(
Resources=to_tag[retention_days],
Tags=[
{'Key': 'DeleteOn', 'Value': delete_fmt},
{'Key': 'Account', 'Value': 'Gov'},
{'Key': 'Environment', 'Value': 'Test'},
{'Key': 'Name', 'Value': instance_name}
]
)

@mcalr3

This comment has been minimized.

Copy link

mcalr3 commented Apr 17, 2018

Could someone please tell me how I send an email notification with the exection result of this function, or just some sort of output to show the backup went well?

EDIT: I figred out how to do this using Cloudwatch Logs and creating a Metric Filter for the logs looking for specific keywords in the script that I was interested in Alerting on. For example, "error". Now I don't care if the whole log gets sent by email or not because I can log in and check the logs right from cloudwatch.

@mcalr3

This comment has been minimized.

Copy link

mcalr3 commented Jun 4, 2018

In relation to my comment on the Cleanup script where it was timing out if you were looping through too many instances, here is the code I inserted into this Backup script in order to add another Tag with InstanceID to the AMI, which allows us to filter on this value in the cleanup script to allow much better performance.

Replace lines 78 - 82 with the following:

            print "Creating AMI %s of instance %s and retaining for %d days" % (
                AMIid['ImageId'],
                instance['InstanceId'],
                retention_days,
            )
            print "--------"
            amiid = AMIid['ImageId']
            instanceid = instance['InstanceId']
            tag_image = ec2.Image(amiid)
            tag_image.create_tags(
                Tags=[
                    {'Key': 'InstanceID', 'Value': instanceid},
                ]
            )

@AndrewFarley

This comment has been minimized.

Copy link

AndrewFarley commented Jun 5, 2018

Hey guys... so... I saw this and all your struggling and it kinda made me weep for the internet as a whole. So, I've been needing this a while... I finally got around to it and made it into a micro example/open source serverless stack. It's here on my github, called AWS Automated Daily Snapshots. It will...

  • Automatically scan through EVERY AWS region (by default, you can also narrow the list to speed it up)
  • Will automatically deploy to your AWS account with restrictive permissions and set a CloudWatch event to run daily
  • Will automatically backup instances to AMIs and then delete those AMIs as they become stale
  • Will preserve ALL tags that the instance had, on the AMI (and adding a few for our purposes, to rotate)
  • Has a bit of error checking... but it could have more. :P Haven't hit any errors yet that I'm not catching but if you guys find any, let me know I'll gladly fix it.
  • And is totally open source and simple as heck, so feel free to hack on it yourselves. Forks/MRs welcome.
  • Is very loosely based on the logic you guys had here above, but really rewritten from the ground up, as you can tell. Some of the flaws in the linked delete script everyone had is fixed in my project.

Enjoy y'all! :)

@bmalave-cng

This comment has been minimized.

Copy link

bmalave-cng commented Jun 6, 2018

I have a windows instance,
how do I have it stop the instance before it creates the AMI and start afterward?
Also does it need any waits?

@charterresources

This comment has been minimized.

Copy link

charterresources commented Jun 10, 2018

@AndrewFarley
Thank you for your contribution. It worked like a charm.
two questions:

  • How can we make sure the AMI-Name timestamps are based on our current timezone?
  • How can we tag the snapshots using original volume tags
@mcalr3

This comment has been minimized.

Copy link

mcalr3 commented Jun 26, 2018

@AndrewFarley
Thanks for your project. Have you tried it in an account with 80+ instances? I am using @bkozora 's code which I tweaked and find that each function (creation then cleanup) runs for at least a minute each. Sometimes it fails due to boto3 errors (max retries reached) but Lambda re-runs it automatically.

Just want to know how scalable your serverless app is.

@cmcconnell1

This comment has been minimized.

Copy link

cmcconnell1 commented Jul 2, 2018

Thanks @AndrewFarley (and @bkozora for this gist). I just implemented the https://github.com/AndrewFarley/AWSAutomatedDailyInstanceAMISnapshots project to replace a previous CM-based solution we had, and it works great! I just submitted a P/R for adding to the README with examples of scheduling the daily backups via cron, and aws cli query filtering on tags, to help folks visualize and manage AMI's, etc.
This project is a gem and saved me a lot of time reinventing another wheel. Thanks again!

@tmblue

This comment has been minimized.

Copy link

tmblue commented Aug 1, 2018

Anyone tweak this or the addition that @cmcconnell1 made to do snapshots. Not AMI's but of volumes, to replace the sensitive and kind of clunky AWS CLI ec2-automate-backup ?

@DJ-Laker

This comment has been minimized.

Copy link

DJ-Laker commented Oct 14, 2018

I've just tried implementing this script for the first time and am getting the following error - can anyone provide some guidance please?


09:34:25
START RequestId: 568053dd-cf94-11e8-8b3a-a7435442769d Version: $LATEST

09:34:25
module initialization error: name 'instances' is not defined

09:34:25
END RequestId: 568053dd-cf94-11e8-8b3a-a7435442769d

09:34:25
REPORT RequestId: 568053dd-cf94-11e8-8b3a-a7435442769d Duration: 557.09 ms Billed Duration: 600 ms Memory Size: 128 MB Max Memory Used: 56 MB

09:34:25
module initialization error name 'instances' is not defined

@psipher

This comment has been minimized.

@nehas19

This comment has been minimized.

Copy link

nehas19 commented Feb 21, 2019

some created images are not getting tags,
so , they are not deleting using delete function.

@Phanindrapavan

This comment has been minimized.

Copy link

Phanindrapavan commented Feb 27, 2019

Hi i need instance names tagging with AMI as following

AMIid = ec.create_image(InstanceId=instance['InstanceId'], Name="Lambda - " + instance_Name + " from " + create_fmt, Description="Lambda created AMI of instance " + instance_Name + " from " + create_fmt, NoReboot=True, DryRun=False)

Can any one help me?
@sstalon your modification mentioned @ramesher is not working

@prasantaku

This comment has been minimized.

Copy link

prasantaku commented Aug 13, 2019

My execution failed. Can u help me
Response:
{
"errorMessage": "Syntax error in module 'lambda_function': invalid syntax (lambda_function.py, line 39)",
"errorType": "Runtime.UserCodeSyntaxError",
"stackTrace": [
" File "/var/task/lambda_function.py" Line 39\n print "Found %d instances that need backing up" % len(instances)\n"
]
}

Request ID:
"f95badef-b2be-4716-8f57-ac650e051c03"

Function Logs:
START RequestId: f95badef-b2be-4716-8f57-ac650e051c03 Version: $LATEST
[ERROR] Runtime.UserCodeSyntaxError: Syntax error in module 'lambda_function': invalid syntax (lambda_function.py, line 39)
Traceback (most recent call last):
  File "/var/task/lambda_function.py" Line 39
        print "Found %d instances that need backing up" % len(instances)
END RequestId: f95badef-b2be-4716-8f57-ac650e051c03
REPORT RequestId: f95badef-b2be-4716-8f57-ac650e051c03 Duration: 36.79 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 55 MB

@prasantaku

This comment has been minimized.

Copy link

prasantaku commented Aug 13, 2019

execution failed plz help me

Response:
{
"errorMessage": "Syntax error in module 'lambda_function': invalid syntax (lambda_function.py, line 39)",
"errorType": "Runtime.UserCodeSyntaxError",
"stackTrace": [
" File "/var/task/lambda_function.py" Line 39\n print "Found %d instances that need backing up" % len(instances)\n"
]
}

Request ID:
"f95badef-b2be-4716-8f57-ac650e051c03"

Function Logs:
START RequestId: f95badef-b2be-4716-8f57-ac650e051c03 Version: $LATEST
[ERROR] Runtime.UserCodeSyntaxError: Syntax error in module 'lambda_function': invalid syntax (lambda_function.py, line 39)
Traceback (most recent call last):
  File "/var/task/lambda_function.py" Line 39
        print "Found %d instances that need backing up" % len(instances)
END RequestId: f95badef-b2be-4716-8f57-ac650e051c03
REPORT RequestId: f95badef-b2be-4716-8f57-ac650e051c03 Duration: 36.79 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 55 MB

@sstalon

This comment has been minimized.

Copy link

sstalon commented Sep 16, 2019

execution failed plz help me

Response:
{
"errorMessage": "Syntax error in module 'lambda_function': invalid syntax (lambda_function.py, line 39)",
"errorType": "Runtime.UserCodeSyntaxError",
"stackTrace": [
" File "/var/task/lambda_function.py" Line 39\n print "Found %d instances that need backing up" % len(instances)\n"
]
}

Request ID:
"f95badef-b2be-4716-8f57-ac650e051c03"

Function Logs:
START RequestId: f95badef-b2be-4716-8f57-ac650e051c03 Version: $LATEST
[ERROR] Runtime.UserCodeSyntaxError: Syntax error in module 'lambda_function': invalid syntax (lambda_function.py, line 39)
Traceback (most recent call last):
  File "/var/task/lambda_function.py" Line 39
        print "Found %d instances that need backing up" % len(instances)
END RequestId: f95badef-b2be-4716-8f57-ac650e051c03
REPORT RequestId: f95badef-b2be-4716-8f57-ac650e051c03 Duration: 36.79 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 55 MB

Your probably using higher than python 2.7. Looks like his code only works on Python 2.7.

@bkozora

This comment has been minimized.

Copy link
Owner Author

bkozora commented Sep 18, 2019

I've updated the code to be syntactically compliant with Python 3, only having to wrap print in parenthesis. This will prob be the last update I make, however. AWS has evolved a great deal and now offers automated backups using Systems Manager.

https://docs.aws.amazon.com/en_pv/systems-manager/latest/userguide/automation-aws-createimage.html

https://console.aws.amazon.com/systems-manager/documents/AWS-CreateImage/content?region=us-east-1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.