cluster 0.5.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,69 @@
1
+ PKG_NAME = ENV['PKG_NAME'] || 'cluster'
2
+ require File.join(File.dirname(__FILE__), 'lib', PKG_NAME, 'version')
3
+
4
+ namespace :cluster do
5
+ desc "Display #{Cluster::NAME} current version."
6
+ task :version do
7
+ puts "#{Cluster::NAME} is at version #{Cluster::Version::CURRENT}"
8
+ end
9
+
10
+ desc "Update the #{Cluster::NAME}.gem file at S3."
11
+ task :update => :package do
12
+ # Using right_aws directly to get around having to
13
+ # have a fantastic require structure to use the
14
+ # cluster tool itself to do this.
15
+ #
16
+ # Most particularly this means that error messages will
17
+ # show up...
18
+ require 'right_aws'
19
+
20
+ Rightscale::HttpConnection.params = Rightscale::HttpConnection.params.merge(:ca_file => File.expand_path(File.join(File.dirname(__FILE__), 'lib', 'cluster', 'infrastructures', 'aws-certificates.crt')))
21
+
22
+ oven = RightAws::S3.new(ENV['AMAZON_ACCESS_KEY_ID'], ENV['AMAZON_SECRET_ACCESS_KEY']).bucket(Cluster::BUCKET)
23
+
24
+ unless oven
25
+ STDERR.puts "Bucket by the name of #{Cluster::BUCKET} cannot be found or accessed."
26
+ exit 1
27
+ end
28
+
29
+ filename = File.join('pkg', "#{Cluster::NAME}-#{Cluster::Version::STRING}.gem")
30
+ file = open(filename)
31
+ oven.put("#{Cluster::NAME}.gem", file, {}, 'public-read')
32
+ end
33
+ end
34
+
35
+ # FIXME these RightAws calls could be refactored into one.
36
+ # Not only that but they should probably be using the cluster
37
+ # infrastructure...
38
+ namespace :images do
39
+ desc "List the current images used"
40
+ task :show do
41
+ require 'right_aws'
42
+
43
+ oven = RightAws::S3.new(ENV['AMAZON_ACCESS_KEY_ID'], ENV['AMAZON_SECRET_ACCESS_KEY']).bucket(Cluster::BUCKET)
44
+
45
+ unless oven
46
+ STDERR.puts "Bucket by the name of #{Cluster::BUCKET} cannot be found or accessed."
47
+ exit 1
48
+ end
49
+
50
+ filename = 'cluster_images.yml'
51
+ puts oven.get(filename)
52
+ end
53
+
54
+ desc "Update the ec2 base image data"
55
+ task :update do
56
+ require 'right_aws'
57
+
58
+ oven = RightAws::S3.new(ENV['AMAZON_ACCESS_KEY_ID'], ENV['AMAZON_SECRET_ACCESS_KEY']).bucket(Cluster::BUCKET)
59
+
60
+ unless oven
61
+ STDERR.puts "Bucket by the name of #{Cluster::BUCKET} cannot be found or accessed."
62
+ exit 1
63
+ end
64
+
65
+ filename = 'cluster_images.yml'
66
+ file = open(filename)
67
+ oven.put(filename, file, {}, 'public-read')
68
+ end
69
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.5.32
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ begin
4
+ require 'cluster'
5
+ rescue LoadError
6
+ unless $:.include? '.'
7
+ $: << '.'
8
+ retry
9
+ end
10
+ end
11
+
12
+ require File.join('cluster', 'cli')
13
+
14
+ Cluster::Cli.new
@@ -0,0 +1,676 @@
1
+ #!/usr/bin/perl
2
+ #
3
+ # Copyright (C) 2008-2009 Eric Hammond
4
+ #
5
+ use strict;
6
+ use warnings;
7
+ (our $Prog) = ($0 =~ m%([^/]+)$%);
8
+ use Getopt::Long;
9
+ use Pod::Usage;
10
+ use File::Slurp;
11
+ use Time::HiRes qw(ualarm usleep);
12
+ use Net::Amazon::EC2 0.11;
13
+ use POSIX ':signal_h';
14
+ my $SIGALRM = 14;
15
+
16
+ #---- OPTIONS ----
17
+
18
+ my $Help = 0;
19
+ my $Debug = 0;
20
+ my $Quiet = 0;
21
+ my $Noaction = 0;
22
+
23
+ my $aws_access_key_id = $ENV{AWS_ACCESS_KEY_ID};
24
+ my $aws_secret_access_key = $ENV{AWS_SECRET_ACCESS_KEY};
25
+ my $aws_access_key_id_file = $ENV{AWS_ACCESS_KEY_ID};
26
+ my $aws_secret_access_key_file = $ENV{AWS_SECRET_ACCESS_KEY};
27
+ my $region = undef;
28
+ my $description = $Prog;
29
+ my $xfs_filesystem = undef;
30
+ my $mysql = 0;
31
+ my $mysql_defaults_file = undef;
32
+ my $mysql_master_status_file = undef;
33
+ my $mysql_username = undef;
34
+ my $mysql_password = undef;
35
+ my $mysql_host = undef;
36
+ my $mysql_stop = 0;
37
+ my $snapshot_timeout = 10.0; # seconds
38
+ my $lock_timeout = 0.5; # seconds
39
+ my $lock_tries = 60;
40
+ my $lock_sleep = 5.0; # seconds
41
+
42
+ Getopt::Long::config('no_ignore_case');
43
+ GetOptions(
44
+ 'help|?' => \$Help,
45
+ 'debug' => \$Debug,
46
+ 'quiet' => \$Quiet,
47
+ 'noaction' => \$Noaction,
48
+
49
+ 'aws-access-key-id=s' => \$aws_access_key_id,
50
+ 'aws-secret-access-key=s' => \$aws_secret_access_key,
51
+ 'aws-access-key-id-file=s' => \$aws_access_key_id_file,
52
+ 'aws-secret-access-key-file=s' => \$aws_secret_access_key_file,
53
+ 'region=s' => \$region,
54
+ 'description=s' => \$description,
55
+ 'xfs-filesystem=s' => \$xfs_filesystem,
56
+ 'mysql' => \$mysql,
57
+ 'mysql-defaults-file=s' => \$mysql_defaults_file,
58
+ 'mysql-master-status-file=s' => \$mysql_master_status_file,
59
+ 'mysql-username=s' => \$mysql_username,
60
+ 'mysql-password=s' => \$mysql_password,
61
+ 'mysql-host=s' => \$mysql_host,
62
+ 'mysql-stop' => \$mysql_stop,
63
+ 'snapshot-timeout=s' => \$snapshot_timeout,
64
+ 'lock-timeout=s' => \$lock_timeout,
65
+ 'lock-tries=s' => \$lock_tries,
66
+ 'lock-sleep=s' => \$lock_sleep,
67
+ ) or pod2usage(2);
68
+
69
+ pod2usage(1) if $Help;
70
+
71
+ my @volume_ids = @ARGV;
72
+ pod2usage(2) unless scalar @volume_ids;
73
+
74
+ #---- MAIN ----
75
+
76
+ ($aws_access_key_id, $aws_secret_access_key) = determine_access_keys(
77
+ $aws_access_key_id, $aws_secret_access_key,
78
+ $aws_access_key_id_file, $aws_secret_access_key_file,
79
+ );
80
+ die "$Prog: ERROR: Can't find AWS access key or secret access key"
81
+ unless $aws_access_key_id and $aws_secret_access_key;
82
+
83
+ my $mysql_stopped = undef;
84
+ my $mysql_dbh = undef;
85
+ if ( $mysql_stop ) {
86
+ $mysql_stopped = mysql_stop();
87
+ } elsif ( $mysql ) {
88
+ $mysql_dbh = mysql_connect($mysql_host, $mysql_username, $mysql_password);
89
+ mysql_lock($mysql_dbh);
90
+ }
91
+
92
+ # sync may help flush changed blocks, increasing the consistency on
93
+ # non-XFS file systems, and reducing the time the freeze locks changes
94
+ # out on XFS file systems.
95
+ run_command(['sync']);
96
+
97
+ xfs_freeze($xfs_filesystem) if $xfs_filesystem;
98
+
99
+ ebs_snapshot(\@volume_ids, $region, $description);
100
+
101
+ exit 0;
102
+
103
+ END {
104
+ xfs_thaw($xfs_filesystem) if $xfs_filesystem;
105
+
106
+ if ( defined($mysql_master_status_file) ) {
107
+ $Debug and warn "$Prog: ", scalar localtime,
108
+ ": removing master info file: $mysql_master_status_file\n";
109
+ if ( not $Noaction ) {
110
+ unlink $mysql_master_status_file
111
+ or die "$Prog: Couldn't remove file: $mysql_master_status_file: $!\n";
112
+ }
113
+ }
114
+
115
+ if ( $mysql_stopped ) {
116
+ mysql_start();
117
+ } elsif ( $mysql_dbh ) {
118
+ mysql_unlock($mysql_dbh);
119
+ $Debug and warn "$Prog: ", scalar localtime, ": MySQL disconnect\n";
120
+ $mysql_dbh->disconnect;
121
+ }
122
+
123
+ $Debug and warn "$Prog: ", scalar localtime, ": done\n";
124
+ }
125
+
126
+ #---- METHODS ----
127
+
128
+ sub ebs_snapshot {
129
+ my ($volume_ids, $region, $description) = @_;
130
+
131
+ $Debug and warn "$Prog: ", scalar localtime, ": create EC2 object\n";
132
+
133
+ # EC2 API object
134
+ my $ec2 = Net::Amazon::EC2->new(
135
+ AWSAccessKeyId => $aws_access_key_id,
136
+ SecretAccessKey => $aws_secret_access_key,
137
+ ($region ? (base_url => "http://$region.ec2.amazonaws.com") : ()),
138
+ # ($Debug ? (debug => 1) : ()),
139
+ );
140
+
141
+ VOLUME:
142
+ for my $volume_id ( @$volume_ids ) {
143
+ # Snapshot
144
+ $Debug and
145
+ warn "$Prog: ", scalar localtime, ": ec2-create-snapshot $volume_id\n";
146
+ if ( $Noaction ) {
147
+ warn "snapshot SKIPPED (--noaction)\n";
148
+ next VOLUME;
149
+ }
150
+
151
+ my $snapshot;
152
+ eval {
153
+ ualarm($snapshot_timeout * 1_000_000);
154
+ $snapshot = $ec2->create_snapshot(
155
+ VolumeId => $volume_id,
156
+ Description => $description,
157
+ );
158
+ ualarm(0);
159
+ };
160
+ ualarm(0);
161
+ if ( $@ ){
162
+ warn "$Prog: ERROR: create_snapshot: $@";
163
+ return undef;
164
+ }
165
+
166
+ my $snapshot_id;
167
+ if ( not defined $snapshot ) {
168
+ warn "$Prog: ERROR: create_snapshot returned undef\n";
169
+ } elsif ( not ref $snapshot ) {
170
+ warn "$Prog: ERROR: create_snapshot returned '$snapshot'\n";
171
+ } elsif ( $snapshot->can('snapshot_id') ) {
172
+ $snapshot_id = $snapshot->snapshot_id;
173
+ $Quiet or print "$snapshot_id\n";
174
+ } else {
175
+ for my $error ( @{$snapshot->errors} ) {
176
+ warn "$Prog: ERROR: ".$error->message."\n";
177
+ }
178
+ }
179
+ }
180
+ }
181
+
182
+ sub mysql_connect {
183
+ my ($mysql_host, $mysql_username, $mysql_password) = @_;
184
+
185
+ require DBI;
186
+ DBI->import;
187
+
188
+ if ( defined($mysql_defaults_file) )
189
+ {
190
+ ($mysql_host, $mysql_username, $mysql_password) = read_mydefaultsfile(
191
+ $mysql_defaults_file,
192
+ );
193
+ } else {
194
+ ($mysql_host, $mysql_username, $mysql_password) = read_mycnf(
195
+ $mysql_host, $mysql_username, $mysql_password,
196
+ );
197
+ }
198
+
199
+ $mysql_host ||= 'localhost';
200
+
201
+ $Debug and warn "$Prog: ", scalar localtime,
202
+ ": MySQL connect as $mysql_username\n";
203
+
204
+ my $mysql_dbh = DBI->connect("DBI:mysql:;host=$mysql_host",
205
+ $mysql_username, $mysql_password)
206
+ or die "$Prog: ERROR: Unable to connect to MySQL"
207
+ . " on $mysql_host as $mysql_username";
208
+
209
+ return $mysql_dbh;
210
+ }
211
+
212
+ sub mysql_lock {
213
+ my ($mysql_dbh) = @_;
214
+
215
+ # Don't pass FLUSH TABLES statements on to replication slaves
216
+ # as this can interfere with long-running queries on the slaves.
217
+ $mysql_dbh->do(q{ SET SQL_LOG_BIN=0 }) unless $Noaction;
218
+
219
+ # Try a flush first without locking so the later flush with lock
220
+ # goes faster. This may not be needed as it seems to interfere with
221
+ # some statements anyway.
222
+ sql_timeout_retry(
223
+ q{ FLUSH LOCAL TABLES },
224
+ "MySQL flush",
225
+ $lock_timeout,
226
+ $lock_tries,
227
+ $lock_sleep,
228
+ );
229
+
230
+ # Get a lock on the entire database
231
+ sql_timeout_retry(
232
+ q{ FLUSH LOCAL TABLES WITH READ LOCK },
233
+ "MySQL flush & lock",
234
+ $lock_timeout,
235
+ $lock_tries,
236
+ $lock_sleep,
237
+ );
238
+
239
+ my ($mysql_logfile, $mysql_position,
240
+ $mysql_binlog_do_db, $mysql_binlog_ignore_db);
241
+ if ( not $Noaction ) {
242
+ # This might be a slave database already
243
+ my $slave_status = $mysql_dbh->selectrow_hashref(q{ SHOW SLAVE STATUS });
244
+ $mysql_logfile = $slave_status->{Master_Log_File};
245
+ $mysql_position = $slave_status->{Read_Master_Log_Pos};
246
+ $mysql_binlog_do_db = $slave_status->{Replicate_Do_DB};
247
+ $mysql_binlog_ignore_db = $slave_status->{Replicate_Ignore_DB};
248
+
249
+ # or this might be the master
250
+ ($mysql_logfile, $mysql_position,
251
+ $mysql_binlog_do_db, $mysql_binlog_ignore_db) =
252
+ $mysql_dbh->selectrow_array(q{ SHOW MASTER STATUS })
253
+ unless $mysql_logfile;
254
+ }
255
+
256
+ $mysql_dbh->do(q{ SET SQL_LOG_BIN=1 }) unless $Noaction;
257
+
258
+ print "$Prog: master_log_file=\"$mysql_logfile\",",
259
+ " master_log_pos=$mysql_position\n"
260
+ if $mysql_logfile and not $Quiet;
261
+
262
+ if ( defined($mysql_master_status_file) && $mysql_logfile ) {
263
+ $Debug and warn "$Prog: ", scalar localtime,
264
+ ": writing MASTER STATUS to $mysql_master_status_file\n";
265
+ if ( not $Noaction ) {
266
+ open(MYSQLMASTERSTATUS,"> $mysql_master_status_file") or
267
+ die "$Prog: Unable to open for write: $mysql_master_status_file: $!\n";
268
+ print MYSQLMASTERSTATUS <<"EOM";
269
+ master_log_file="$mysql_logfile"
270
+ master_log_pos="$mysql_position"
271
+ master_binlog_do_db="$mysql_binlog_do_db"
272
+ master_binlog_ignore_db="$mysql_binlog_ignore_db"
273
+ EOM
274
+ close(MYSQLMASTERSTATUS);
275
+ }
276
+ }
277
+
278
+ return ($mysql_logfile, $mysql_position);
279
+ }
280
+
281
+ sub mysql_unlock {
282
+ my ($mysql_dbh) = @_;
283
+
284
+ $Debug and warn "$Prog: ", scalar localtime, ": MySQL unlock\n";
285
+ $mysql_dbh->do(q{ UNLOCK TABLES }) unless $Noaction;
286
+
287
+ }
288
+
289
+ sub mysql_stop {
290
+ $Debug and warn "$Prog: ", scalar localtime, ": MySQL stop\n";
291
+
292
+ my $result = system('/usr/bin/mysqladmin', 'shutdown');
293
+
294
+ if ( $result != 0 ) {
295
+ die "$Prog: Unable to stop mysqld: $? $!";
296
+ }
297
+
298
+ return 1;
299
+ }
300
+
301
+ sub mysql_start {
302
+ $Debug and warn "$Prog: ", scalar localtime, ": MySQL start\n";
303
+
304
+ my $result = system('/etc/init.d/mysql', 'start');
305
+
306
+ if ( $result != 0 ) {
307
+ warn "$Prog: Unable to start mysqld: $? $!";
308
+ }
309
+ }
310
+
311
+ # See also:
312
+ #http://search.cpan.org/dist/DBI/DBI.pm#Signal_Handling_and_Canceling_Operations
313
+ sub sql_timeout_retry {
314
+ my ($sql, $description, $lock_timeout, $lock_tries, $lock_sleep) = @_;
315
+
316
+ my $action = POSIX::SigAction->new(
317
+ sub { die "timeout" },
318
+ POSIX::SigSet->new( SIGALRM ),
319
+ );
320
+ my $oldaction = POSIX::SigAction->new();
321
+ sigaction($SIGALRM, $action, $oldaction);
322
+ LOCK:
323
+ while ( $lock_tries -- ) {
324
+ $Debug and warn "$Prog: ", scalar localtime, ": $description\n";
325
+ eval {
326
+ ualarm($lock_timeout * 1_000_000);
327
+ $mysql_dbh->do($sql) unless $Noaction;
328
+ ualarm(0);
329
+ };
330
+ ualarm(0);
331
+ last LOCK unless $@ =~ /timeout/;
332
+
333
+ } continue {
334
+ $Quiet or warn "$Prog: MySQL timeout at $lock_timeout sec\n";
335
+ $Debug and warn "$Prog: Trying again in $lock_sleep seconds\n";
336
+ usleep($lock_sleep * 1_000_000);
337
+ #TBD: May need to reopen $mysql_dbh here as the handle may not be clean.
338
+ }
339
+ sigaction($SIGALRM, $oldaction);
340
+ die "$Prog: ERROR: MySQL failure: $@" if $@;
341
+ }
342
+
343
+ sub xfs_freeze {
344
+ my ($xfs_filesystem) = @_;
345
+
346
+ run_command(['xfs_freeze', '-f', $xfs_filesystem]);
347
+ }
348
+
349
+ sub xfs_thaw {
350
+ my ($xfs_filesystem) = @_;
351
+
352
+ run_command(['xfs_freeze', '-u', $xfs_filesystem]);
353
+ }
354
+
355
+ # mysql defaults-file
356
+ sub read_mydefaultsfile {
357
+ my ($mysql_defaults_file) = @_;
358
+
359
+ open(MYCNF, $mysql_defaults_file)
360
+ or die "$Prog: ERROR: Couldn't open defaults-file $mysql_defaults_file\n";
361
+ while ( defined (my $line = <MYCNF>) ) {
362
+ $mysql_host = $1 if $line =~ m%^\s*host\s*=\s*"?(\S+?)"?$%
363
+ and not defined $mysql_host;
364
+ $mysql_username = $1 if $line =~ m%^\s*user\s*=\s*"?(\S+?)"?$%
365
+ and not defined $mysql_username;
366
+ $mysql_password = $1 if $line =~ m%^\s*password\s*=\s*"?(\S+?)"?$%
367
+ and not defined $mysql_password;
368
+ }
369
+ close(MYCNF);
370
+
371
+ return ($mysql_host, $mysql_username, $mysql_password);
372
+ }
373
+
374
+ # Look for the MySQL username/password in $HOME/.my.cnf
375
+ sub read_mycnf {
376
+ my ($mysql_host, $mysql_username, $mysql_password) = @_;
377
+
378
+ open(MYCNF, "$ENV{HOME}/.my.cnf")
379
+ or return($mysql_host, $mysql_username, $mysql_password);
380
+ while ( defined (my $line = <MYCNF>) ) {
381
+ $mysql_host = $1 if $line =~ m%^\s*host\s*=\s*"?(\S+?)"?$%
382
+ and not defined $mysql_host;
383
+ $mysql_username = $1 if $line =~ m%^\s*user\s*=\s*"?(\S+?)"?$%
384
+ and not defined $mysql_username;
385
+ $mysql_password = $1 if $line =~ m%^\s*password\s*=\s*"?(\S+?)"?$%
386
+ and not defined $mysql_password;
387
+ }
388
+ close(MYCNF);
389
+
390
+ return ($mysql_host, $mysql_username, $mysql_password);
391
+ }
392
+
393
+
394
+ # Figure out which AWS access keys to use
395
+ sub determine_access_keys {
396
+ my ($aws_access_key_id, $aws_secret_access_key,
397
+ $aws_access_key_id_file, $aws_secret_access_key_file,
398
+ ) = @_;
399
+
400
+ # 1. --aws-access-key-id and --aws-secret-access-key
401
+ return ($aws_access_key_id, $aws_secret_access_key)
402
+ if $aws_access_key_id;
403
+
404
+ # 2. --aws-access-key-id-file and --aws-secret-access-key-file
405
+ if ( $aws_access_key_id_file ) {
406
+ die "$Prog: Please provide both --aws-access-key-id-file and --aws-secret-access-key-file"
407
+ unless $aws_secret_access_key_file;
408
+ $aws_access_key_id = File::Slurp::read_file($aws_access_key_id_file);
409
+ $aws_secret_access_key= File::Slurp::read_file($aws_secret_access_key_file);
410
+ chomp($aws_access_key_id);
411
+ chomp($aws_secret_access_key);
412
+ return ($aws_access_key_id, $aws_secret_access_key);
413
+ }
414
+
415
+ # 3. $HOME/.awssecret
416
+ return read_awssecret();
417
+ }
418
+
419
+
420
+ # Look for the access keys in ~/.awssecret
421
+ sub read_awssecret {
422
+ my $filename = "$ENV{HOME}/.awssecret";
423
+ my ($aws_access_key_id, $aws_secret_access_key);
424
+ eval {
425
+ ($aws_access_key_id, $aws_secret_access_key) =
426
+ File::Slurp::read_file($filename);
427
+ chomp $aws_access_key_id;
428
+ chomp $aws_secret_access_key;
429
+ };
430
+ return ($aws_access_key_id, $aws_secret_access_key);
431
+ }
432
+
433
+ # Run a system command, warning if there are problems.
434
+ # Pass in reference to an array of command args.
435
+ sub run_command {
436
+ my ($command) = @_;
437
+
438
+ $Debug and warn "$Prog: ", scalar localtime, ": @$command\n";
439
+ return if $Noaction;
440
+ eval {
441
+ system(@$command) and die "failed($?)\n";
442
+ };
443
+ warn "$Prog: ERROR: @$command: $@" if $@;
444
+ }
445
+
446
+
447
+ =head1 NAME
448
+
449
+ ec2-consistent-snapshot - Create snapshot, locking db and freezing file system
450
+
451
+ =head1 SYNOPSIS
452
+
453
+ ec2-consistent-snapshot [opts] VOLUMEID...
454
+
455
+ =head1 OPTIONS
456
+
457
+ -h --help Print help and exit.
458
+ -d --debug Debug mode.
459
+ -q --quiet Quiet mode.
460
+ -n --noaction Don't do it. Just say what you would have done.
461
+
462
+ --aws-access-key-id KEY
463
+ --aws-secret-access-key SECRET
464
+
465
+ Amazon AWS access key and secret access key. Defaults to
466
+ environment variables or .awssecret file contents described below.
467
+
468
+ --aws-access-key-id-file KEYFILE
469
+ --aws-secret-access-key-file SECRETFILE
470
+
471
+ Files containing Amazon AWS access key and secret access key.
472
+ Defaults to environment variables or .awssecret file contents
473
+ described below.
474
+
475
+ --region REGION
476
+
477
+ Specify a different region like "eu-west-1". Defaults to
478
+ "us-east-1".
479
+
480
+ --description DESCRIPTION
481
+
482
+ Specify a description string for the EBS snapshot. Defaults to the
483
+ name of the program.
484
+
485
+ --xfs-filesystem MOUNTPOINT
486
+
487
+ Indicates that the volume contains an XFS file system at the specified
488
+ mount point, which will be flushed and frozen during the snapshot.
489
+
490
+ --mysql
491
+
492
+ Indicates that the volume contains data files for a running MySQL
493
+ database, which will be flushed and locked during the snapshot.
494
+
495
+ --mysql-defaults-file FILE
496
+
497
+ MySQL defaults file, containing host, username and password, this
498
+ option will ignore the --mysql-host, --mysql-username,
499
+ --mysql-password parameters
500
+
501
+ --mysql-host HOST
502
+ --mysql-username USER
503
+ --mysql-password PASS
504
+
505
+ MySQL host, username, and password used to flush logs and lock
506
+ tables. User must have appropriate permissions. Defaults to
507
+ $HOME/.my.cnf file contents.
508
+
509
+ --mysql-master-status-file FILE
510
+
511
+ Store the MASTER STATUS output in a file on the snapshot. It will
512
+ be removed after the EBS snapshot is taken. This option will be
513
+ ignored with --mysql-stop
514
+
515
+ --mysql-stop
516
+
517
+ Indicates that the volume contains data files for a running MySQL
518
+ database. The database is shutdown before the snapshot is initiated
519
+ and restarted afterwards. [EXPERIMENTAL]
520
+
521
+ --snapshot-timeout SECONDS
522
+
523
+ How many seconds to wait for the snapshot-create to return.
524
+ Defaults to 10.0
525
+
526
+ --lock-timeout SECONDS
527
+
528
+ How many seconds to wait for a database lock. Defaults to 0.5.
529
+ Making this too large can force other processes to wait while this
530
+ process waits for a lock. Better to make it small and try lots of
531
+ times.
532
+
533
+ --lock-tries COUNT
534
+
535
+ How many times to try to get a database lock before failing.
536
+ Defaults to 60.
537
+
538
+ --lock-sleep SECONDS
539
+
540
+ How many seconds to sleep between database lock tries. Defaults
541
+ to 5.0.
542
+
543
+ =head1 ARGUMENTS
544
+
545
+ VOLUMEID EBS volume id(s) for which to create a snapshot.
546
+
547
+ =head1 DESCRIPTION
548
+
549
+ This program creates an EBS snapshot for an Amazon EC2 EBS volume. To
550
+ help ensure consistent data in the snapshot, it tries to flush and
551
+ freeze the file system first as well as flushing and locking the
552
+ database, if applicable.
553
+
554
+ There are a number of timeouts to reduce the risk of interfering with
555
+ the normal database operation while improving the chances of getting a
556
+ consistent snapshot.
557
+
558
+ If you have multiple EBS volumes in a RAID configuration, you can
559
+ specify all of the volume ids on the command line and it will create
560
+ snapshots for each while the filesystem and database are locked. Note
561
+ that it is your responsibility to keep track of the resulting snapshot
562
+ ids and to figure out how to put these back together when you need to
563
+ restore the RAID setup.
564
+
565
+ If you have multiple EBS volumes which are hosting different file
566
+ systems, it might be better to simply run the command once for each
567
+ volume id.
568
+
569
+ =head1 EXAMPLES
570
+
571
+ Snapshot a volume which has a MySQL database on XFS under /vol:
572
+
573
+ ec2-consistent-snapshot --mysql --xfs-filesystem /vol vol-VOLUMEID
574
+
575
+ Snapshot a volume with XFS on /var/local but no MySQL database:
576
+
577
+ ec2-consistent-snapshot --xfs-filesystem /var/local vol-VOLUMEID
578
+
579
+ Snapshot four European volumes in a RAID configuration with MySQL on
580
+ XFS, saving the snapshots with a description marking the current time:
581
+
582
+ ec2-consistent-snapshot \
583
+ --mysql \
584
+ --xfs-filesystem /vol \
585
+ --region eu-west-1 \
586
+ --description "RAID snapshot $(date +'%Y-%m-%d %H:%M:%S')" \
587
+ vol-VOL1 vol-VOL2 vol-VOL3 vol-VOL4
588
+
589
+ =head1 ENVIRONMENT
590
+
591
+ $AWS_ACCESS_KEY_ID
592
+
593
+ Default value for access key
594
+ Can be overridden by command line options.
595
+
596
+ $AWS_SECRET_ACCESS_KEY
597
+
598
+ Default value for secret access key
599
+ Can be overridden by command line options.
600
+
601
+ =head1 FILES
602
+
603
+ $HOME/.my.cnf
604
+
605
+ Default values for MySQL user and password are sought here in the
606
+ standard format.
607
+
608
+ $HOME/.awssecret
609
+
610
+ Default values for access key and secret access keys are sought
611
+ here. Can be overridden by environment variables and command line
612
+ options.
613
+
614
+ =head1 INSTALLATION
615
+
616
+ On most Ubuntu releases, the B<ec2-consistent-snapshot> package can be
617
+ installed directly from the Alestic.com PPA using the following
618
+ commands:
619
+
620
+ code=$(lsb_release -cs)
621
+ echo "deb http://ppa.launchpad.net/alestic/ppa/ubuntu $code main"|
622
+ sudo tee /etc/apt/sources.list.d/alestic-ppa.list
623
+ sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys BE09C571
624
+ sudo apt-get update
625
+ sudo apt-get install -y ec2-consistent-snapshot
626
+
627
+ This program also requires the installation of the Net::Amazon::EC2
628
+ Perl package from CPAN. On an Ubuntu system this can be accomplished
629
+ with the following commands.:
630
+
631
+ sudo PERL_MM_USE_DEFAULT=1 cpan Net::Amazon::EC2
632
+
633
+ On Ubuntu 8.04 Hardy, use the following commands instead:
634
+
635
+ sudo apt-get install -y build-essential
636
+ sudo cpan Net::Amazon::EC2
637
+
638
+ The default values can be accepted for most of the prompts, though it
639
+ is necessary to select a CPAN mirror on Hardy.
640
+
641
+ =head1 SEE ALSO
642
+
643
+ Amazon EC2
644
+ Amazon EC2 EBS (Elastic Block Store)
645
+ ec2-create-snapshot
646
+
647
+ =head1 CAVEATS
648
+
649
+ This program currently supports the MySQL database and the XFS file
650
+ system. Patches are welcomed for other databases and file systems.
651
+
652
+ =head1 BUGS
653
+
654
+ Please report bugs at https://bugs.launchpad.net/ec2-consistent-snapshot
655
+
656
+ =head1 CREDITS
657
+
658
+ Thanks to the following for performing tests on early versions,
659
+ providing feedback, and patches:
660
+
661
+ David Erickson
662
+ Steve Caldwell
663
+ Gryp
664
+
665
+ =head1 AUTHOR
666
+
667
+ Eric Hammond <ehammond@thinksome.com>
668
+
669
+ =head1 LICENSE
670
+
671
+ Copyright (C) 2009 Eric Hammond <ehammond@thinksome.com>
672
+
673
+ Licensed under the Apache License, Version 2.0, see
674
+ http://www.apache.org/licenses/LICENSE-2.0
675
+
676
+ =cut