Finally!
After many months of talking with Amazon about better ways of getting backups from one region to another they sneak in a sneaky little update on their blog I will say it here, World changing update! The ability to easily and readily sync your EBS data between regions is game changing, I kid you not, in my tests I synced 100GB from us-east-1 to us-west-1 in such a quick time it was done before I’d switched to that region to see it! However… sometimes it is a little slower… Thinking about it, it could have been a blank volume I don’t really know :/
So at Alfresco we do not heavily use EBS as explained Here when we survived a major amazon issue that affected many larger websites than our own. We do still have EBS volumes as it is almost impossible to get rid of them, and by the very nature the data that is on these EBS volumes is very precious so obviously we want it backed up. A few weeks ago I started writing a backup script for EBS volumes, well the script wasn’t well tested it was quickly written but it worked. I decided that I would very quickly, well to be fair I spent ages on it, update the script with the new CopySnapshot feature.
At the time of writing, the CopySnapshot exists in one place, the deepest, darkest place known to man, the Amazon Query API interface; this basically means that rather than simply doing some method call you have to throw all the data to it and back again to make it hang together, for the real programmers out there this is just an inconvenience for me it is a nightmare, it was an epic battle between my motivation, my understanding and my google prowess, in short I won.
It was never going to be easy…
In the past I have done some basic stuff with REST type API’s, set some header, put some variable on the params of the url and just let it go, all very simple, Amazon’s was slightly more advanced to say the least.
So I had to use this complicated encoded, parsed encrypted and back to back handshake as described here with that and the CopySnapshot docs I was ready to rock!
So after failing for an hour to even get my head around the authentication I decided to cheat, and use google. The biggest break through was thanks to James Murty the AWS class he has written is perfect, the only issue was my understanding on how to use modules in ruby which were very new to me. On a side note i thought Modules were meant to fix issues with name space but for some reason even though I included the class in script it seemed to conflict with the ruby aws-sdk I already had so I just had to rename the class / file from AWS to AWSAPI and all was then fine. That and I also had to add a parameter to pass in the AWS_ACCESS_KEY which was a little annoying as I thought the class would have taken care of that, but to be fair it wasn’t hard to work out in the end.
So first things first, have a look at the AWS.rb file on the site it does the whole signature signing bit well and saves me the hassle of doing or thinking about it. On a side note, this all uses version 2 of the signing which I imagine will be deprecated at some point as version 4 is out and about Here
If you were proactive you’ve already read the CopySnapshot docs and noticed that in plain english or complicated that page does not tell you how to copy between regions. I imagine it’s because I don’t know how to really use the tools but it’s not clear to me… I had noticed that th wording they used was identical to the params being passed in the example so I tried using Region, DestinationRegion, DestRegion all failing, kind of as expected seeing as I was left to guess my way through; I was there, that point where you’ve had enough and it doesn’t look like it is ever going to work so I started writing a support ticket for Amazon so they could point out what ever it was I was missing at the moment of just about to hit submit I had a brainwave. If the only option is to specify the source then how do you know the destination? well, I realised that each region has its own API url, so would that work as the destination? YES!
The journey was challenging, epic even for this sysadmin to tackle and yet here we are, a blog post about regional DR backups of EBS snapshots so without further ado, and no more gilding the lily I present some install notes and code…
Make it work
The first thing you will need to do is get the appropriate files, the AWS.rb from James Murty. Once you have this You will need to make the following changes:
21c21 < module AWS --- > module AWSAPI
Next you will need to steal the code for the backup script:
#!/usr/bin/ruby require 'rubygems' require 'aws-sdk' require 'uri' require 'crack' #Get options ENV['AWS_ACCESS_KEY']=ARGV[0] ENV['AWS_SECRET_KEY']=ARGV[1] volumes_file=ARGV[2] source_region=ARGV[3] source_region ||= "us-east-1" #Create a class for the aws module class CopySnapshot #This allows me to initalize the module with out re-writing it require 'awsapi' include AWSAPI end def get_dest_url (region) case region when "us-east-1" url = "ec2.us-east-1.amazonaws.com" when "us-west-2" url = "ec2.us-west-2.amazonaws.com" when "us-west-1" url = "ec2.us-west-1.amazonaws.com" when "eu-west-1" url = "ec2.eu-west-1.amazonaws.com" when "ap-southeast-1" url = "ec2.ap-southeast-1.amazonaws.com" when "ap-southeast-2" url = "ec2.ap-southeast-2.amazonaws.com" when "ap-northeast-1" url = "ec2.ap-northeast-1.amazonaws.com" when "sa-east-1" url = "ec2.sa-east-1.amazonaws.com" end return url end def copy_to_region(description,dest_region,snapshotid, src_region) cs = CopySnapshot.new #Gen URL url= get_dest_url(dest_region) uri="https://#{url}" #Set up Params params = Hash.new params["Action"] = "CopySnapshot" params["Version"] = "2012-12-01" params["SignatureVersion"] = "2" params["Description"] = description params["SourceRegion"] = src_region params["SourceSnapshotId"] = snapshotid params["Timestamp"] = Time.now.iso8601(10) params["AWSAccessKeyId"] = ENV['AWS_ACCESS_KEY'] resp = begin cs.do_query("POST",URI(uri),params) rescue Exception => e puts e.message end if resp.is_a?(Net::HTTPSuccess) response = Crack::XML.parse(resp.body) if response["CopySnapshotResponse"].has_key?('snapshotId') puts "Snapshot ID in #{dest_region} is #{response["CopySnapshotResponse"]["snapshotId"]}" end else puts "Something went wrong: #{resp.class}" end end if File.exist?(volumes_file) puts "File found, loading content" #Fix contributed by Justin Smith: https://soimasysadmin.com/2013/01/09/aws-copysnapshot-a-regional-dr-backup/#comment-379 ec2 = AWS::EC2.new(:access_key_id => ENV['AWS_ACCESS_KEY'], :secret_access_key=> ENV['AWS_SECRET_KEY']).regions[source_region] File.open(volumes_file, "r") do |fh| fh.each do |line| volume_id=line.split(',')[0].chomp volume_desc=line.split(',')[1].chomp if line.split(',').size >2 volume_dest_region=line.split(',')[2].to_s.chomp end puts "Volume ID = #{volume_id} Volume Description = #{volume_desc}" v = ec2.volumes["#{volume_id}"] if v.exists? puts "creating snapshot" date = Time.now backup_string="Backup of #{volume_id} - #{date.day}-#{date.month}-#{date.year}" puts "#{backup_string}" snapshot = v.create_snapshot(backup_string) sleep 1 until [:completed, :error].include?(snapshot.status) snapshot.tag("Name", :value =>"#{volume_desc} #{volume_id}") # if it should be backed up to another region do so now if !volume_dest_region.nil? if !volume_dest_region.match(/\s/) ? true : false puts "Backing up to #{volume_dest_region}" puts "Snapshot ID = #{snapshot.id}" copy_to_region(volume_desc,volume_dest_region,snapshot.id,source_region) end end else puts "Volume #{volume_id} no longer exists" end end end else puts "no file #{volumes_file}" end
Once you have that you will need to create a file with the volume sin to backup, in the following format:
vol-127facd,My vol,us-west-1 vol-1ac123d,My vol2 vol-cd1245f,My vol3,us-west-2
The format is “volume id, description,region” the region is where you want to backup to. once you have these details you just call the file as follows:
ruby ebs_snapshot.rb <Access key> <secret key> <volumes file>
I don’t recommend putting your key’s on the CLI or even in a cron job but it wouldn’t take much to re-facter this into a class if needed and if you were bothered about that.
It should work quite well if anyone has any problems let me know and I’ll see what I can do :)
Hi,
I had to make a change @84 to use a non-default region. So it became,
ec2 = AWS::EC2.new(:access_key_id => ENV[‘AWS_ACCESS_KEY’], :secret_access_key=> ENV[‘AWS_SECRET_KEY’]).regions[source_region]
Otherwise it would never find my volumes to snapshot.
Good catch! I thought about the source region but when I tested it I only went one way :S I’ll update the post with that :) Thanks!