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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: adbce5a0782103ad9050e9e643af42782e61eb95
4
- data.tar.gz: 2180be623ea74f1adec4e6f8c7d534f70d8fa3fd
3
+ metadata.gz: 09451fa9061511bfe1442cac3f15884407f08232
4
+ data.tar.gz: de83068b869c4ea72a3020c4946121743f269dee
5
5
  SHA512:
6
- metadata.gz: 68736848b73e037d82a3ac4dcd66934a747f0bb6a207d8b7da609f9b9d50baefdf0af55f5cdcd38aa21d9015f278d1594d0e591b4a611f119a53f39792e7fd9f
7
- data.tar.gz: 1a597eeb20d2921d5db002e8a4d62c35b642537bf18e3c2bc19516b0c467557beeb24829596e677302cd20b977323b4403053c69045460b03a479d989771ae02
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 started
10
- - *Reliability*: comprehensive test sweet makes sure that `snap-ebs` behaves as expected, even in unexpected conditions.
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
- Put something like this in your root crontab:
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 -a AWSACCESSKEY -s AWSSECRETKEY --directory /data,/log,/journal --mongo --mongo-shutdown yes --logfile /var/log/snap-ebs.log
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 pathological** 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`
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.
@@ -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
@@ -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
- logger.debug filename
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
@@ -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'
@@ -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
- # lazy loaders
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
- normalized_device = vol.device.gsub('/dev/s', '/dev/xv') rescue vol.device
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| `df -T #{dir} | grep dev`.split(/\s/).first.strip }
79
- logger.debug @devices_to_snap
80
- @devices_to_snap
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 Exception => e
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 Exception => e
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.11
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-06-29 00:00:00.000000000 Z
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: Easy EBS snapshots that work
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: Easy EBS snapshots that work
107
+ summary: EBS backups in a snap
108
108
  test_files: []