capistrano-autoscaling 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in capistrano-autoscaling.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Yamashita Yuu
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,60 @@
1
+ # capistrano-autoscaling
2
+
3
+ A Capistrano recipe that configures [AutoScaling](http://aws.amazon.com/autoscaling/) on [Amazon Web Services](http://aws.amazon.com/) infrastructure for your application.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'capistrano-autoscaling'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install capistrano-autoscaling
18
+
19
+ ## Usage
20
+
21
+ This recipe will try to setup AutoScaling for your appliction. The following actions are prepared to be invoked from Capistrano.
22
+
23
+ * Create ELB for your application
24
+ * Create AMI from EC2 instances behind your ELB
25
+ * Create launch configurations from AMI of your application
26
+ * Create auto scaling group for your application
27
+
28
+ To enable this recipe, add following in your `config/deploy.rb`.
29
+
30
+ # in "config/deploy.rb"
31
+ require "capistrano-autoscaling"
32
+ set(:autoscaling_region, "ap-northeast-1")
33
+ set(:autoscaling_access_key_id, "PUTYOURAWSACCESSKEYIDHERE")
34
+ set(:autoscaling_secret_access_key, "PUTYOURAWSSECRETACCESSKEYHERE")
35
+ set(:autoscaling_instance_type, "m1.small")
36
+ set(:autoscaling_security_groups, %w(default))
37
+ set(:autoscaling_min_size, 2)
38
+ set(:autoscaling_max_size, 10)
39
+ after "deploy:setup", "autoscaling:setup"
40
+ after "deploy", "autoscaling:update"
41
+
42
+ TODO: Write usage instructions here
43
+
44
+
45
+ ## Contributing
46
+
47
+ 1. Fork it
48
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
49
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
50
+ 4. Push to the branch (`git push origin my-new-feature`)
51
+ 5. Create new Pull Request
52
+
53
+ ## Author
54
+
55
+ - YAMASHITA Yuu (https://github.com/yyuu)
56
+ - Geisha Tokyo Entertainment Inc. (http://www.geishatokyo.com/)
57
+
58
+ ## License
59
+
60
+ MIT
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'capistrano-autoscaling/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "capistrano-autoscaling"
8
+ gem.version = Capistrano::AutoScaling::VERSION
9
+ gem.authors = ["Yamashita Yuu"]
10
+ gem.email = ["yamashita@geishatokyo.com"]
11
+ gem.description = %q{A Capistrano recipe that configures AutoScaling on Amazon Web Services infrastructure for your application.}
12
+ gem.summary = %q{A Capistrano recipe that configures AutoScaling on Amazon Web Services infrastructure for your application.}
13
+ gem.homepage = "https://github.com/yyuu/capistrano-autoscaling"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_dependency("capistrano")
21
+ gem.add_dependency("aws-sdk", ">= 1.5.4")
22
+ end
@@ -0,0 +1,602 @@
1
+ require "aws"
2
+ require "capistrano-autoscaling/version"
3
+ require "yaml"
4
+
5
+ module Capistrano
6
+ module AutoScaling
7
+ def self.extended(configuration)
8
+ configuration.load {
9
+ namespace(:autoscaling) {
10
+ ## AWS
11
+ _cset(:autoscaling_region, nil)
12
+ _cset(:autoscaling_autoscaling_endpoint) {
13
+ case autoscaling_region
14
+ when "us-east-1" then "autoscaling.us-east-1.amazonaws.com"
15
+ when "us-west-1" then "autoscaling.us-west-1.amazonaws.com"
16
+ when "us-west-2" then "autoscaling.us-west-2.amazonaws.com"
17
+ when "sa-east-1" then "autoscaling.sa-east-1.amazonaws.com"
18
+ when "eu-west-1" then "autoscaling.eu-west-1.amazonaws.com"
19
+ when "ap-southeast-1" then "autoscaling.ap-southeast-1.amazonaws.com"
20
+ when "ap-southeast-2" then "autoscaling.ap-southeast-2.amazonaws.com"
21
+ when "ap-northeast-1" then "autoscaling.ap-northeast-1.amazonaws.com"
22
+ end
23
+ }
24
+ _cset(:autoscaling_cloudwatch_endpoint) {
25
+ case autoscaling_region
26
+ when "us-east-1" then "monitoring.us-east-1.amazonaws.com"
27
+ when "us-west-1" then "monitoring.us-west-1.amazonaws.com"
28
+ when "us-west-2" then "monitoring.us-west-2.amazonaws.com"
29
+ when "sa-east-1" then "monitoring.sa-east-1.amazonaws.com"
30
+ when "eu-west-1" then "monitoring.eu-west-1.amazonaws.com"
31
+ when "ap-southeast-1" then "monitoring.ap-southeast-1.amazonaws.com"
32
+ when "ap-southeast-2" then "monitoring.ap-southeast-2.amazonaws.com"
33
+ when "ap-northeast-1" then "monitoring.ap-northeast-1.amazonaws.com"
34
+ end
35
+ }
36
+ _cset(:autoscaling_ec2_endpoint) {
37
+ case autoscaling_region
38
+ when "us-east-1" then "ec2.us-east-1.amazonaws.com"
39
+ when "us-west-1" then "ec2.us-west-1.amazonaws.com"
40
+ when "us-west-2" then "ec2.us-west-2.amazonaws.com"
41
+ when "sa-east-1" then "ec2.sa-east-1.amazonaws.com"
42
+ when "eu-west-1" then "ec2.eu-west-1.amazonaws.com"
43
+ when "ap-southeast-1" then "ec2.ap-southeast-1.amazonaws.com"
44
+ when "ap-southeast-2" then "ec2.ap-southeast-2.amazonaws.com"
45
+ when "ap-northeast-1" then "ec2.ap-northeast-1.amazonaws.com"
46
+ end
47
+ }
48
+ _cset(:autoscaling_elb_endpoint) {
49
+ case autoscaling_region
50
+ when "us-east-1" then "elasticloadbalancing.us-east-1.amazonaws.com"
51
+ when "us-west-1" then "elasticloadbalancing.us-west-1.amazonaws.com"
52
+ when "us-west-2" then "elasticloadbalancing.us-west-2.amazonaws.com"
53
+ when "sa-east-1" then "elasticloadbalancing.sa-east-1.amazonaws.com"
54
+ when "eu-west-1" then "elasticloadbalancing.eu-west-1.amazonaws.com"
55
+ when "ap-southeast-1" then "elasticloadbalancing.ap-southeast-1.amazonaws.com"
56
+ when "ap-southeast-2" then "elasticloadbalancing.ap-southeast-2.amazonaws.com"
57
+ when "ap-northeast-1" then "elasticloadbalancing.ap-northeast-1.amazonaws.com"
58
+ end
59
+ }
60
+ _cset(:autoscaling_access_key_id) {
61
+ fetch(:aws_access_key_id, ENV["AWS_ACCESS_KEY_ID"]) or abort("AWS_ACCESS_KEY_ID is not set")
62
+ }
63
+ _cset(:autoscaling_secret_access_key) {
64
+ fetch(:aws_secret_access_key, ENV["AWS_SECRET_ACCESS_KEY"]) or abort("AWS_SECRET_ACCESS_KEY is not set")
65
+ }
66
+ _cset(:autoscaling_aws_options) {
67
+ {
68
+ :access_key_id => autoscaling_access_key_id,
69
+ :secret_access_key => autoscaling_secret_access_key,
70
+ :log_level => fetch(:autoscaling_log_level, :debug),
71
+ :auto_scaling_endpoint => autoscaling_autoscaling_endpoint,
72
+ :cloud_watch_endpoint => autoscaling_cloudwatch_endpoint,
73
+ :ec2_endpoint => autoscaling_ec2_endpoint,
74
+ :elb_endpoint => autoscaling_elb_endpoint,
75
+ }.merge(fetch(:autoscaling_aws_extra_options, {}))
76
+ }
77
+ _cset(:autoscaling_autoscaling_client) { AWS::AutoScaling.new(fetch(:autoscaling_autoscaling_aws_options, autoscaling_aws_options)) }
78
+ _cset(:autoscaling_cloudwatch_client) { AWS::CloudWatch.new(fetch(:autoscaling_cloudwatch_options, autoscaling_aws_options)) }
79
+ _cset(:autoscaling_ec2_client) { AWS::EC2.new(fetch(:autoscaling_ec2_options, autoscaling_aws_options)) }
80
+ _cset(:autoscaling_elb_client) { AWS::ELB.new(fetch(:autoscaling_elb_options, autoscaling_aws_options)) }
81
+
82
+ def autoscaling_name_mangling(s)
83
+ s.to_s.gsub(/[^0-9A-Za-z]/, "-")
84
+ end
85
+
86
+ ## general
87
+ _cset(:autoscaling_application) { autoscaling_name_mangling(application) }
88
+ _cset(:autoscaling_timestamp) { Time.now.strftime("%Y%m%d%H%M%S") }
89
+ _cset(:autoscaling_availability_zones) { autoscaling_ec2_client.availability_zones.to_a.map { |az| az.name } }
90
+ _cset(:autoscaling_wait_interval, 1.0)
91
+ _cset(:autoscaling_keep_images, 2)
92
+ _cset(:autoscaling_instance_type, "t1.micro")
93
+ _cset(:autoscaling_security_groups, %w(default))
94
+ _cset(:autoscaling_min_size, 1)
95
+ _cset(:autoscaling_max_size) { autoscaling_min_size }
96
+
97
+ ## behaviour
98
+ _cset(:autoscaling_create_elb, true)
99
+ _cset(:autoscaling_create_image, true)
100
+ _cset(:autoscaling_create_launch_configuration) {
101
+ autoscaling_create_image or ( autoscaling_image and autoscaling_image.exists? )
102
+ }
103
+ _cset(:autoscaling_create_group) {
104
+ ( autoscaling_create_elb or ( autoscaling_elb_instance and autoscaling_elb_instance.exists? ) ) and
105
+ autoscaling_create_launch_configuration
106
+ }
107
+ _cset(:autoscaling_create_policy) { autoscaling_create_group }
108
+ _cset(:autoscaling_create_alarm) { autoscaling_create_policy }
109
+
110
+ ## ELB
111
+ _cset(:autoscaling_elb_instance_name_prefix, "elb-")
112
+ _cset(:autoscaling_elb_instance_name) { "#{autoscaling_elb_instance_name_prefix}#{autoscaling_application}" }
113
+ _cset(:autoscaling_elb_instance) { autoscaling_elb_client.load_balancers[autoscaling_elb_instance_name] }
114
+ _cset(:autoscaling_elb_listeners) {
115
+ [
116
+ {
117
+ :port => fetch(:autoscaling_elb_port, 80),
118
+ :protocol => fetch(:autoscaling_elb_protocol, :http),
119
+ :instance_port => fetch(:autoscaling_elb_instance_port, 80),
120
+ :instance_protocol => fetch(:autoscaling_elb_instance_protocol, :http),
121
+ },
122
+ ]
123
+ }
124
+ _cset(:autoscaling_elb_instance_options) {
125
+ {
126
+ :availability_zones => fetch(:autoscaling_elb_availability_zones, autoscaling_availability_zones),
127
+ :listeners => autoscaling_elb_listeners,
128
+ }.merge(fetch(:autoscaling_elb_instance_extra_options, {}))
129
+ }
130
+ _cset(:autoscaling_elb_health_check_target_path, "/")
131
+ _cset(:autoscaling_elb_health_check_target) {
132
+ autoscaling_elb_listeners.map { |listener|
133
+ if /^https?$/i =~ listener[:instance_protocol]
134
+ "#{listener[:instance_protocol].to_s.upcase}:#{listener[:instance_port]}#{autoscaling_elb_health_check_target_path}"
135
+ else
136
+ "#{listener[:instance_protocol].to_s.upcase}:#{listener[:instance_port]}"
137
+ end
138
+ }.first
139
+ }
140
+ _cset(:autoscaling_elb_health_check_options) {
141
+ {
142
+ :healthy_threshold => fetch(:autoscaling_elb_healthy_threshold, 10).to_i,
143
+ :unhealthy_threshold => fetch(:autoscaling_elb_unhealthy_threshold, 2).to_i,
144
+ :interval => fetch(:autoscaling_elb_health_check_interval, 30).to_i,
145
+ :timeout => fetch(:autoscaling_elb_health_check_timeout, 5).to_i,
146
+ :target => autoscaling_elb_health_check_target,
147
+ }.merge(fetch(:autoscaling_elb_health_check_extra_options, {}))
148
+ }
149
+
150
+ ## EC2
151
+ _cset(:autoscaling_ec2_instance_name) { autoscaling_application }
152
+ _cset(:autoscaling_ec2_instances) {
153
+ if autoscaling_elb_instance and autoscaling_elb_instance.exists?
154
+ autoscaling_elb_instance.instances.to_a
155
+ else
156
+ abort("ELB is not ready: #{autoscaling_elb_instance_name}")
157
+ end
158
+ }
159
+ _cset(:autoscaling_ec2_instance_dns_names) { autoscaling_ec2_instances.map { |instance| instance.dns_name } }
160
+ _cset(:autoscaling_ec2_instance_private_dns_names) { autoscaling_ec2_instances.map { |instance| instance.private_dns_name } }
161
+
162
+ ## AMI
163
+ _cset(:autoscaling_image_name) { "#{autoscaling_ec2_instance_name}/#{autoscaling_timestamp}" }
164
+ _cset(:autoscaling_image_instance) {
165
+ if 0 < autoscaling_ec2_instances.length
166
+ autoscaling_ec2_instances.reject { |instance| instance.root_device_type != :ebs }.last
167
+ else
168
+ abort("No EC2 instances are ready to create AMI.")
169
+ end
170
+ }
171
+ _cset(:autoscaling_image_options) {
172
+ { :no_reboot => true }.merge(fetch(:autoscaling_image_extra_options, {}))
173
+ }
174
+ _cset(:autoscaling_image_tag_name) { autoscaling_application }
175
+ _cset(:autoscaling_image) {
176
+ autoscaling_ec2_client.images.with_owner("self").tagged("Name").tagged_values(autoscaling_image_name).to_a.first
177
+ }
178
+ _cset(:autoscaling_images) {
179
+ autoscaling_ec2_client.images.with_owner("self").tagged(autoscaling_image_tag_name).reject { |image| image.state != :available }
180
+ }
181
+
182
+ ## LaunchConfiguration
183
+ _cset(:autoscaling_launch_configuration) {
184
+ autoscaling_autoscaling_client.launch_configurations[autoscaling_launch_configuration_name]
185
+ }
186
+ _cset(:autoscaling_launch_configuration_name_prefix, "lc-")
187
+ _cset(:autoscaling_launch_configuration_name) { "#{autoscaling_launch_configuration_name_prefix}#{autoscaling_image_name}" }
188
+ _cset(:autoscaling_launch_configuration_instance_type) { autoscaling_instance_type }
189
+ _cset(:autoscaling_launch_configuration_options) {
190
+ {
191
+ :security_groups => fetch(:autoscaling_launch_configuration_security_groups, autoscaling_security_groups),
192
+ }.merge(fetch(:autoscaling_launch_configuration_extra_options, {}))
193
+ }
194
+
195
+ ## AutoScalingGroup
196
+ _cset(:autoscaling_group_name_prefix, "asg-")
197
+ _cset(:autoscaling_group_name) { "#{autoscaling_group_name_prefix}#{autoscaling_application}" }
198
+ _cset(:autoscaling_group_options) {
199
+ {
200
+ :availability_zones => fetch(:autoscaling_group_availability_zones, autoscaling_availability_zones),
201
+ :min_size => fetch(:autoscaling_group_min_size, autoscaling_min_size),
202
+ :max_size => fetch(:autoscaling_group_max_size, autoscaling_max_size),
203
+ }.merge(fetch(:autoscaling_group_extra_options, {}))
204
+ }
205
+ _cset(:autoscaling_group) { autoscaling_autoscaling_client.groups[autoscaling_group_name] }
206
+
207
+ ## ScalingPolicy
208
+ _cset(:autoscaling_expand_policy_name_prefix, "expand-")
209
+ _cset(:autoscaling_shrink_policy_name_prefix, "shrink-")
210
+ _cset(:autoscaling_expand_policy_name) { "#{autoscaling_expand_policy_name_prefix}#{autoscaling_application}" }
211
+ _cset(:autoscaling_shrink_policy_name) { "#{autoscaling_shrink_policy_name_prefix}#{autoscaling_application}" }
212
+ _cset(:autoscaling_expand_policy_options) {{
213
+ :adjustment => fetch(:autoscaling_expand_policy_adjustment, 1),
214
+ :cooldown => fetch(:autoscaling_expand_policy_cooldown, 300),
215
+ :type => fetch(:autoscaling_expand_policy_type, "ChangeInCapacity"),
216
+ }}
217
+ _cset(:autoscaling_shrink_policy_options) {
218
+ {
219
+ :adjustment => fetch(:autoscaling_shrink_policy_adjustment, -1),
220
+ :cooldown => fetch(:autoscaling_shrink_policy_cooldown, 300),
221
+ :type => fetch(:autoscaling_shrink_policy_type, "ChangeInCapacity"),
222
+ }.merge(fetch(:autoscaling_shrink_policy_extra_options, {}))
223
+ }
224
+ _cset(:autoscaling_expand_policy) { autoscaling_group.scaling_policies[autoscaling_expand_policy_name] }
225
+ _cset(:autoscaling_shrink_policy) { autoscaling_group.scaling_policies[autoscaling_shrink_policy_name] }
226
+
227
+ ## Alarm
228
+ _cset(:autoscaling_expand_alarm_options) {
229
+ {
230
+ :period => fetch(:autoscaling_expand_alarm_period, 60),
231
+ :evaluation_periods => fetch(:autoscaling_expand_alarm_evaluation_periods, 1),
232
+ }.merge(fetch(:autoscaling_expand_alarm_extra_options, {}))
233
+ }
234
+ _cset(:autoscaling_shrink_alarm_options) {
235
+ {
236
+ :period => fetch(:autoscaling_shrink_alarm_period, 60),
237
+ :evaluation_periods => fetch(:autoscaling_shrink_alarm_evaluation_periods, 1),
238
+ }.merge(fetch(:autoscaling_shrink_alarm_extra_options, {}))
239
+ }
240
+ _cset(:autoscaling_expand_alarm_name_prefix, "alarm-expand-")
241
+ _cset(:autoscaling_shrink_alarm_name_prefix, "alarm-shrink-")
242
+ _cset(:autoscaling_expand_alarm_name) { "#{autoscaling_expand_alarm_name_prefix}#{autoscaling_application}" }
243
+ _cset(:autoscaling_shrink_alarm_name) { "#{autoscaling_shrink_alarm_name_prefix}#{autoscaling_application}" }
244
+ _cset(:autoscaling_expand_alarm_definitions) {{
245
+ autoscaling_expand_alarm_name => {
246
+ :statistic => fetch(:autoscaling_expand_alarm_evaluation_statistic, "Average"),
247
+ :namespace => fetch(:autoscaling_expand_alarm_namespace, "AWS/EC2"),
248
+ :metric_name => fetch(:autoscaling_expand_alarm_metric_name, "CPUUtilization"),
249
+ :comparison_operator => fetch(:autoscaling_expand_alarm_comparison_operator, "LessThanThreshold"),
250
+ :threshold => fetch(:autoscaling_expand_alarm_threshold, 30),
251
+ },
252
+ }}
253
+ _cset(:autoscaling_shrink_alarm_definitions) {{
254
+ autoscaling_shrink_alarm_name => {
255
+ :statistic => fetch(:autoscaling_shrink_alarm_evaluation_statistic, "Average"),
256
+ :namespace => fetch(:autoscaling_shrink_alarm_namespace, "AWS/EC2"),
257
+ :metric_name => fetch(:autoscaling_shrink_alarm_metric_name, "CPUUtilization"),
258
+ :comparison_operator => fetch(:autoscaling_shrink_alarm_comparison_operator, "LessThanThreshold"),
259
+ :threshold => fetch(:autoscaling_shrink_alarm_threshold, 30),
260
+ },
261
+ }}
262
+
263
+ desc("Setup AutoScaling.")
264
+ task(:setup, :roles => :app, :except => { :no_release => true }) {
265
+ setup_elb
266
+ }
267
+
268
+ task(:setup_elb, :roles => :app, :except => { :no_release => true }) {
269
+ if autoscaling_create_elb
270
+ if autoscaling_elb_instance and autoscaling_elb_instance.exists?
271
+ logger.debug("Found ELB: #{autoscaling_elb_instance.name}")
272
+ autoscaling_elb_listeners.each do |listener|
273
+ autoscaling_elb_instance.listeners.create(listener)
274
+ end
275
+ else
276
+ logger.debug("Creating ELB instance: #{autoscaling_elb_instance_name}")
277
+ set(:autoscaling_elb_instance, autoscling_elb_client.load_balancers.create(
278
+ autoscaling_elb_instance_name, autoscaling_elb_instance_options))
279
+ sleep(autoscaling_wait_interval) unless autoscaling_elb_instance.exists?
280
+ logger.debug("Created ELB instance: #{autoscaling_elb_instance.name}")
281
+ logger.info("You must setup EC2 instance(s) behind the ELB manually: #{autoscaling_elb_instance_name}")
282
+ end
283
+ logger.debug("Configuring ELB health check: #{autoscaling_elb_instance_name}")
284
+ autoscaling_elb_instance.configure_health_check(autoscaling_elb_health_check_options)
285
+ else
286
+ logger.info("Skip creating ELB instance: #{autoscaling_elb_instance_name}")
287
+ end
288
+ }
289
+
290
+ desc("Remove AutoScaling settings.")
291
+ task(:destroy, :roles => :app, :except => { :no_release => true }) {
292
+ abort("FIXME: Not yet implemented.")
293
+ }
294
+
295
+ desc("Register current instance for AutoScaling.")
296
+ task(:update, :roles => :app, :except => { :no_release => true }) {
297
+ suspend
298
+ update_image
299
+ update_launch_configuration
300
+ update_group
301
+ update_policy
302
+ resume
303
+ }
304
+
305
+ task(:update_image, :roles => :app, :except => { :no_release => true }) {
306
+ if autoscaling_create_image
307
+ if autoscaling_image and autoscaling_image.exists?
308
+ logger.debug("Found AMI: #{autoscaling_image.name} (#{autoscaling_image.id})")
309
+ else
310
+ logger.debug("Creating AMI: #{autoscaling_image_name}")
311
+ run("sync; sync; sync") # force flushing to disk
312
+ set(:autoscaling_image, autoscaling_ec2_client.images.create(
313
+ autoscaling_image_options.merge(:name => autoscaling_image_name, :instance_id => autoscaling_image_instance.id)))
314
+ sleep(autoscaling_wait_interval) until autoscaling_image.exists?
315
+ logger.debug("Created AMI: #{autoscaling_image.name} (#{autoscaling_image.id})")
316
+ [["Name", {:value => autoscaling_image_name}], [autoscaling_image_tag_name]].each do |tag_name, tag_options|
317
+ begin
318
+ if tag_options
319
+ autoscaling_image.add_tag(tag_name, tag_options)
320
+ else
321
+ autoscaling_image.add_tag(tag_name)
322
+ end
323
+ rescue AWS::EC2::Errors::InvalidAMIID::NotFound => error
324
+ logger.info("[ERROR] " + error.inspect)
325
+ sleep(autoscaling_wait_interval)
326
+ retry
327
+ end
328
+ end
329
+ end
330
+ else
331
+ logger.info("Skip creating AMI: #{autoscaling_image_name}")
332
+ end
333
+ }
334
+
335
+ task(:update_launch_configuration, :roles => :app, :except => { :no_release => true }) {
336
+ if autoscaling_create_launch_configuration
337
+ if autoscaling_launch_configuration.exists?
338
+ logger.debug("Found LaunchConfiguration: #{autoscaling_launch_configuration.name} (#{autoscaling_launch_configuration.image_id})")
339
+ else
340
+ logger.debug("Creating LaunchConfiguration: #{autoscaling_launch_configuration_name} (#{autoscaling_image.id})")
341
+ set(:autoscaling_launch_configuration, autoscaling_autoscaling_client.launch_configurations.create(
342
+ autoscaling_launch_configuration_name, autoscaling_image.id, autoscaling_launch_configuration_instance_type,
343
+ autoscaling_launch_configuration_options))
344
+ sleep(autoscaling_wait_interval) unless autoscaling_launch_configuration.exists?
345
+ logger.debug("Created LaunchConfiguration: #{autoscaling_launch_configuration.name} (#{autoscaling_launch_configuration.image_id})")
346
+ end
347
+ else
348
+ logger.info("Skip creating LaunchConfiguration: #{autoscaling_launch_configuration_name}")
349
+ end
350
+ }
351
+
352
+ task(:update_group, :roles => :app, :except => { :no_release => true }) {
353
+ if autoscaling_create_group
354
+ if autoscaling_group and autoscaling_group.exists?
355
+ logger.debug("Found AutoScalingGroup: #{autoscaling_group.name} (#{autoscaling_group.launch_configuration_name})")
356
+ autoscaling_group.update(autoscaling_group_options.merge(:launch_configuration => autoscaling_launch_configuration))
357
+ else
358
+ if autoscaling_elb_instance.exists? and autoscaling_launch_configuration.exists?
359
+ logger.debug("Creating AutoScalingGroup: #{autoscaling_group_name} (#{autoscaling_launch_configuration.name})")
360
+ set(:autoscaling_group, autoscaling_autoscaling_client.groups.create(autoscaling_group_name,
361
+ autoscaling_group_options.merge(:launch_configuration => autoscaling_launch_configuration,
362
+ :load_balancers => [ autoscaling_elb_instance ])))
363
+ logger.debug("Created AutoScalingGroup: #{autoscaling_group.name} (#{autoscaling_group.launch_configuration_name})")
364
+ else
365
+ logger.info("Skip creating AutoScalingGroup: #{autoscaling_group_name} (#{autoscaling_launch_configuration_name})")
366
+ end
367
+ end
368
+ else
369
+ logger.info("Skip creating AutoScalingGroup: #{autoscaling_group_name}")
370
+ end
371
+ }
372
+
373
+ task(:update_policy, :roles => :app, :except => { :no_release => true }) {
374
+ if autoscaling_create_policy
375
+ if autoscaling_expand_policy and autoscaling_expand_policy.exists?
376
+ logger.debug("Found ScalingPolicy for expansion: #{autoscaling_expand_policy.name}")
377
+ else
378
+ logger.debug("Createing ScalingPolicy for expansion: #{autoscaling_expand_policy_name}")
379
+ set(:autoscaling_expand_policy, autoscaling_group.scaling_policies.create(autoscaling_expand_policy_name,
380
+ autoscaling_expand_policy_options))
381
+ sleep(autoscaling_wait_interval) unless autoscaling_expand_policy.exists?
382
+ logger.debug("Created ScalingPolicy for expansion: #{autoscaling_expand_policy.name}")
383
+ end
384
+ else
385
+ logger.info("Skip creating ScalingPolicy for expansion: #{autoscaling_expand_policy_name}")
386
+ end
387
+
388
+ if autoscaling_create_policy
389
+ if autoscaling_shrink_policy and autoscaling_shrink_policy.exists?
390
+ logger.debug("Found ScalingPolicy for shrinking: #{autoscaling_shrink_policy.name}")
391
+ else
392
+ logger.debug("Createing ScalingPolicy for shrinking: #{autoscaling_shrink_policy_name}")
393
+ set(:autoscaling_shrink_policy, autoscaling_group.scaling_policies.create(autoscaling_shrink_policy_name,
394
+ autoscaling_shrink_policy_options))
395
+ sleep(autoscaling_wait_interval) unless autoscaling_shrink_policy.exists?
396
+ logger.debug("Created ScalingPolicy for shrinking: #{autoscaling_shrink_policy.name}")
397
+ end
398
+ else
399
+ logger.info("Skip creating ScalingPolicy for shrinking: #{autoscaling_shrink_policy_name}")
400
+ end
401
+ }
402
+
403
+ def autoscaling_default_alarm_dimensions(namespace)
404
+ case namespace
405
+ when %r{AWS/EC2}i
406
+ [{"Name" => "AutoScalingGroupName", "Value" => autoscaling_group_name}]
407
+ when %r{AWS/ELB}i
408
+ [{"Name" => "LoadBalancerName", "Value" => autoscaling_elb_instance_name}]
409
+ else
410
+ abort("Unknown metric namespace to generate dimensions: #{namespace}")
411
+ end
412
+ end
413
+
414
+ task(:update_alarm, :roles => :app, :except => { :no_release => true }) {
415
+ if autoscaling_create_alarm
416
+ autoscaling_expand_alarm_definitions.each do |alarm_name, alarm_options|
417
+ alarm = autoscaling_cloudwatch_client.alarms[alarm_name]
418
+ if alarm and alarm.exists?
419
+ logger.debug("Found Alarm for expansion: #{alarm.name}")
420
+ else
421
+ logger.debug("Creating Alarm for expansion: #{alarm_name}")
422
+ options = autoscaling_expand_alarm_options.merge(alarm_options)
423
+ options[:alarm_actions] = [ autoscaling_expand_policy.arn ] unless options.has_key?(:alarm_actions)
424
+ options[:dimensions] = autoscaling_default_alarm_dimensions(options[:namespace]) unless options.has_key?(:dimensions)
425
+ alarm = autoscaling_cloudwatch_client.alarms.create(alarm_name, options)
426
+ logger.debug("Created Alarm for expansion: #{alarm.name}")
427
+ end
428
+ end
429
+ else
430
+ logger.info("Skip creating Alarm for expansion")
431
+ end
432
+
433
+ if autoscaling_create_alarm
434
+ autoscaling_shrink_alarm_definitions.each do |alarm_name, alarm_options|
435
+ alarm = autoscaling_cloudwatch_client.alarms[alarm_name]
436
+ if alarm and alarm.exists?
437
+ logger.debug("Found Alarm for shrinking: #{alarm.name}")
438
+ else
439
+ logger.debug("Creating Alarm for shrinking: #{alarm_name}")
440
+ options = autoscaling_shrink_alarm_options.merge(alarm_options)
441
+ options[:alarm_actions] = [ autoscaling_shrink_policy.arn ] unless options.has_key?(:alarm_actions)
442
+ options[:dimensions] = autoscaling_default_alarm_dimensions(options[:namespace]) unless options.has_key?(:dimensions)
443
+ alarm = autoscaling_cloudwatch_client.alarms.create(alarm_name, options)
444
+ logger.debug("Created Alarm for shrinking: #{alarm.name}")
445
+ end
446
+ end
447
+ else
448
+ logger.info("Skip creating Alarm for shrinking")
449
+ end
450
+ }
451
+
452
+ desc("Suspend AutoScaling processes.")
453
+ task(:suspend, :roles => :app, :except => { :no_release => true }) {
454
+ if autoscaling_group and autoscaling_group.exists?
455
+ logger.info("Suspending Group: #{autoscaling_group.name}")
456
+ autoscaling_group.suspend_all_processes
457
+ else
458
+ logger.info("Skip suspending AutoScalingGroup: #{autoscaling_group_name}")
459
+ end
460
+ }
461
+
462
+ desc("Resume AutoScaling processes.")
463
+ task(:resume, :roles => :app, :except => { :no_release => true }) {
464
+ if autoscaling_group and autoscaling_group.exists?
465
+ logger.info("Resuming Group: #{autoscaling_group.name}")
466
+ autoscaling_group.resume_all_processes
467
+ else
468
+ logger.info("Skip resuming AutoScalingGroup: #{autoscaling_group_name}")
469
+ end
470
+ }
471
+
472
+ desc("Show AutoScaling status.")
473
+ task(:status, :roles => :app, :except => { :no_release => true }) {
474
+ if autoscaling_group and autoscaling_group.exists?
475
+ STDOUT.puts({
476
+ :availability_zone_names => autoscaling_group.availability_zone_names.to_a,
477
+ :desired_capacity => autoscaling_group.desired_capacity,
478
+ :launch_configuration => {
479
+ :iam_instance_profile => autoscaling_group.launch_configuration.iam_instance_profile,
480
+ :image => {
481
+ :id => autoscaling_group.launch_configuration.image.id,
482
+ :name => autoscaling_group.launch_configuration.image.name,
483
+ :state => autoscaling_group.launch_configuration.image.state,
484
+ },
485
+ :instance_type => autoscaling_group.launch_configuration.instance_type,
486
+ :name => autoscaling_group.launch_configuration.name,
487
+ :security_groups => autoscaling_group.launch_configuration.security_groups.map { |sg| sg.name },
488
+ },
489
+ :load_balancers => autoscaling_group.load_balancers.to_a.map { |lb|
490
+ {
491
+ :availability_zone_names => lb.availability_zone_names.to_a,
492
+ :dns_name => lb.dns_name,
493
+ :instances => lb.instances.map { |i|
494
+ {
495
+ :dns_name => i.dns_name,
496
+ :id => i.id,
497
+ :private_dns_name => i.private_dns_name,
498
+ :status => i.status,
499
+ }
500
+ },
501
+ :name => lb.name,
502
+ }
503
+ },
504
+ :max_size => autoscaling_group.max_size,
505
+ :min_size => autoscaling_group.min_size,
506
+ :name => autoscaling_group.name,
507
+ :scaling_policies => autoscaling_group.scaling_policies.map { |policy|
508
+ {
509
+ :adjustment_type => policy.adjustment_type,
510
+ :alarms => policy.alarms.to_hash.keys,
511
+ :cooldown => policy.cooldown,
512
+ :name => policy.name,
513
+ :scaling_adjustment => policy.scaling_adjustment,
514
+ }
515
+ },
516
+ :scheduled_actions => autoscaling_group.scheduled_actions.map { |action|
517
+ {
518
+ :desired_capacity => action.desired_capacity,
519
+ :end_time => action.end_time,
520
+ :max_size => action.max_size,
521
+ :min_size => action.min_size,
522
+ :name => action.name,
523
+ :start_time => action.start_time,
524
+ }
525
+ },
526
+ :suspended_processes => autoscaling_group.suspended_processes.to_hash,
527
+ }.to_yaml)
528
+ end
529
+ }
530
+
531
+ desc("Show AutoScaling history.")
532
+ task(:history, :roles => :app, :except => { :no_release => true }) {
533
+ if autoscaling_group and autoscaling_group.exists?
534
+ autoscaling_group.scaling_policies.each do |policy|
535
+ policy.alarms.each do |alarm_name, alarm_arn|
536
+ alarm = autoscaling_cloudwatch_client.alarms[alarm_name]
537
+ start_date = Time.now - fetch(:autoscaling_history_days, 3) * 86400
538
+ history_items = alarm.history_items.with_start_date(start_date)
539
+
540
+ STDOUT.puts("--")
541
+ STDOUT.puts("Alarm: #{alarm_name} (ScalingPolicy: #{policy.name})")
542
+ history_items.each do |hi|
543
+ STDOUT.puts("#{hi.timestamp}: #{hi.type}: #{hi.summary}")
544
+ end
545
+ end
546
+ end
547
+ else
548
+ abort("AutoScalingGroup is not ready: #{autoscaling_group_name}")
549
+ end
550
+
551
+ }
552
+
553
+ desc("Delete old AMIs.")
554
+ task(:cleanup, :roles => :app, :except => { :no_release => true }) {
555
+ images = autoscaling_images.sort { |x, y| x.name <=> y.name }.reject { |image|
556
+ autoscaling_group.launch_configuration.image_id == image.id
557
+ }
558
+ (images - images.last(autoscaling_keep_images-1)).each do |image|
559
+ if autoscaling_create_image and ( image and image.exists? )
560
+ snapshots = image.block_device_mappings.map { |device, block_device| block_device.snapshot_id }
561
+ logger.debug("Deregistering AMI: #{image.id}")
562
+ image.deregister()
563
+ sleep(autoscaling_wait_interval) unless image.exists?
564
+
565
+ snapshots.each do |id|
566
+ snapshot = autoscaling_ec2_client.snapshots[id]
567
+ if snapshot and snapshot.exists?
568
+ logger.debug("Deleting EBS snapshot: #{snapshot.id}")
569
+ begin
570
+ snapshot.delete()
571
+ rescue AWS::EC2::Errors::InvalidSnapshot::InUse => error
572
+ logger.info("[ERROR] " + error.inspect)
573
+ sleep(autoscaling_wait_interval)
574
+ retry
575
+ end
576
+ end
577
+ end
578
+ else
579
+ logger.info("Skip deleting AMI: #{image.name} (#{image.id})")
580
+ end
581
+
582
+ launch_configuration_name = "#{autoscaling_launch_configuration_name_prefix}#{image.name}"
583
+ launch_configuration = autoscaling_autoscaling_client.launch_configurations[launch_configuration_name]
584
+ if autoscaling_create_launch_configuration and ( launch_configuration and launch_configuration.exists? )
585
+ logger.debug("Deleting LaunchConfiguration: #{launch_configuration.name}")
586
+ launch_configuration.delete()
587
+ else
588
+ logger.info("Skip deleting LaunchConfiguration: #{launch_configuration_name}")
589
+ end
590
+ end
591
+ }
592
+ }
593
+ }
594
+ end
595
+ end
596
+ end
597
+
598
+ if Capistrano::Configuration.instance
599
+ Capistrano::Configuration.instance.extend(Capistrano::AutoScaling)
600
+ end
601
+
602
+ # vim:set ft=ruby ts=2 sw=2 :
@@ -0,0 +1,5 @@
1
+ module Capistrano
2
+ module AutoScaling
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
metadata ADDED
@@ -0,0 +1,87 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: capistrano-autoscaling
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Yamashita Yuu
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-11-22 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: capistrano
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: aws-sdk
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: 1.5.4
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: 1.5.4
46
+ description: A Capistrano recipe that configures AutoScaling on Amazon Web Services
47
+ infrastructure for your application.
48
+ email:
49
+ - yamashita@geishatokyo.com
50
+ executables: []
51
+ extensions: []
52
+ extra_rdoc_files: []
53
+ files:
54
+ - .gitignore
55
+ - Gemfile
56
+ - LICENSE.txt
57
+ - README.md
58
+ - Rakefile
59
+ - capistrano-autoscaling.gemspec
60
+ - lib/capistrano-autoscaling.rb
61
+ - lib/capistrano-autoscaling/version.rb
62
+ homepage: https://github.com/yyuu/capistrano-autoscaling
63
+ licenses: []
64
+ post_install_message:
65
+ rdoc_options: []
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ! '>='
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ required_rubygems_version: !ruby/object:Gem::Requirement
75
+ none: false
76
+ requirements:
77
+ - - ! '>='
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
80
+ requirements: []
81
+ rubyforge_project:
82
+ rubygems_version: 1.8.23
83
+ signing_key:
84
+ specification_version: 3
85
+ summary: A Capistrano recipe that configures AutoScaling on Amazon Web Services infrastructure
86
+ for your application.
87
+ test_files: []