capistrano-autoscaling 0.0.1

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.
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: []