How to Schedule Daily Rolling EBS Snapshots

Posted (Updated ) in Linux

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