Update 2016-08-02: Some AWS commands no longer return any responses. Updated the script accordingly.
I want to schedule backups of my Ubuntu EC2’s EBS on a daily rolling schedule – ie a backup will occur once each day, and after 7 days the oldest snapshot is deleted – so there will always be 1 weeks worth of backups.
Prerequisites
First we need an IAM user with permissions only to do what our backup script requires. Create one in the IAM section of AWS console (I named mine snapshot-backup) and in the Inline Policies area give it the following policy:
{
"Statement": [
{
"Effect": "Allow",
"Action": [
"ec2:CreateSnapshot",
"ec2:CreateTags",
"ec2:DeleteSnapshot",
"ec2:DescribeSnapshots",
"ec2:DescribeTags"
],
"Resource": [
"*"
]
}
]
}
For the script itself we’ll need AWS command line tools:
sudo apt-get install awscli
# Follow the config documentation http://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html
aws configure --profile=ssbackup
AWS Access Key ID [None]: AAAAAAAAAAAAAAAAAA
AWS Secret Access Key [None]: bbbbbbbbbbbbbbbbbbbbbbbbbbbb
Default region name [None]: ap-southeast-2
Default output format [None]: json
The Script
My script will perform a snapshot of each volume passed to it, then delete any snapshots older than the given number of days. It will only delete snapshots that it created:
#!/usr/bin/env python3
#
# Creats a new snapshot for all passed volumes deletes old snapshots
#
# http://jayaraj.sosblogs.com/The-first-blog-b1/AWS-Automated-EBS-Snapshot-Script-b1-p3.htm
#
# Usage:
# python3 ssbackup.py --volume-ids=vol-1a23bcd4 --volume-ids=vol-2b34cde5 --expiry-days=7
#
import argparse
import subprocess
import json
import logging
import time, datetime, dateutil.parser
profile = 'ssbackup' # Your AWS CLI profile
region = 'ap-southeast-2' # AWS region volumes/snapshots are located
def bash(command):
process = subprocess.Popen(command, stdout=subprocess.PIPE)
return process.communicate()[0].decode('utf-8')
def getOurSnapshots():
"""
Return a list of snapshot Dicts created with this plugin.
"""
return json.loads(bash([
"aws", "ec2", "describe-snapshots",
"--filters", "Name=tag-key,Values=Group", "Name=tag-value,Values=ssbackup",
"--profile", profile,
"--region", region,
"--output=json"
]))['Snapshots']
def createSnapshots(volumeIds):
"""
Return True if snapshots of the given volumes are created, else False
Keyword arguments:
volumeIds -- List of EBS volume IDs
"""
# Create the snapshots
snapshots = []
for volumeId in volumeIds:
snapshots.append(createSnapshotForVolume(volumeId))
# Add Name and Group tags to the snapshot
if len(snapshots):
snapshotIds = []
date = time.strftime("%Y-%m-%d")
for snapshot in snapshots:
snapshotIds.append(snapshot['SnapshotId'])
# create-tags returns no output now so just perform the command and
# return True
bash([
"aws", "ec2", "create-tags",
"--resources", ' '.join(snapshotIds),
"--tags", "Key=Name,Value='Backup "+date+"'", "Key=Group,Value=ssbackup",
"--profile", profile,
"--region", region,
"--output=json"
])
return True
return False
def createSnapshotForVolume(volumeId):
"""
Return a Dict of a created snapshot for the given EBS volume
Keyword arguments:
volumeId -- An EBS volume ID
"""
date = time.strftime("%Y-%m-%d")
message = "Creating snapshot for volume "+volumeId+"..."
response = json.loads(bash([
"aws", "ec2", "create-snapshot",
"--volume-id", volumeId,
"--description", "Backup "+date,
"--profile", profile,
"--region", region,
"--output=json"
]))
message += response['SnapshotId']
logging.info(message)
return response
def deleteOldSnapshots(snapshots, max_age):
"""
Delete all listed snapshots older than max_age
"""
snapshotIds = []
date = datetime.datetime.now()
for snapshot in snapshots:
snapshotDate = dateutil.parser.parse(snapshot['StartTime']).replace(tzinfo=None)
dateDiff = date - snapshotDate
if dateDiff.days >= max_age:
message = "Deleting snapshot "+snapshot['SnapshotId']+" ("+str(dateDiff.days)+" days old)..."
# delete-snapshot no longer returns any output
bash([
"aws", "ec2", "delete-snapshot",
"--snapshot-id", snapshot['SnapshotId'],
"--profile", profile,
"--region", region,
"--output=json"
])
message += "done"
logging.info(message)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='ADD YOUR DESCRIPTION HERE')
parser.add_argument('-i','--volume-ids', help='EBS volume ID', required=True)
parser.add_argument('-d','--delete-old', help='Delete old snapshots?', required=False, type=bool, default=True)
parser.add_argument('-x','--expiry-days', help='Number of days to keep snapshots', required=False, type=int, default=7)
args = parser.parse_args()
logging.basicConfig(filename='ssbackup.log', level=logging.DEBUG, format='%(asctime)s: %(message)s', datefmt='%Y-%m-%d %I:%M:%S%p')
# Get all active volumes
volumeIds = args.volume_ids.split(',')
# Create the snapshots
if len(volumeIds):
snapshots = createSnapshots(volumeIds)
pass
# Delete snapshots older than expiry-days
if args.delete_old:
deleteOldSnapshots(getOurSnapshots(), args.expiry_days)
Put it to Work
To run this script automatically each day, add it to your crontab like so:
# m h dom mon dow command 5 0 * * * python3 /home/user/ssbackup.py --volume-ids=vol-a1bcd2e3 --expiry-days=7
The script will automatically create an ssbackup.log file that sits next to it describing exactly what it did on last run. For example here’s my log when running a backup with expiry-days=0 a couple of times:
2015-06-18 01:09:50AM: Creating snapshot for volume vol-a1bcd2e3...snap-a123b4cd 2015-06-18 01:09:52AM: Deleting snapshot snap-b123c4de (0 days old)...done 2015-06-18 02:36:28AM: Creating snapshot for volume vol-a1bcd2e3...snap-c012345d 2015-06-18 02:36:30AM: Deleting snapshot snap-a123b4cd (0 days old)...done