mongo-ec2-backup 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +21 -50
- data/Rakefile +3 -2
- data/bin/ec2_snapshot_restorer +132 -0
- metadata +4 -3
- data/lib/ec2_snapshot_restorer.rb +0 -34
data/README.markdown
CHANGED
@@ -2,67 +2,38 @@
|
|
2
2
|
|
3
3
|
Suite of tools to backup and manage snapshots of MongoDB data set to EC2 Snapshots.
|
4
4
|
|
5
|
-
## Lock and Snapshot:
|
5
|
+
## Lock and Snapshot: mongo_lock_and_snapshot.rb
|
6
6
|
|
7
7
|
### Usage
|
8
8
|
|
9
|
-
Snapshot a list of devices on a given instance on ec2.
|
9
|
+
Snapshot a list of devices on a given instance on ec2.
|
10
10
|
|
11
11
|
```shell
|
12
|
-
|
12
|
+
/mnt/lib/mongo-ec2-consistent-backup/bin# infra-ruby lock_and_snapshot -p /ebs/lvms/lvol0/ -a ACCESS_KEY -s SECRET_KEY -d /dev/sdg,/dev/sdh,/dev/sdi,/dev/sdj,/dev/sdk,/dev/sdl,/dev/sdm,/dev/sdn
|
13
13
|
```
|
14
14
|
|
15
|
-
* --
|
16
|
-
* --access-key-id, -a
|
17
|
-
* --secret-access-key, -s
|
18
|
-
* --devices, -d
|
19
|
-
* --
|
20
|
-
* --
|
21
|
-
* --limit, -l <i>: Cleanup old snapshots to keep only limit snapshots. Default values are stored in EC2VolumeSnapshoter::KIND
|
22
|
-
* --region: Region hosting the instances
|
23
|
-
* --help, -e: Show this message
|
15
|
+
* --path, -p : Data path to freeze
|
16
|
+
* --access-key-id, -a : Access Key Id for AWS
|
17
|
+
* --secret-access-key, -s : Secret Access Key for AWS
|
18
|
+
* --devices, -d : Devices to snapshot, comma separated
|
19
|
+
* --type, -t : Snapshot type, to choose among test,snapshot,daily,weekly,monthly,yearly (default: snapshot)
|
20
|
+
* --help, -h: Show this message
|
24
21
|
|
25
|
-
|
22
|
+
It freeze the path using ```xfs_freeze```and create a snapshot for all the disks passed in the command line. To make this work without too much trouble with a mongo that is in a replica set, you can shut down the replica before running the command. You can also use mongo fsync and lock but this will probably make your cluster a bit nervous about that. Shutting down ensure no mongos will try to use the frozen mongo.
|
26
23
|
|
27
|
-
|
24
|
+
### Usage with IAM
|
28
25
|
|
29
|
-
|
30
|
-
This information can be fetched out for use at a later point via the knife script provided in the ohai-raid package:
|
26
|
+
If you use IAM for your authentication in EC2, here is a probably up to date list of the permissions you need to grant:
|
31
27
|
|
32
28
|
```
|
33
|
-
|
34
|
-
|
29
|
+
"ec2:CreateSnapshot",
|
30
|
+
"ec2:DeleteSnapshot",
|
31
|
+
"ec2:DescribeSnapshots",
|
32
|
+
"ec2:CreateTags",
|
33
|
+
"ec2:DescribeTags",
|
34
|
+
"ec2:DescribeVolumes",
|
35
|
+
"ec2:DescribeInstances",
|
36
|
+
"ec2:AttachVolume",
|
37
|
+
"ec2:CreateVolume"
|
35
38
|
```
|
36
|
-
|
37
|
-
You can combine the two tools to automate daily backup of you MongoDB server:
|
38
|
-
|
39
|
-
```
|
40
|
-
./lock_and_snapshot.rb -a ACCESS_KEY_ID -s SECRET_ACCESS_KEY --hostname server01 --devices $(knife exec /path/to/scripts/show_raid_devices server01.fqdn.com /dev/md0) --type daily
|
41
|
-
```
|
42
|
-
|
43
|
-
### Tool Description
|
44
|
-
|
45
|
-
* Find instance id by resolving the hostname provided in the CLI and scanning the instances in EC2
|
46
|
-
* Lock Mongo by connecting via the hostname:port provided in the parameters
|
47
|
-
* Snapshot the disks, delete old backups
|
48
|
-
* Unlock Mongo
|
49
|
-
|
50
|
-
## MD inspection: ec2-consistent-backup.rb
|
51
|
-
|
52
|
-
### Usage
|
53
|
-
|
54
|
-
This script demonstrates the way it analyses Mongo DB Data path to extract the MD device and components associated
|
55
|
-
|
56
|
-
```shell
|
57
|
-
./ec2-consistent-backup -p 27017
|
58
|
-
```
|
59
|
-
|
60
|
-
### Tool description
|
61
|
-
|
62
|
-
* connect to mongo at port provided, retrieves dbpath
|
63
|
-
* find what mount this dbpath corresponds to
|
64
|
-
* use /proc/mdstat to find out which drive are corresponding to the dbpath mount disk
|
65
|
-
|
66
|
-
# API
|
67
|
-
|
68
39
|
Internal API documentation is at: http://rubydoc.info/github/octplane/mongo-ec2-consistent-backup/master/frames
|
data/Rakefile
CHANGED
@@ -3,12 +3,12 @@ require "rake/clean"
|
|
3
3
|
require 'rake/gempackagetask'
|
4
4
|
require 'rake/rdoctask'
|
5
5
|
|
6
|
-
desc "Packages up
|
6
|
+
desc "Packages up the gem."
|
7
7
|
task :default => :package
|
8
8
|
|
9
9
|
spec = Gem::Specification.new do |s|
|
10
10
|
s.name = 'mongo-ec2-backup'
|
11
|
-
s.version = '0.0.
|
11
|
+
s.version = '0.0.5'
|
12
12
|
s.summary = 'Snapshot your mongodb in the EC2 cloud via XFS Freeze'
|
13
13
|
|
14
14
|
s.author = 'Pierre Baillet'
|
@@ -25,6 +25,7 @@ spec = Gem::Specification.new do |s|
|
|
25
25
|
s.files = FileList['lib/**/*.rb', 'bin/*', '[A-Z]*', 'test/**/*'].to_a
|
26
26
|
|
27
27
|
s.executables << "mongo_lock_and_snapshot"
|
28
|
+
s.executables << "ec2_snapshot_restorer"
|
28
29
|
|
29
30
|
# Supress the warning about no rubyforge project
|
30
31
|
s.rubyforge_project = 'nowarning'
|
@@ -0,0 +1,132 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
ENV['BUNDLE_GEMFILE'] = File.join(File.dirname(__FILE__), "..", "Gemfile")
|
5
|
+
require 'bundler/setup'
|
6
|
+
require 'trollop'
|
7
|
+
require 'fog'
|
8
|
+
require 'open-uri'
|
9
|
+
|
10
|
+
$: << File.join(File.dirname(__FILE__), "../lib")
|
11
|
+
|
12
|
+
DEBUG=false
|
13
|
+
|
14
|
+
def log what
|
15
|
+
puts what if DEBUG
|
16
|
+
end
|
17
|
+
#
|
18
|
+
class SnapshotRestorer
|
19
|
+
attr_accessor :snaps
|
20
|
+
def initialize(aki, sak)
|
21
|
+
@compute = Fog::Compute.new({:provider => 'AWS', :aws_access_key_id => aki, :aws_secret_access_key => sak})
|
22
|
+
@snaps = []
|
23
|
+
@volumes = []
|
24
|
+
end
|
25
|
+
def find_snapshots(instance_id, kind = 'snapshot')
|
26
|
+
log "Looking for snapshots for #{instance_id}"
|
27
|
+
volume_map = []
|
28
|
+
snapshots = {}
|
29
|
+
|
30
|
+
tags = @compute.tags.all(:key => 'instance_id', :value => instance_id)
|
31
|
+
|
32
|
+
max_date = nil
|
33
|
+
tags.each do |tag|
|
34
|
+
snap = @compute.snapshots.get(tag.resource_id)
|
35
|
+
t = snap.tags
|
36
|
+
|
37
|
+
# Ignore in progress snapshots
|
38
|
+
if instance_id == t['instance_id'] &&
|
39
|
+
snap.state == 'completed' &&
|
40
|
+
t['kind'] == kind
|
41
|
+
max_date = t['date'] if !max_date || max_date < t['date']
|
42
|
+
log "#{snap.inspect} is valid"
|
43
|
+
snapshots[t['date']] ||= []
|
44
|
+
snapshots[t['date']] << snap
|
45
|
+
end
|
46
|
+
end
|
47
|
+
snapshots['LATEST'] = snapshots[max_date] if snapshots[max_date]
|
48
|
+
return snapshots
|
49
|
+
end
|
50
|
+
def prepare_volumes(dest_instance)
|
51
|
+
@snaps.each do | resource_id |
|
52
|
+
snap = @compute.snapshots.get(resource_id)
|
53
|
+
# Snap have the following tags
|
54
|
+
# application
|
55
|
+
# device
|
56
|
+
# instance_id
|
57
|
+
# date
|
58
|
+
# kind
|
59
|
+
|
60
|
+
t = snap.tags
|
61
|
+
volume = @compute.volumes.new :snapshot_id => snap.id, :size => snap.volume_size, :availability_zone => 'us-east-1c'
|
62
|
+
volume.save
|
63
|
+
volume.reload
|
64
|
+
@compute.create_tags(volume.id, { "application" => t['application'],
|
65
|
+
"sdevice" => t['device'],
|
66
|
+
"date" => t['date'],
|
67
|
+
"kind" => t['kind'],
|
68
|
+
"sinstance" => t['instance_id'],
|
69
|
+
"dinstance" => dest_instance})
|
70
|
+
|
71
|
+
@volumes << volume
|
72
|
+
end
|
73
|
+
def rattach_volumes(base_device = nil)
|
74
|
+
dest = base_device
|
75
|
+
if !dest
|
76
|
+
dest = @volumes.map{ |v| v.tags['sdevice']}.min
|
77
|
+
end
|
78
|
+
dest = dest.dup
|
79
|
+
|
80
|
+
@volumes.each do |vol|
|
81
|
+
vol.reload
|
82
|
+
puts "Attaching #{vol.id} to #{dest} on #{vol.tags['dinstance']}"
|
83
|
+
@compute.attach_volume(vol.tags['dinstance'], vol.id, dest)
|
84
|
+
dest.next!
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
if __FILE__ == $0
|
91
|
+
require 'trollop'
|
92
|
+
require 'ec2_instance_identifier'
|
93
|
+
require 'pp'
|
94
|
+
opts = Trollop::options do
|
95
|
+
opt :hostname, "Hostname tag to use to find the instance", :type => :string, :required => true
|
96
|
+
opt :access_key_id, "Access Key Id for AWS", :type => :string, :required => true
|
97
|
+
opt :secret_access_key, "Secret Access Key for AWS", :type => :string, :required => true
|
98
|
+
opt :date, "Date to restore, use LATEST to take latest data", :type => :string
|
99
|
+
opt :type, "Snapshot type to restore, defaults to snapshot", :type => :string, :default => 'snapshot'
|
100
|
+
opt :target, "Creates volume ready for mounting on instance id. Use special value SELF to restore here", :type => :string
|
101
|
+
opt :first_device, "First device to attach to (default is to use source first device) /dev/sdx", :type => :string
|
102
|
+
end
|
103
|
+
|
104
|
+
finder = EC2InstanceIdentifier.new(opts[:access_key_id], opts[:secret_access_key])
|
105
|
+
instance_identifier = finder.get_instance(opts[:hostname]).id
|
106
|
+
s = SnapshotRestorer.new(opts[:access_key_id], opts[:secret_access_key])
|
107
|
+
|
108
|
+
# Find this instance snapshots
|
109
|
+
snaps = s.find_snapshots(instance_identifier, opts[:type])
|
110
|
+
|
111
|
+
if ! opts[:date] || !snaps.has_key?(opts[:date])
|
112
|
+
puts "We have found the following snapshot's dates:"
|
113
|
+
snaps.each do |k,v|
|
114
|
+
puts "- #{k} (#{v.length} volume snapshots)"
|
115
|
+
end
|
116
|
+
else
|
117
|
+
puts "Snapshot taken at #{opts[:date]}"
|
118
|
+
snaps[opts[:date]].each do |snapshot|
|
119
|
+
puts "- #{snapshot.id}, #{snapshot.volume_size}GB - #{snapshot.tags['device']}"
|
120
|
+
end
|
121
|
+
if opts[:target]
|
122
|
+
s.snaps = snaps[opts[:date]].map{ |s| s.id }
|
123
|
+
target = opts[:target]
|
124
|
+
target = open("http://169.254.169.254/latest/meta-data/instance-id").read if target == "SELF"
|
125
|
+
puts "Preparing volumes for instance #{target}"
|
126
|
+
s.prepare_volumes(target)
|
127
|
+
# Need to clone, because trollop freeze the variable
|
128
|
+
s.rattach_volumes(opts[:first_device])
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mongo-ec2-backup
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.5
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-09-20 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: fog
|
@@ -79,13 +79,14 @@ description:
|
|
79
79
|
email: oct@fotopedia.com
|
80
80
|
executables:
|
81
81
|
- mongo_lock_and_snapshot
|
82
|
+
- ec2_snapshot_restorer
|
82
83
|
extensions: []
|
83
84
|
extra_rdoc_files: []
|
84
85
|
files:
|
85
86
|
- lib/ec2-consistent-backup.rb
|
86
87
|
- lib/ec2_instance_identifier.rb
|
87
|
-
- lib/ec2_snapshot_restorer.rb
|
88
88
|
- lib/ec2_volume_snapshoter.rb
|
89
|
+
- bin/ec2_snapshot_restorer
|
89
90
|
- bin/mongo_lock_and_snapshot
|
90
91
|
- Gemfile
|
91
92
|
- Gemfile.lock
|
@@ -1,34 +0,0 @@
|
|
1
|
-
require 'fog'
|
2
|
-
require 'open-uri'
|
3
|
-
|
4
|
-
|
5
|
-
#
|
6
|
-
class SnapshotRestorer
|
7
|
-
def initialize(aki, sak, snap_ids)
|
8
|
-
@compute = Fog::Compute.new({:provider => 'AWS', :aws_access_key_id => aki, :aws_secret_access_key => sak})
|
9
|
-
@snaps = snap_ids
|
10
|
-
@volumes = []
|
11
|
-
end
|
12
|
-
def restore()
|
13
|
-
@snaps.each do | resource_id |
|
14
|
-
snap = @compute.snapshots.get(resource_id)
|
15
|
-
# Snap have the following tags
|
16
|
-
# application
|
17
|
-
# device
|
18
|
-
# instance_id
|
19
|
-
# date
|
20
|
-
# kind
|
21
|
-
|
22
|
-
t = snap.tags
|
23
|
-
volume = @compute.volumes.new :snapshot_id => snap.id, :size => snap.volume_size, :availability_zone => 'us-east-1c'
|
24
|
-
@compute.tags.create(:resource_id => volume.id, :key =>"application", :value => NAME_PREFIX)
|
25
|
-
@compute.tags.create(:resource_id => volume.id, :key =>"device", :value => device)
|
26
|
-
@compute.tags.create(:resource_id => volume.id, :key =>"date", :value => ts)
|
27
|
-
@compute.tags.create(:resource_id => volume.id, :key =>"kind", :value => kind)
|
28
|
-
volume.save
|
29
|
-
@volumes << volume
|
30
|
-
end
|
31
|
-
end
|
32
|
-
def connect(instance_id = open("http://169.254.169.254/latest/meta-data/instance-id").read)
|
33
|
-
end
|
34
|
-
end
|