snap-ebs 0.0.11 → 0.0.20
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +25 -7
- data/lib/plugins/mongo_plugin.rb +18 -16
- data/lib/snap_ebs/options.rb +9 -1
- data/lib/snap_ebs/plugin.rb +17 -4
- data/lib/snap_ebs/snapshotter.rb +46 -6
- data/lib/snap_ebs.rb +10 -2
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 09451fa9061511bfe1442cac3f15884407f08232
|
4
|
+
data.tar.gz: de83068b869c4ea72a3020c4946121743f269dee
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9cdd818bb391797aa7b9f63349a06bbe7d492c9f2327f9d65a90ad050daadbd62fa5d75487bde5e1790870a5f85aa41da8a2733915af4fa4181bc39d62dc60e7
|
7
|
+
data.tar.gz: d4d9ba9c19e88163ba9afefa271b41a9b2ed54737464607e81091e9a41d8a88e3e39caa677fac3220686f02915f908c660f5cbd5fa51a240d10e10b6f59a77f5
|
data/README.md
CHANGED
@@ -1,14 +1,17 @@
|
|
1
1
|
snap-ebs
|
2
2
|
===
|
3
3
|
|
4
|
+
![snap-ebs logo -- the snapping turtle](http://i.imgur.com/DgUZmIq.png)
|
5
|
+
> Backup your EBS volumes in a snap
|
6
|
+
|
4
7
|
This project aims to provide easy, automatic, and consistent snapshots for AWS
|
5
8
|
EBS volumes on EC2 instances.
|
6
9
|
|
7
10
|
Some specific goals and how they are achieved:
|
8
11
|
|
9
|
-
- *Safety*: refuses to operate unless everything seems ok, and tries desperately to leave your system no worse than it
|
10
|
-
- *Reliability*: comprehensive test
|
11
|
-
- *Visibility*: verbose logging options to inspect the decision-making process for any action
|
12
|
+
- *Safety*: refuses to operate unless everything seems ok, and tries desperately to leave your system no worse than it was found
|
13
|
+
- *Reliability*: comprehensive test suite ensures that `snap-ebs` behaves as expected, even in unexpected conditions
|
14
|
+
- *Visibility*: verbose logging options to inspect the decision-making process for any action or non-action
|
12
15
|
- *Ease of Installation*: just install the gem and add one line to your crontab
|
13
16
|
- *Ease of Use*: automatically detects volumes mounted to the machine
|
14
17
|
- *Ease of Monitoring*: 100% visibility of operation can be gained from off-the-shelf monitoring solution plugins
|
@@ -32,15 +35,21 @@ sudo yum install gcc \
|
|
32
35
|
zlib-devel
|
33
36
|
|
34
37
|
gem install snap-ebs
|
35
|
-
crontab -e
|
36
38
|
```
|
37
39
|
|
38
40
|
Usage
|
39
41
|
===
|
40
42
|
|
41
|
-
|
43
|
+
1. Create an IAM user with the necessary permissions: `ec2:CreateSnapshot`, `ec2:DescribeTags`, `ec2:DescribeVolumes`
|
44
|
+
2. Download the user's credentials file (as `.csv`) and put it somewhere on the server (I would suggest `/opt/snap-ebs-credentials.csv`, ideally with mode 600 and owned by the user who will run `snap-ebs`)
|
45
|
+
3. Add something like this to your root crontab:
|
42
46
|
```
|
43
|
-
snap-ebs -
|
47
|
+
0 0 * * * * /bin/bash -lc 'snap-ebs -c /opt/snap-ebs-credentials.csv --directory /data,/log,/journal --logfile /var/log/snap-ebs.log --mongo --mongo-shutdown yes'
|
48
|
+
```
|
49
|
+
|
50
|
+
4. If you're using rvm with a passwordless-sudo user, you might use this instead:
|
51
|
+
```
|
52
|
+
0 0 * * * * /bin/bash -lc 'rvmsudo snap-ebs -c /opt/snap-ebs-credentials.csv --directory /data,/log,/journal --logfile /var/log/snap-ebs.log --mongo --mongo-shutdown yes'
|
44
53
|
```
|
45
54
|
|
46
55
|
Testing
|
@@ -51,12 +60,21 @@ Because you'll be running this against production servers with critical data, it
|
|
51
60
|
Unit Tests
|
52
61
|
---
|
53
62
|
|
63
|
+
```
|
64
|
+
rspec spec/*
|
65
|
+
```
|
66
|
+
|
54
67
|
Like any good Ruby software, this tool has a unit test suite that seeks mostly to verify the plumbing and ensure that there are no runtime errors on the expected execution paths. This is only the tip of the iceberg...
|
55
68
|
|
56
69
|
Vagrant Integration Testing
|
57
70
|
---
|
58
71
|
|
72
|
+
```
|
73
|
+
vagrant up
|
74
|
+
```
|
75
|
+
|
59
76
|
The integration layer contains an Ansible + Vagrant setup to configure clusters of services for live-fire testing (the AWS bits are mocked out via `snap-ebs`'s `--mock` flag). Simply running `vagrant up` will build a cluster of servers running MySQL, MongoDB, etc, configured in a master/slave architecture as approprite for the given system.
|
60
77
|
|
61
|
-
There is also a set of Ansible tasks that verify the operation of each plugin under **both ideal and
|
78
|
+
There is also a set of Ansible tasks that verify the operation of each plugin under **both ideal and degenerate** conditions. This means that `snap-ebs` runs reliably, even when the services it operates on do not. Things like timeouts and service restart failures are modeled via `socat`, and assertions are made on the correct error output for each condition. For more info on how this is done, check `roles/integration-test/tasks/*.yml`
|
62
79
|
|
80
|
+
Your local copy of `snap-ebs` is mounted to each machine, so all modifications carry over immediately. If you are hacking on `snap-ebs` and just want to run the integration tests, rather than the full ansible provisioning playbook, simply add `-t test` to the command Vagrant spits out when starting the ansible provisioner.
|
data/lib/plugins/mongo_plugin.rb
CHANGED
@@ -34,22 +34,6 @@ class SnapEbs::Plugin::MongoPlugin < SnapEbs::Plugin
|
|
34
34
|
}
|
35
35
|
end
|
36
36
|
|
37
|
-
def client
|
38
|
-
@client ||= Mongo::Client.new [ "#{options.host}:#{options.port}" ], client_options
|
39
|
-
end
|
40
|
-
|
41
|
-
def client_options
|
42
|
-
{
|
43
|
-
connect: :direct,
|
44
|
-
user: options.user,
|
45
|
-
password: options.password,
|
46
|
-
server_selection_timeout: options.server_selection_timeout.to_i,
|
47
|
-
wait_queue_timeout: options.wait_queue_timeout.to_i,
|
48
|
-
connection_timeout: options.connection_timeout.to_i,
|
49
|
-
socket_timeout: options.socket_timeout.to_i
|
50
|
-
}
|
51
|
-
end
|
52
|
-
|
53
37
|
def before
|
54
38
|
require 'mongo'
|
55
39
|
Mongo::Logger.logger = logger
|
@@ -76,6 +60,8 @@ class SnapEbs::Plugin::MongoPlugin < SnapEbs::Plugin
|
|
76
60
|
"Mongo"
|
77
61
|
end
|
78
62
|
|
63
|
+
private
|
64
|
+
|
79
65
|
def unlock_or_start_mongo
|
80
66
|
(options.retry.to_i + 1).times do
|
81
67
|
if wired_tiger?
|
@@ -151,4 +137,20 @@ class SnapEbs::Plugin::MongoPlugin < SnapEbs::Plugin
|
|
151
137
|
logger.info "Unlocking mongo"
|
152
138
|
client.database['$cmd.sys.unlock'].find().read
|
153
139
|
end
|
140
|
+
|
141
|
+
def client
|
142
|
+
@client ||= Mongo::Client.new [ "#{options.host}:#{options.port}" ], client_options
|
143
|
+
end
|
144
|
+
|
145
|
+
def client_options
|
146
|
+
{
|
147
|
+
connect: :direct,
|
148
|
+
user: options.user,
|
149
|
+
password: options.password,
|
150
|
+
server_selection_timeout: options.server_selection_timeout.to_i,
|
151
|
+
wait_queue_timeout: options.wait_queue_timeout.to_i,
|
152
|
+
connection_timeout: options.connection_timeout.to_i,
|
153
|
+
socket_timeout: options.socket_timeout.to_i
|
154
|
+
}
|
155
|
+
end
|
154
156
|
end
|
data/lib/snap_ebs/options.rb
CHANGED
@@ -2,6 +2,10 @@ require 'optparse'
|
|
2
2
|
|
3
3
|
class SnapEbs
|
4
4
|
module Options
|
5
|
+
# Gets the root `option_parser`. Plugins do not append to this directly,
|
6
|
+
# but instead supply a list of options, arguments, descriptions, and
|
7
|
+
# default values. `SnapEbs` manages the namespacing of options, and each
|
8
|
+
# plugin receives its own `options` object.
|
5
9
|
def option_parser
|
6
10
|
unless @option_parser
|
7
11
|
@option_parser = OptionParser.new do |o|
|
@@ -29,7 +33,10 @@ class SnapEbs
|
|
29
33
|
|
30
34
|
o.on("-l", "--logfile FILE", "Path to a file used for logging") do |filename|
|
31
35
|
options.logfile = filename
|
32
|
-
|
36
|
+
end
|
37
|
+
|
38
|
+
o.on("-f", "--fs-freeze", "Freeze filesystems for fsfreeze or xfs_freeze before snapping (unfreezes after)") do |fs_freeze|
|
39
|
+
options.fs_freeze = fs_freeze
|
33
40
|
end
|
34
41
|
|
35
42
|
o.on("-d", "--directory PATH", "Only snap volumes mounted to PATH, a comma-separated list of directories") do |d|
|
@@ -44,6 +51,7 @@ class SnapEbs
|
|
44
51
|
end
|
45
52
|
end
|
46
53
|
|
54
|
+
# Get the root `options` object, and instance of OpenStruct
|
47
55
|
def options
|
48
56
|
@options ||= OpenStruct.new
|
49
57
|
end
|
data/lib/snap_ebs/plugin.rb
CHANGED
@@ -20,10 +20,6 @@ class SnapEbs::Plugin
|
|
20
20
|
@options ||= OpenStruct.new default_options
|
21
21
|
end
|
22
22
|
|
23
|
-
def logger
|
24
|
-
SnapEbs.logger false
|
25
|
-
end
|
26
|
-
|
27
23
|
def collect_options option_parser
|
28
24
|
option_parser.on "--#{name.downcase}", "Enable the #{name} plugin" do
|
29
25
|
options.enable = true
|
@@ -36,6 +32,19 @@ class SnapEbs::Plugin
|
|
36
32
|
end
|
37
33
|
end
|
38
34
|
|
35
|
+
protected
|
36
|
+
|
37
|
+
# Executes the given block with error handling, and prints helpful error
|
38
|
+
# messages when an exception is caught.
|
39
|
+
#
|
40
|
+
# Returns the result of the block, or nil if an exception occured
|
41
|
+
#
|
42
|
+
# ```
|
43
|
+
# carefully 'reticulate splines' do
|
44
|
+
# splines.each &:reticulate
|
45
|
+
# end
|
46
|
+
# ```
|
47
|
+
#
|
39
48
|
def carefully msg
|
40
49
|
yield
|
41
50
|
rescue => e
|
@@ -43,6 +52,10 @@ class SnapEbs::Plugin
|
|
43
52
|
logger.error e
|
44
53
|
nil
|
45
54
|
end
|
55
|
+
|
56
|
+
def logger
|
57
|
+
SnapEbs.logger false
|
58
|
+
end
|
46
59
|
end
|
47
60
|
|
48
61
|
require 'plugins/mysql_plugin'
|
data/lib/snap_ebs/snapshotter.rb
CHANGED
@@ -5,8 +5,13 @@ module SnapEbs::Snapshotter
|
|
5
5
|
|
6
6
|
attr_writer :compute
|
7
7
|
|
8
|
+
# Takes snapshots of attached volumes (optionally filtering by volumes
|
9
|
+
# mounted to the given directories)
|
8
10
|
def take_snapshots
|
11
|
+
system 'sync'
|
9
12
|
attached_volumes.collect do |vol|
|
13
|
+
dir = device_to_directory device_name vol
|
14
|
+
fs_freeze dir if options[:fs_freeze]
|
10
15
|
next unless should_snap vol
|
11
16
|
logger.debug "Snapping #{vol.id}"
|
12
17
|
snapshot = compute.snapshots.new
|
@@ -14,10 +19,13 @@ module SnapEbs::Snapshotter
|
|
14
19
|
snapshot.description = snapshot_name(vol)
|
15
20
|
snapshot.save
|
16
21
|
snapshot
|
22
|
+
fs_unfreeze dir if options[:fs_freeze]
|
17
23
|
end
|
18
24
|
end
|
19
25
|
|
20
|
-
#
|
26
|
+
# Get the Fog compute object. When `--mock` is given, `Fog.mock!` is called
|
27
|
+
# and information normally auto-detected from AWS is injected with dummy
|
28
|
+
# values to circumvent the lazy loaders.
|
21
29
|
def compute
|
22
30
|
require 'fog/aws'
|
23
31
|
if options[:mock]
|
@@ -35,6 +43,8 @@ module SnapEbs::Snapshotter
|
|
35
43
|
})
|
36
44
|
end
|
37
45
|
|
46
|
+
private
|
47
|
+
|
38
48
|
def attached_volumes
|
39
49
|
@attached_volumes ||= compute.volumes.select { |vol| vol.server_id == instance_id }
|
40
50
|
end
|
@@ -69,14 +79,44 @@ module SnapEbs::Snapshotter
|
|
69
79
|
"#{Time.now.strftime "%Y%m%d%H%M%S"}-#{id}-#{vol.device}"
|
70
80
|
end
|
71
81
|
|
82
|
+
def directory_to_device dir
|
83
|
+
`df -T #{dir} | grep dev`.split(/\s/).first.strip
|
84
|
+
end
|
85
|
+
|
86
|
+
def device_to_directory device
|
87
|
+
`df -T | grep dev`.lines.each do |lines|
|
88
|
+
parts = line.split(/\s/).map &:strip
|
89
|
+
return parts.list if parts.first == device
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def is_root_device? dir
|
94
|
+
directory_to_device('/') == directory_to_device(dir)
|
95
|
+
end
|
96
|
+
|
97
|
+
def device_name vol
|
98
|
+
vol.device.gsub('/dev/s', '/dev/xv') rescue vol.device
|
99
|
+
end
|
100
|
+
|
72
101
|
def should_snap vol
|
73
|
-
|
74
|
-
options.directory.nil? or devices_to_snap.include?(normalized_device)
|
102
|
+
options.directory.nil? or devices_to_snap.include?(device_name vol)
|
75
103
|
end
|
76
104
|
|
77
105
|
def devices_to_snap
|
78
|
-
@devices_to_snap ||= options.directory.split(',').map { |dir|
|
79
|
-
|
80
|
-
|
106
|
+
@devices_to_snap ||= options.directory.split(',').map { |dir| directory_to_device dir }
|
107
|
+
end
|
108
|
+
|
109
|
+
def fs_freeze_command
|
110
|
+
@fs_freeze_command ||= system('which fsfreeze > /dev/null') ? 'fsfreeze' : 'xfs_freeze'
|
111
|
+
end
|
112
|
+
|
113
|
+
def fs_freeze dir
|
114
|
+
return logger.warn "Refusing to freeze #{dir}, which is the root device (#{directory_to_device dir})" if is_root_device? dir
|
115
|
+
system("#{fs_freeze_command} -f #{dir}")
|
116
|
+
end
|
117
|
+
|
118
|
+
def fs_unfreeze dir
|
119
|
+
return logger.warn "Refusing to unfreeze #{dir}, which is the root device (#{directory_to_device dir})" if is_root_device? dir
|
120
|
+
system("#{fs_freeze_command} -u #{dir}")
|
81
121
|
end
|
82
122
|
end
|
data/lib/snap_ebs.rb
CHANGED
@@ -34,11 +34,16 @@ class SnapEbs
|
|
34
34
|
SnapEbs::Plugin.registered_plugins
|
35
35
|
end
|
36
36
|
|
37
|
+
# Executes plugin before hooks, takes the snapshot, then runs the after
|
38
|
+
# hooks. Plugin hooks are called within `rescue` blocks to isolate errors
|
39
|
+
# from affecting other plugins or the snapshot plugins. Note that non-
|
40
|
+
# standard exceptions (i.e. out of memory or keyboard interrupt) will still
|
41
|
+
# cause a execution to abort.
|
37
42
|
def run
|
38
43
|
plugins.each do |plugin|
|
39
44
|
begin
|
40
45
|
plugin.before if plugin.options.enable
|
41
|
-
rescue
|
46
|
+
rescue => e
|
42
47
|
logger.error "Encountered error while running the #{plugin.name} plugin's before hook"
|
43
48
|
logger.error e
|
44
49
|
end
|
@@ -49,19 +54,22 @@ class SnapEbs
|
|
49
54
|
plugins.each do |plugin|
|
50
55
|
begin
|
51
56
|
plugin.after if plugin.options.enable
|
52
|
-
rescue
|
57
|
+
rescue => e
|
53
58
|
logger.error "Encountered error while running the #{plugin.name} plugin's after hook"
|
54
59
|
logger.error e
|
55
60
|
end
|
56
61
|
end
|
57
62
|
end
|
58
63
|
|
64
|
+
# Entry point for the `snap-ebs` binary
|
59
65
|
def execute
|
60
66
|
option_parser.parse!
|
61
67
|
logger.debug "Debug logging enabled"
|
62
68
|
run
|
63
69
|
end
|
64
70
|
|
71
|
+
# Get the global logger instance
|
72
|
+
# `logger.debug 'reticulating splines'`
|
65
73
|
def logger
|
66
74
|
# HACK -- the logfile argument only gets used on the first invocation
|
67
75
|
SnapEbs.logger options.logfile
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: snap-ebs
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.20
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bryan Conrad
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-07-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: fog
|
@@ -66,7 +66,7 @@ dependencies:
|
|
66
66
|
- - ~>
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '2.0'
|
69
|
-
description:
|
69
|
+
description: Tested, service-aware and consistent AWS EC2 backups via EBS snapshots.
|
70
70
|
email: bryan.conrad@synctree.com
|
71
71
|
executables:
|
72
72
|
- snap-ebs
|
@@ -104,5 +104,5 @@ rubyforge_project:
|
|
104
104
|
rubygems_version: 2.4.3
|
105
105
|
signing_key:
|
106
106
|
specification_version: 4
|
107
|
-
summary:
|
107
|
+
summary: EBS backups in a snap
|
108
108
|
test_files: []
|