autocanary24 0.1.0.pre.alpha
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.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/Gemfile +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +74 -0
- data/Rakefile +6 -0
- data/autocanary24.gemspec +33 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/examples/.bundle/config +3 -0
- data/examples/Gemfile +4 -0
- data/examples/Gemfile.lock +26 -0
- data/examples/README.md +0 -0
- data/examples/Rakefile +25 -0
- data/examples/asg-stack.json +326 -0
- data/examples/base-stack.json +59 -0
- data/lib/autocanary24.rb +9 -0
- data/lib/autocanary24/canarystack.rb +178 -0
- data/lib/autocanary24/client.rb +183 -0
- data/lib/autocanary24/configuration.rb +40 -0
- data/lib/autocanary24/version.rb +3 -0
- metadata +109 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 26fa649c88fc42c4c4e77d4a732695f298e9276d
|
4
|
+
data.tar.gz: 24d0de4d0b38ef6ce5f033db668da64cee47405e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 50666494d784f0252d165b4476c6ba7f84fc1f8ed83f919322992f4e43385009f227e178b5f90b2a7a8f5a358e02a6778a3dae0b498c03919e48731b9a3e29ff
|
7
|
+
data.tar.gz: 127c59c643c6077142f821fb9c918916d2dc5a593fc82fe154ed760804ebaf563a382837964232e76636495d71f73818b5a08911f9136ed27d072e998ef01d71
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 AutoScout24 GmbH
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# AutoCanary24
|
2
|
+
|
3
|
+
AutoCanary24 is a ruby utility to do [blue/green](http://martinfowler.com/bliki/BlueGreenDeployment.html) and [canary](http://martinfowler.com/bliki/CanaryRelease.html) deployments with AWS CloudFormation stacks.
|
4
|
+
|
5
|
+
This library use the [Swap AutoScaling Groups](http://www.slideshare.net/AmazonWebServices/dvo401-deep-dive-into-bluegreen-deployments-on-aws/32) approach and expects two stacks. A "base" stack which includes at least the `ELB` and another which includes the `AutoScaling Group`.
|
6
|
+
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
Add this line to your application's Gemfile:
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
gem 'autocanary24'
|
14
|
+
```
|
15
|
+
|
16
|
+
And then execute:
|
17
|
+
|
18
|
+
$ bundle install
|
19
|
+
|
20
|
+
Or install it yourself as:
|
21
|
+
|
22
|
+
$ gem install autocanary24
|
23
|
+
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
Initialize the client, e.g. in your `Rakefile` and deploy your stack:
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
# Deploy the base stack before
|
31
|
+
|
32
|
+
ac = AutoCanary24::Client.new({
|
33
|
+
keep_inactive_stack: false,
|
34
|
+
scaling_instance_percent: 50,
|
35
|
+
keep_instances_balanced: false})
|
36
|
+
|
37
|
+
ac.deploy_stack(parent_stack_name, template, parameters, tags, deployment_check)
|
38
|
+
```
|
39
|
+
|
40
|
+
- `parent_stack_name`: the name of the 'base' stack. In addition `AutoStacker24` will read the output parameters of an existing stack and merge them to the given parameters.
|
41
|
+
- `template`: is either the template json data itself or the name of a file containing the template body
|
42
|
+
- `parameters`: specify the input parameter as a simple ruby hash. It gets converted to the
|
43
|
+
cumbersome AWS format automatically.
|
44
|
+
The template body will be validated and optionally preprocessed.
|
45
|
+
- `tags`: Optional. Key-value pairs to associate with this stack.
|
46
|
+
- 'deployment_check': Optional. A `lambda` which is executed whenever new instances are added to the ELB. If `true` AutoCanary continues, if `false` it will rollback to the current stack.
|
47
|
+
|
48
|
+
> For more information about stacked CloudFormation stacks visit [AutoStacker24](https://github.com/autoscout24/autostacker24).
|
49
|
+
|
50
|
+
The available configuration:
|
51
|
+
- `keep_inactive_stack`: If `true` the inactive stack gets not deleted. Default is `false`
|
52
|
+
- `keep_instances_balanced`: If `true` a instance from current stack gets removed whenever a new instance from the new stack is added. If `false` first all new instances are created and afterwards the old instances gets removed.
|
53
|
+
- `scaling_instance_percent`: Percent of instances which are added at once (depends on the actual number of instances, read from desired)
|
54
|
+
|
55
|
+
|
56
|
+
|
57
|
+
### Examples
|
58
|
+
Have a look into the [examples subfolder](https://github.com/autoscout24/autocanary24/blob/master/examples/)
|
59
|
+
|
60
|
+
## Development
|
61
|
+
|
62
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
63
|
+
|
64
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
65
|
+
|
66
|
+
|
67
|
+
## Contributing
|
68
|
+
|
69
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/autoscout24/autocanary24.
|
70
|
+
|
71
|
+
|
72
|
+
## License
|
73
|
+
|
74
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'autocanary24/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "autocanary24"
|
8
|
+
spec.version = AutoCanary24::VERSION
|
9
|
+
spec.authors = ["Philipp Garbe"]
|
10
|
+
spec.email = ["pgarbe@autoscout24.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{A very narrow interface to AWS ECR}
|
13
|
+
spec.description = %q{autocanary24 provides a small convenient module for blue/green and canary deployments with CloudFormation.}
|
14
|
+
spec.homepage = "https://github.com/autoscout24/autocanary24"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
# Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
|
18
|
+
# delete this section to allow pushing this gem to any host.
|
19
|
+
# if spec.respond_to?(:metadata)
|
20
|
+
# spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'"
|
21
|
+
# else
|
22
|
+
# raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
|
23
|
+
# end
|
24
|
+
|
25
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
26
|
+
spec.bindir = "exe"
|
27
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
28
|
+
spec.require_paths = ["lib"]
|
29
|
+
|
30
|
+
spec.add_development_dependency "bundler", "~> 1.11"
|
31
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
32
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
33
|
+
end
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "autocanary24"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/examples/Gemfile
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
GEM
|
2
|
+
remote: https://rubygems.org/
|
3
|
+
specs:
|
4
|
+
autostacker24 (1.0.71)
|
5
|
+
aws-sdk-core (~> 2)
|
6
|
+
json (~> 1.8)
|
7
|
+
json_pure (~> 1.8)
|
8
|
+
aws-sdk (2.2.30)
|
9
|
+
aws-sdk-resources (= 2.2.30)
|
10
|
+
aws-sdk-core (2.2.30)
|
11
|
+
jmespath (~> 1.0)
|
12
|
+
aws-sdk-resources (2.2.30)
|
13
|
+
aws-sdk-core (= 2.2.30)
|
14
|
+
jmespath (1.1.3)
|
15
|
+
json (1.8.3)
|
16
|
+
json_pure (1.8.3)
|
17
|
+
|
18
|
+
PLATFORMS
|
19
|
+
ruby
|
20
|
+
|
21
|
+
DEPENDENCIES
|
22
|
+
autostacker24
|
23
|
+
aws-sdk (~> 2)
|
24
|
+
|
25
|
+
BUNDLED WITH
|
26
|
+
1.11.2
|
data/examples/README.md
ADDED
File without changes
|
data/examples/Rakefile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require "aws-sdk-core"
|
2
|
+
require "autostacker24"
|
3
|
+
require_relative "../lib/autocanary24/client"
|
4
|
+
|
5
|
+
task :default do
|
6
|
+
|
7
|
+
# 1) Create the base stack which includes at least the ELB
|
8
|
+
parameters = {}
|
9
|
+
Stacker.create_or_update_stack('autocanary24-example', 'base-stack.json', parameters)
|
10
|
+
|
11
|
+
# 2) Create the stack which includes the ASG
|
12
|
+
client = AutoCanary24::Client.new({
|
13
|
+
:keep_inactive_stack => true
|
14
|
+
})
|
15
|
+
|
16
|
+
parameters = {}
|
17
|
+
client.deploy_stack('autocanary24-example', 'asg-stack.json', parameters)
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
task :cleanup do
|
22
|
+
Stacker.delete_stack('autocanary24-example-B')
|
23
|
+
Stacker.delete_stack('autocanary24-example-G')
|
24
|
+
Stacker.delete_stack('autocanary24-example')
|
25
|
+
end
|
@@ -0,0 +1,326 @@
|
|
1
|
+
//AutoStacker24
|
2
|
+
{
|
3
|
+
"AWSTemplateFormatVersion" : "2010-09-09",
|
4
|
+
|
5
|
+
"Description" : "AWS CloudFormation Sample Template AutoScalingMultiAZWithNotifications: Create a multi-az, load balanced and Auto Scaled sample web site running on an Apache Web Serever. The application is configured to span all Availability Zones in the region and is Auto-Scaled based on the CPU utilization of the web servers. Notifications will be sent to the operator email address on scaling events. The instances are load balanced with a simple health check against the default web page. **WARNING** This template creates one or more Amazon EC2 instances and an Elastic Load Balancer. You will be billed for the AWS resources used if you create a stack from this template.",
|
6
|
+
|
7
|
+
"Parameters" : {
|
8
|
+
"InstanceSecurityGroup": {
|
9
|
+
"Type": "String"
|
10
|
+
},
|
11
|
+
"InstanceType" : {
|
12
|
+
"Description" : "WebServer EC2 instance type",
|
13
|
+
"Type" : "String",
|
14
|
+
"Default" : "t2.nano",
|
15
|
+
"AllowedValues" : [ "t1.micro", "t2.nano", "t2.micro", "t2.small", "t2.medium", "t2.large", "m1.small", "m1.medium", "m1.large", "m1.xlarge", "m2.xlarge", "m2.2xlarge", "m2.4xlarge", "m3.medium", "m3.large", "m3.xlarge", "m3.2xlarge", "m4.large", "m4.xlarge", "m4.2xlarge", "m4.4xlarge", "m4.10xlarge", "c1.medium", "c1.xlarge", "c3.large", "c3.xlarge", "c3.2xlarge", "c3.4xlarge", "c3.8xlarge", "c4.large", "c4.xlarge", "c4.2xlarge", "c4.4xlarge", "c4.8xlarge", "g2.2xlarge", "g2.8xlarge", "r3.large", "r3.xlarge", "r3.2xlarge", "r3.4xlarge", "r3.8xlarge", "i2.xlarge", "i2.2xlarge", "i2.4xlarge", "i2.8xlarge", "d2.xlarge", "d2.2xlarge", "d2.4xlarge", "d2.8xlarge", "hi1.4xlarge", "hs1.8xlarge", "cr1.8xlarge", "cc2.8xlarge", "cg1.4xlarge"],
|
16
|
+
"ConstraintDescription" : "must be a valid EC2 instance type."
|
17
|
+
}
|
18
|
+
},
|
19
|
+
|
20
|
+
"Mappings" : {
|
21
|
+
"Region2Examples" : {
|
22
|
+
"us-east-1" : { "Examples" : "https://s3.amazonaws.com/cloudformation-examples-us-east-1" },
|
23
|
+
"us-west-2" : { "Examples" : "https://s3-us-west-2.amazonaws.com/cloudformation-examples-us-west-2" },
|
24
|
+
"us-west-1" : { "Examples" : "https://s3-us-west-1.amazonaws.com/cloudformation-examples-us-west-1" },
|
25
|
+
"eu-west-1" : { "Examples" : "https://s3-eu-west-1.amazonaws.com/cloudformation-examples-eu-west-1" },
|
26
|
+
"eu-central-1" : { "Examples" : "https://s3-eu-central-1.amazonaws.com/cloudformation-examples-eu-central-1" },
|
27
|
+
"ap-southeast-1" : { "Examples" : "https://s3-ap-southeast-1.amazonaws.com/cloudformation-examples-ap-southeast-1" },
|
28
|
+
"ap-northeast-1" : { "Examples" : "https://s3-ap-northeast-1.amazonaws.com/cloudformation-examples-ap-northeast-1" },
|
29
|
+
"ap-northeast-2" : { "Examples" : "https://s3-ap-northeast-2.amazonaws.com/cloudformation-examples-ap-northeast-2" },
|
30
|
+
"ap-southeast-2" : { "Examples" : "https://s3-ap-southeast-2.amazonaws.com/cloudformation-examples-ap-southeast-2" },
|
31
|
+
"sa-east-1" : { "Examples" : "https://s3-sa-east-1.amazonaws.com/cloudformation-examples-sa-east-1" },
|
32
|
+
"cn-north-1" : { "Examples" : "https://s3.cn-north-1.amazonaws.com.cn/cloudformation-examples-cn-north-1" }
|
33
|
+
},
|
34
|
+
"AWSInstanceType2Arch" : {
|
35
|
+
"t1.micro" : { "Arch" : "PV64" },
|
36
|
+
"t2.nano" : { "Arch" : "HVM64" },
|
37
|
+
"t2.micro" : { "Arch" : "HVM64" },
|
38
|
+
"t2.small" : { "Arch" : "HVM64" },
|
39
|
+
"t2.medium" : { "Arch" : "HVM64" },
|
40
|
+
"t2.large" : { "Arch" : "HVM64" },
|
41
|
+
"m1.small" : { "Arch" : "PV64" },
|
42
|
+
"m1.medium" : { "Arch" : "PV64" },
|
43
|
+
"m1.large" : { "Arch" : "PV64" },
|
44
|
+
"m1.xlarge" : { "Arch" : "PV64" },
|
45
|
+
"m2.xlarge" : { "Arch" : "PV64" },
|
46
|
+
"m2.2xlarge" : { "Arch" : "PV64" },
|
47
|
+
"m2.4xlarge" : { "Arch" : "PV64" },
|
48
|
+
"m3.medium" : { "Arch" : "HVM64" },
|
49
|
+
"m3.large" : { "Arch" : "HVM64" },
|
50
|
+
"m3.xlarge" : { "Arch" : "HVM64" },
|
51
|
+
"m3.2xlarge" : { "Arch" : "HVM64" },
|
52
|
+
"m4.large" : { "Arch" : "HVM64" },
|
53
|
+
"m4.xlarge" : { "Arch" : "HVM64" },
|
54
|
+
"m4.2xlarge" : { "Arch" : "HVM64" },
|
55
|
+
"m4.4xlarge" : { "Arch" : "HVM64" },
|
56
|
+
"m4.10xlarge" : { "Arch" : "HVM64" },
|
57
|
+
"c1.medium" : { "Arch" : "PV64" },
|
58
|
+
"c1.xlarge" : { "Arch" : "PV64" },
|
59
|
+
"c3.large" : { "Arch" : "HVM64" },
|
60
|
+
"c3.xlarge" : { "Arch" : "HVM64" },
|
61
|
+
"c3.2xlarge" : { "Arch" : "HVM64" },
|
62
|
+
"c3.4xlarge" : { "Arch" : "HVM64" },
|
63
|
+
"c3.8xlarge" : { "Arch" : "HVM64" },
|
64
|
+
"c4.large" : { "Arch" : "HVM64" },
|
65
|
+
"c4.xlarge" : { "Arch" : "HVM64" },
|
66
|
+
"c4.2xlarge" : { "Arch" : "HVM64" },
|
67
|
+
"c4.4xlarge" : { "Arch" : "HVM64" },
|
68
|
+
"c4.8xlarge" : { "Arch" : "HVM64" },
|
69
|
+
"g2.2xlarge" : { "Arch" : "HVMG2" },
|
70
|
+
"g2.8xlarge" : { "Arch" : "HVMG2" },
|
71
|
+
"r3.large" : { "Arch" : "HVM64" },
|
72
|
+
"r3.xlarge" : { "Arch" : "HVM64" },
|
73
|
+
"r3.2xlarge" : { "Arch" : "HVM64" },
|
74
|
+
"r3.4xlarge" : { "Arch" : "HVM64" },
|
75
|
+
"r3.8xlarge" : { "Arch" : "HVM64" },
|
76
|
+
"i2.xlarge" : { "Arch" : "HVM64" },
|
77
|
+
"i2.2xlarge" : { "Arch" : "HVM64" },
|
78
|
+
"i2.4xlarge" : { "Arch" : "HVM64" },
|
79
|
+
"i2.8xlarge" : { "Arch" : "HVM64" },
|
80
|
+
"d2.xlarge" : { "Arch" : "HVM64" },
|
81
|
+
"d2.2xlarge" : { "Arch" : "HVM64" },
|
82
|
+
"d2.4xlarge" : { "Arch" : "HVM64" },
|
83
|
+
"d2.8xlarge" : { "Arch" : "HVM64" },
|
84
|
+
"hi1.4xlarge" : { "Arch" : "HVM64" },
|
85
|
+
"hs1.8xlarge" : { "Arch" : "HVM64" },
|
86
|
+
"cr1.8xlarge" : { "Arch" : "HVM64" },
|
87
|
+
"cc2.8xlarge" : { "Arch" : "HVM64" }
|
88
|
+
},
|
89
|
+
"AWSInstanceType2NATArch" : {
|
90
|
+
"t1.micro" : { "Arch" : "NATPV64" },
|
91
|
+
"t2.nano" : { "Arch" : "NATHVM64" },
|
92
|
+
"t2.micro" : { "Arch" : "NATHVM64" },
|
93
|
+
"t2.small" : { "Arch" : "NATHVM64" },
|
94
|
+
"t2.medium" : { "Arch" : "NATHVM64" },
|
95
|
+
"t2.large" : { "Arch" : "NATHVM64" },
|
96
|
+
"m1.small" : { "Arch" : "NATPV64" },
|
97
|
+
"m1.medium" : { "Arch" : "NATPV64" },
|
98
|
+
"m1.large" : { "Arch" : "NATPV64" },
|
99
|
+
"m1.xlarge" : { "Arch" : "NATPV64" },
|
100
|
+
"m2.xlarge" : { "Arch" : "NATPV64" },
|
101
|
+
"m2.2xlarge" : { "Arch" : "NATPV64" },
|
102
|
+
"m2.4xlarge" : { "Arch" : "NATPV64" },
|
103
|
+
"m3.medium" : { "Arch" : "NATHVM64" },
|
104
|
+
"m3.large" : { "Arch" : "NATHVM64" },
|
105
|
+
"m3.xlarge" : { "Arch" : "NATHVM64" },
|
106
|
+
"m3.2xlarge" : { "Arch" : "NATHVM64" },
|
107
|
+
"m4.large" : { "Arch" : "NATHVM64" },
|
108
|
+
"m4.xlarge" : { "Arch" : "NATHVM64" },
|
109
|
+
"m4.2xlarge" : { "Arch" : "NATHVM64" },
|
110
|
+
"m4.4xlarge" : { "Arch" : "NATHVM64" },
|
111
|
+
"m4.10xlarge" : { "Arch" : "NATHVM64" },
|
112
|
+
"c1.medium" : { "Arch" : "NATPV64" },
|
113
|
+
"c1.xlarge" : { "Arch" : "NATPV64" },
|
114
|
+
"c3.large" : { "Arch" : "NATHVM64" },
|
115
|
+
"c3.xlarge" : { "Arch" : "NATHVM64" },
|
116
|
+
"c3.2xlarge" : { "Arch" : "NATHVM64" },
|
117
|
+
"c3.4xlarge" : { "Arch" : "NATHVM64" },
|
118
|
+
"c3.8xlarge" : { "Arch" : "NATHVM64" },
|
119
|
+
"c4.large" : { "Arch" : "NATHVM64" },
|
120
|
+
"c4.xlarge" : { "Arch" : "NATHVM64" },
|
121
|
+
"c4.2xlarge" : { "Arch" : "NATHVM64" },
|
122
|
+
"c4.4xlarge" : { "Arch" : "NATHVM64" },
|
123
|
+
"c4.8xlarge" : { "Arch" : "NATHVM64" },
|
124
|
+
"g2.2xlarge" : { "Arch" : "NATHVMG2" },
|
125
|
+
"g2.8xlarge" : { "Arch" : "NATHVMG2" },
|
126
|
+
"r3.large" : { "Arch" : "NATHVM64" },
|
127
|
+
"r3.xlarge" : { "Arch" : "NATHVM64" },
|
128
|
+
"r3.2xlarge" : { "Arch" : "NATHVM64" },
|
129
|
+
"r3.4xlarge" : { "Arch" : "NATHVM64" },
|
130
|
+
"r3.8xlarge" : { "Arch" : "NATHVM64" },
|
131
|
+
"i2.xlarge" : { "Arch" : "NATHVM64" },
|
132
|
+
"i2.2xlarge" : { "Arch" : "NATHVM64" },
|
133
|
+
"i2.4xlarge" : { "Arch" : "NATHVM64" },
|
134
|
+
"i2.8xlarge" : { "Arch" : "NATHVM64" },
|
135
|
+
"d2.xlarge" : { "Arch" : "NATHVM64" },
|
136
|
+
"d2.2xlarge" : { "Arch" : "NATHVM64" },
|
137
|
+
"d2.4xlarge" : { "Arch" : "NATHVM64" },
|
138
|
+
"d2.8xlarge" : { "Arch" : "NATHVM64" },
|
139
|
+
"hi1.4xlarge" : { "Arch" : "NATHVM64" },
|
140
|
+
"hs1.8xlarge" : { "Arch" : "NATHVM64" },
|
141
|
+
"cr1.8xlarge" : { "Arch" : "NATHVM64" },
|
142
|
+
"cc2.8xlarge" : { "Arch" : "NATHVM64" }
|
143
|
+
},
|
144
|
+
"AWSRegionArch2AMI" : {
|
145
|
+
"us-east-1" : {"PV64" : "ami-5fb8c835", "HVM64" : "ami-60b6c60a", "HVMG2" : "ami-e998ea83"},
|
146
|
+
"us-west-2" : {"PV64" : "ami-d93622b8", "HVM64" : "ami-f0091d91", "HVMG2" : "ami-315f4850"},
|
147
|
+
"us-west-1" : {"PV64" : "ami-56ea8636", "HVM64" : "ami-d5ea86b5", "HVMG2" : "ami-943956f4"},
|
148
|
+
"eu-west-1" : {"PV64" : "ami-95e33ce6", "HVM64" : "ami-bff32ccc", "HVMG2" : "ami-83fd23f0"},
|
149
|
+
"eu-central-1" : {"PV64" : "ami-794a5915", "HVM64" : "ami-bc5b48d0", "HVMG2" : "ami-ba1a09d6"},
|
150
|
+
"ap-northeast-1" : {"PV64" : "ami-393c1957", "HVM64" : "ami-383c1956", "HVMG2" : "ami-08e5c166"},
|
151
|
+
"ap-northeast-2" : {"PV64" : "NOT_SUPPORTED", "HVM64" : "ami-249b554a", "HVMG2" : "NOT_SUPPORTED"},
|
152
|
+
"ap-southeast-1" : {"PV64" : "ami-34bd7a57", "HVM64" : "ami-c9b572aa", "HVMG2" : "ami-5a15d239"},
|
153
|
+
"ap-southeast-2" : {"PV64" : "ami-ced887ad", "HVM64" : "ami-48d38c2b", "HVMG2" : "ami-0c1a446f"},
|
154
|
+
"sa-east-1" : {"PV64" : "ami-7d15ad11", "HVM64" : "ami-6817af04", "HVMG2" : "NOT_SUPPORTED"},
|
155
|
+
"cn-north-1" : {"PV64" : "ami-18ac6575", "HVM64" : "ami-43a36a2e", "HVMG2" : "NOT_SUPPORTED"}
|
156
|
+
}
|
157
|
+
},
|
158
|
+
|
159
|
+
"Resources" : {
|
160
|
+
"WebServerGroup" : {
|
161
|
+
"Type" : "AWS::AutoScaling::AutoScalingGroup",
|
162
|
+
"Properties" : {
|
163
|
+
"AvailabilityZones" : { "Fn::GetAZs" : ""},
|
164
|
+
"LaunchConfigurationName" : { "Ref" : "LaunchConfig" },
|
165
|
+
"MinSize" : "1",
|
166
|
+
"MaxSize" : "3",
|
167
|
+
"HealthCheckGracePeriod" : "600",
|
168
|
+
"HealthCheckType" : "ELB"
|
169
|
+
},
|
170
|
+
"CreationPolicy" : {
|
171
|
+
"ResourceSignal" : {
|
172
|
+
"Timeout" : "PT15M",
|
173
|
+
"Count" : "1"
|
174
|
+
}
|
175
|
+
},
|
176
|
+
"UpdatePolicy": {
|
177
|
+
"AutoScalingRollingUpdate": {
|
178
|
+
"MinInstancesInService": "1",
|
179
|
+
"MaxBatchSize": "1",
|
180
|
+
"PauseTime" : "PT15M",
|
181
|
+
"WaitOnResourceSignals": "true"
|
182
|
+
}
|
183
|
+
}
|
184
|
+
},
|
185
|
+
|
186
|
+
"LaunchConfig" : {
|
187
|
+
"Type" : "AWS::AutoScaling::LaunchConfiguration",
|
188
|
+
"Metadata" : {
|
189
|
+
"Comment" : "Install a simple application",
|
190
|
+
"AWS::CloudFormation::Init" : {
|
191
|
+
"config" : {
|
192
|
+
"packages" : {
|
193
|
+
"yum" : {
|
194
|
+
"httpd" : []
|
195
|
+
}
|
196
|
+
},
|
197
|
+
|
198
|
+
"files" : {
|
199
|
+
"/var/www/html/index.html" : {
|
200
|
+
"content" : { "Fn::Join" : ["\n", [
|
201
|
+
"<img src=\"", {"Fn::FindInMap" : ["Region2Examples", {"Ref" : "AWS::Region"}, "Examples"]}, "/cloudformation_graphic.png\" alt=\"AWS CloudFormation Logo\"/>",
|
202
|
+
"<h1>Congratulations, you have successfully launched the AWS CloudFormation sample.</h1>"
|
203
|
+
]]},
|
204
|
+
"mode" : "000644",
|
205
|
+
"owner" : "root",
|
206
|
+
"group" : "root"
|
207
|
+
},
|
208
|
+
|
209
|
+
"/etc/cfn/cfn-hup.conf" : {
|
210
|
+
"content" : { "Fn::Join" : ["", [
|
211
|
+
"[main]\n",
|
212
|
+
"stack=", { "Ref" : "AWS::StackId" }, "\n",
|
213
|
+
"region=", { "Ref" : "AWS::Region" }, "\n"
|
214
|
+
]]},
|
215
|
+
"mode" : "000400",
|
216
|
+
"owner" : "root",
|
217
|
+
"group" : "root"
|
218
|
+
},
|
219
|
+
|
220
|
+
"/etc/cfn/hooks.d/cfn-auto-reloader.conf" : {
|
221
|
+
"content": { "Fn::Join" : ["", [
|
222
|
+
"[cfn-auto-reloader-hook]\n",
|
223
|
+
"triggers=post.update\n",
|
224
|
+
"path=Resources.LaunchConfig.Metadata.AWS::CloudFormation::Init\n",
|
225
|
+
"action=/opt/aws/bin/cfn-init -v ",
|
226
|
+
" --stack ", { "Ref" : "AWS::StackName" },
|
227
|
+
" --resource LaunchConfig ",
|
228
|
+
" --region ", { "Ref" : "AWS::Region" }, "\n",
|
229
|
+
"runas=root\n"
|
230
|
+
]]}
|
231
|
+
}
|
232
|
+
},
|
233
|
+
|
234
|
+
"services" : {
|
235
|
+
"sysvinit" : {
|
236
|
+
"httpd" : { "enabled" : "true", "ensureRunning" : "true" },
|
237
|
+
"cfn-hup" : { "enabled" : "true", "ensureRunning" : "true",
|
238
|
+
"files" : ["/etc/cfn/cfn-hup.conf", "/etc/cfn/hooks.d/cfn-auto-reloader.conf"]}
|
239
|
+
}
|
240
|
+
}
|
241
|
+
}
|
242
|
+
}
|
243
|
+
},
|
244
|
+
"Properties" : {
|
245
|
+
"ImageId" : { "Fn::FindInMap" : [ "AWSRegionArch2AMI", { "Ref" : "AWS::Region" },
|
246
|
+
{ "Fn::FindInMap" : [ "AWSInstanceType2Arch", { "Ref" : "InstanceType" }, "Arch" ] } ] },
|
247
|
+
"SecurityGroups" : [ { "Ref" : "InstanceSecurityGroup" } ],
|
248
|
+
"InstanceType" : { "Ref" : "InstanceType" },
|
249
|
+
"UserData" : { "Fn::Base64" : { "Fn::Join" : ["", [
|
250
|
+
"#!/bin/bash -xe\n",
|
251
|
+
"yum update -y aws-cfn-bootstrap\n",
|
252
|
+
|
253
|
+
"/opt/aws/bin/cfn-init -v ",
|
254
|
+
" --stack ", { "Ref" : "AWS::StackName" },
|
255
|
+
" --resource LaunchConfig ",
|
256
|
+
" --region ", { "Ref" : "AWS::Region" }, "\n",
|
257
|
+
|
258
|
+
"/opt/aws/bin/cfn-signal -e $? ",
|
259
|
+
" --stack ", { "Ref" : "AWS::StackName" },
|
260
|
+
" --resource WebServerGroup ",
|
261
|
+
" --region ", { "Ref" : "AWS::Region" }, "\n"
|
262
|
+
]]}}
|
263
|
+
}
|
264
|
+
},
|
265
|
+
|
266
|
+
"CPUAlarmHigh": {
|
267
|
+
"Type": "AWS::CloudWatch::Alarm",
|
268
|
+
"Properties": {
|
269
|
+
"AlarmDescription": "Scale-up if CPU > 90% for 10 minutes",
|
270
|
+
"MetricName": "CPUUtilization",
|
271
|
+
"Namespace": "AWS/EC2",
|
272
|
+
"Statistic": "Average",
|
273
|
+
"Period": "300",
|
274
|
+
"EvaluationPeriods": "2",
|
275
|
+
"Threshold": "90",
|
276
|
+
"AlarmActions": [ { "Ref": "WebServerScaleUpPolicy" } ],
|
277
|
+
"Dimensions": [
|
278
|
+
{
|
279
|
+
"Name": "AutoScalingGroupName",
|
280
|
+
"Value": { "Ref": "WebServerGroup" }
|
281
|
+
}
|
282
|
+
],
|
283
|
+
"ComparisonOperator": "GreaterThanThreshold"
|
284
|
+
}
|
285
|
+
},
|
286
|
+
"CPUAlarmLow": {
|
287
|
+
"Type": "AWS::CloudWatch::Alarm",
|
288
|
+
"Properties": {
|
289
|
+
"AlarmDescription": "Scale-down if CPU < 70% for 10 minutes",
|
290
|
+
"MetricName": "CPUUtilization",
|
291
|
+
"Namespace": "AWS/EC2",
|
292
|
+
"Statistic": "Average",
|
293
|
+
"Period": "300",
|
294
|
+
"EvaluationPeriods": "2",
|
295
|
+
"Threshold": "70",
|
296
|
+
"AlarmActions": [ { "Ref": "WebServerScaleDownPolicy" } ],
|
297
|
+
"Dimensions": [
|
298
|
+
{
|
299
|
+
"Name": "AutoScalingGroupName",
|
300
|
+
"Value": { "Ref": "WebServerGroup" }
|
301
|
+
}
|
302
|
+
],
|
303
|
+
"ComparisonOperator": "LessThanThreshold"
|
304
|
+
}
|
305
|
+
},
|
306
|
+
|
307
|
+
"WebServerScaleUpPolicy" : {
|
308
|
+
"Type" : "AWS::AutoScaling::ScalingPolicy",
|
309
|
+
"Properties" : {
|
310
|
+
"AdjustmentType" : "ChangeInCapacity",
|
311
|
+
"AutoScalingGroupName" : { "Ref" : "WebServerGroup" },
|
312
|
+
"Cooldown" : "60",
|
313
|
+
"ScalingAdjustment" : "1"
|
314
|
+
}
|
315
|
+
},
|
316
|
+
"WebServerScaleDownPolicy" : {
|
317
|
+
"Type" : "AWS::AutoScaling::ScalingPolicy",
|
318
|
+
"Properties" : {
|
319
|
+
"AdjustmentType" : "ChangeInCapacity",
|
320
|
+
"AutoScalingGroupName" : { "Ref" : "WebServerGroup" },
|
321
|
+
"Cooldown" : "60",
|
322
|
+
"ScalingAdjustment" : "-1"
|
323
|
+
}
|
324
|
+
}
|
325
|
+
}
|
326
|
+
}
|
@@ -0,0 +1,59 @@
|
|
1
|
+
//AutoStacker24
|
2
|
+
{
|
3
|
+
"AWSTemplateFormatVersion" : "2010-09-09",
|
4
|
+
|
5
|
+
"Description" : "AWS CloudFormation Sample Template AutoScalingMultiAZWithNotifications: Create a multi-az, load balanced and Auto Scaled sample web site running on an Apache Web Serever. The application is configured to span all Availability Zones in the region and is Auto-Scaled based on the CPU utilization of the web servers. Notifications will be sent to the operator email address on scaling events. The instances are load balanced with a simple health check against the default web page. **WARNING** This template creates one or more Amazon EC2 instances and an Elastic Load Balancer. You will be billed for the AWS resources used if you create a stack from this template.",
|
6
|
+
|
7
|
+
"Parameters" : {
|
8
|
+
},
|
9
|
+
|
10
|
+
"Resources" : {
|
11
|
+
|
12
|
+
"ElasticLoadBalancer" : {
|
13
|
+
"Type" : "AWS::ElasticLoadBalancing::LoadBalancer",
|
14
|
+
"Properties" : {
|
15
|
+
"AvailabilityZones" : { "Fn::GetAZs" : "" },
|
16
|
+
"CrossZone" : "true",
|
17
|
+
"Listeners" : [ {
|
18
|
+
"LoadBalancerPort" : "80",
|
19
|
+
"InstancePort" : "80",
|
20
|
+
"Protocol" : "HTTP"
|
21
|
+
} ],
|
22
|
+
"HealthCheck" : {
|
23
|
+
"Target" : "HTTP:80/",
|
24
|
+
"HealthyThreshold" : "3",
|
25
|
+
"UnhealthyThreshold" : "5",
|
26
|
+
"Interval" : "30",
|
27
|
+
"Timeout" : "5"
|
28
|
+
}
|
29
|
+
}
|
30
|
+
},
|
31
|
+
|
32
|
+
"InstanceSecurityGroup" : {
|
33
|
+
"Type" : "AWS::EC2::SecurityGroup",
|
34
|
+
"Properties" : {
|
35
|
+
"GroupDescription" : "Enable SSH access and HTTP from the load balancer only",
|
36
|
+
"SecurityGroupIngress" : [
|
37
|
+
{
|
38
|
+
"IpProtocol" : "tcp",
|
39
|
+
"FromPort" : "80",
|
40
|
+
"ToPort" : "80",
|
41
|
+
"SourceSecurityGroupOwnerId" : {"Fn::GetAtt" : ["ElasticLoadBalancer", "SourceSecurityGroup.OwnerAlias"]},
|
42
|
+
"SourceSecurityGroupName" : {"Fn::GetAtt" : ["ElasticLoadBalancer", "SourceSecurityGroup.GroupName"]}
|
43
|
+
}
|
44
|
+
]
|
45
|
+
}
|
46
|
+
}
|
47
|
+
},
|
48
|
+
|
49
|
+
"Outputs" : {
|
50
|
+
"InstanceSecurityGroup" : {
|
51
|
+
"Description" : "The security group id of the instance",
|
52
|
+
"Value" : { "Fn::GetAtt" : [ "InstanceSecurityGroup", "GroupId" ]}
|
53
|
+
},
|
54
|
+
"URL" : {
|
55
|
+
"Description" : "The URL of the website",
|
56
|
+
"Value" : { "Fn::Join" : [ "", [ "http://", { "Fn::GetAtt" : [ "ElasticLoadBalancer", "DNSName" ]}]]}
|
57
|
+
}
|
58
|
+
}
|
59
|
+
}
|
data/lib/autocanary24.rb
ADDED
@@ -0,0 +1,178 @@
|
|
1
|
+
require 'aws-sdk-core'
|
2
|
+
|
3
|
+
module AutoCanary24
|
4
|
+
class CanaryStack
|
5
|
+
attr_reader :stack_name
|
6
|
+
|
7
|
+
def initialize(stack_name, wait_timeout, sleep_during_wait = 5)
|
8
|
+
raise "ERR: stack_name is missing" if stack_name.nil?
|
9
|
+
raise "ERR: wait_timeout is missing" if wait_timeout.nil?
|
10
|
+
@stack_name = stack_name
|
11
|
+
@wait_timeout = wait_timeout
|
12
|
+
@sleep_during_wait = sleep_during_wait
|
13
|
+
end
|
14
|
+
|
15
|
+
def get_desired_capacity
|
16
|
+
asg = get_autoscaling_group
|
17
|
+
asg_client = Aws::AutoScaling::Client.new
|
18
|
+
describe_asg(asg).desired_capacity
|
19
|
+
end
|
20
|
+
|
21
|
+
def set_desired_capacity_and_wait(desired_capacity)
|
22
|
+
asg = get_autoscaling_group
|
23
|
+
asg_client = Aws::AutoScaling::Client.new
|
24
|
+
resp = asg_client.set_desired_capacity({
|
25
|
+
auto_scaling_group_name: asg,
|
26
|
+
desired_capacity: desired_capacity,
|
27
|
+
honor_cooldown: false,
|
28
|
+
})
|
29
|
+
wait_for_instances_in_asg(asg, desired_capacity)
|
30
|
+
end
|
31
|
+
|
32
|
+
def is_attached_to(elb)
|
33
|
+
asg = get_autoscaling_group
|
34
|
+
elbs = get_attached_loadbalancers(asg) unless asg.nil?
|
35
|
+
(!elbs.nil? && elbs.any? { |e| e.load_balancer_name == elb })
|
36
|
+
end
|
37
|
+
|
38
|
+
def attach_instances_to_elb_and_wait(elb, instances)
|
39
|
+
elb_client = Aws::ElasticLoadBalancing::Client.new
|
40
|
+
elb_client.register_instances_with_load_balancer({ load_balancer_name: elb, instances: instances })
|
41
|
+
wait_for_instances_attached_to_elb(instances, elb)
|
42
|
+
end
|
43
|
+
|
44
|
+
def detach_instances_from_elb(elb, instances)
|
45
|
+
elb_client = Aws::ElasticLoadBalancing::Client.new
|
46
|
+
elb_client.deregister_instances_from_load_balancer({ load_balancer_name: elb, instances: instances })
|
47
|
+
end
|
48
|
+
|
49
|
+
def detach_asg_from_elb_and_wait(elb)
|
50
|
+
asg = get_autoscaling_group
|
51
|
+
asg_client = Aws::AutoScaling::Client.new
|
52
|
+
asg_client.detach_load_balancers({auto_scaling_group_name: asg, load_balancer_names: [elb]})
|
53
|
+
wait_for_asg_detached_from_elb(asg, elb)
|
54
|
+
end
|
55
|
+
|
56
|
+
def attach_asg_to_elb_and_wait(elb)
|
57
|
+
asg = get_autoscaling_group
|
58
|
+
asg_client = Aws::AutoScaling::Client.new
|
59
|
+
asg_client.attach_load_balancers({auto_scaling_group_name: asg, load_balancer_names: [elb]})
|
60
|
+
wait_for_asg_on_elb(asg, elb)
|
61
|
+
end
|
62
|
+
|
63
|
+
def suspend_asg_processes
|
64
|
+
processes = ['Launch', 'Terminate', 'AddToLoadBalancer', 'AlarmNotification']
|
65
|
+
asg = get_autoscaling_group
|
66
|
+
asg_client = Aws::AutoScaling::Client.new
|
67
|
+
asg_client.suspend_processes({auto_scaling_group_name: asg, scaling_processes: processes})
|
68
|
+
end
|
69
|
+
|
70
|
+
def resume_asg_processes
|
71
|
+
processes = ['Launch', 'Terminate', 'AddToLoadBalancer', 'AlarmNotification']
|
72
|
+
asg = get_autoscaling_group
|
73
|
+
asg_client = Aws::AutoScaling::Client.new
|
74
|
+
asg_client.resume_processes({auto_scaling_group_name: asg, scaling_processes: processes})
|
75
|
+
end
|
76
|
+
|
77
|
+
def get_instance_ids
|
78
|
+
asg = get_autoscaling_group
|
79
|
+
asg_client = Aws::AutoScaling::Client.new
|
80
|
+
describe_asg(asg)[:instances].map{ |i| { instance_id: i[:instance_id] } }
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
private
|
85
|
+
def describe_asg(asg)
|
86
|
+
asg_client = Aws::AutoScaling::Client.new
|
87
|
+
asg_client.describe_auto_scaling_groups({auto_scaling_group_names: [asg], max_records: 1})[:auto_scaling_groups][0]
|
88
|
+
end
|
89
|
+
|
90
|
+
def wait_for_asg_detached_from_elb(asg, elb)
|
91
|
+
auto_scaling_group = describe_asg(asg)
|
92
|
+
|
93
|
+
if auto_scaling_group[:load_balancer_names].select{|l| l == elb}.length == 1
|
94
|
+
puts "WARNING: ASG still on the ELB!"
|
95
|
+
end
|
96
|
+
|
97
|
+
instances = auto_scaling_group[:instances].map{ |i| { instance_id: i[:instance_id] } }
|
98
|
+
wait_for_instances_detached_from_elb(instances, elb)
|
99
|
+
end
|
100
|
+
|
101
|
+
def wait_for_instances_detached_from_elb(instances, elb)
|
102
|
+
elb_client = Aws::ElasticLoadBalancing::Client.new
|
103
|
+
retries = (@wait_timeout / @sleep_during_wait).round
|
104
|
+
while retries > 0
|
105
|
+
begin
|
106
|
+
elb_instances = elb_client.describe_instance_health({load_balancer_name: elb, instances: instances})
|
107
|
+
break if elb_instances[:instance_states].select{ |s| s.state == 'InService' }.length == 0
|
108
|
+
rescue Aws::ElasticLoadBalancing::Errors::InvalidInstance
|
109
|
+
end
|
110
|
+
sleep @sleep_during_wait
|
111
|
+
retries -= 1
|
112
|
+
end
|
113
|
+
|
114
|
+
raise "Timeout. Couldn't wait for instances '#{instances}' to get detached from ELB '#{elb}'." if retries == 0
|
115
|
+
end
|
116
|
+
|
117
|
+
def wait_for_asg_on_elb(asg, elb)
|
118
|
+
auto_scaling_group = describe_asg(asg)
|
119
|
+
|
120
|
+
if auto_scaling_group[:load_balancer_names].select{|l| l == elb}.length == 0
|
121
|
+
puts "WARNING: ASG not on the ELB yet!"
|
122
|
+
end
|
123
|
+
|
124
|
+
instances = auto_scaling_group[:instances].map{ |i| { instance_id: i[:instance_id] } }
|
125
|
+
wait_for_instances_attached_to_elb(instances, elb)
|
126
|
+
end
|
127
|
+
|
128
|
+
def wait_for_instances_attached_to_elb(instances, elb)
|
129
|
+
elb_client = Aws::ElasticLoadBalancing::Client.new
|
130
|
+
retries = (@wait_timeout / @sleep_during_wait).round
|
131
|
+
while retries > 0
|
132
|
+
begin
|
133
|
+
elb_instances = elb_client.describe_instance_health({load_balancer_name: elb, instances: instances})
|
134
|
+
break if elb_instances[:instance_states].select{ |s| s.state != 'InService' }.length == 0
|
135
|
+
rescue Aws::ElasticLoadBalancing::Errors::InvalidInstance
|
136
|
+
end
|
137
|
+
sleep @sleep_during_wait
|
138
|
+
retries -= 1
|
139
|
+
end
|
140
|
+
|
141
|
+
raise "Timeout. Couldn't wait for instances '#{instances}' to get attached to ELB '#{elb}'." if retries == 0
|
142
|
+
end
|
143
|
+
|
144
|
+
def wait_for_instances_in_asg(asg, expected_number_of_instances)
|
145
|
+
asg_client = Aws::AutoScaling::Client.new
|
146
|
+
retries = (@wait_timeout / @sleep_during_wait).round
|
147
|
+
while retries > 0
|
148
|
+
instances = asg_client.describe_auto_scaling_groups({auto_scaling_group_names: [asg]})[:auto_scaling_groups][0].instances
|
149
|
+
healthy_instances = instances.select{ |i| i[:health_status] == "Healthy" && i[:lifecycle_state]=="InService"}.length
|
150
|
+
break if healthy_instances == expected_number_of_instances
|
151
|
+
sleep @sleep_during_wait
|
152
|
+
retries -= 1
|
153
|
+
end
|
154
|
+
|
155
|
+
raise "Timeout. Only #{healthy_instances} of #{expected_number_of_instances} instances got healthy in ASG '#{asg}'." if retries == 0
|
156
|
+
end
|
157
|
+
|
158
|
+
def get_attached_loadbalancers(asg)
|
159
|
+
asg_client = Aws::AutoScaling::Client.new
|
160
|
+
asg_client.describe_load_balancers({ auto_scaling_group_name: asg }).load_balancers
|
161
|
+
end
|
162
|
+
|
163
|
+
def get_autoscaling_group
|
164
|
+
get_first_resource_id('AWS::AutoScaling::AutoScalingGroup')
|
165
|
+
end
|
166
|
+
|
167
|
+
def get_first_resource_id(resource_type)
|
168
|
+
client = Aws::CloudFormation::Client.new
|
169
|
+
begin
|
170
|
+
response = client.list_stack_resources({ stack_name: @stack_name })
|
171
|
+
rescue Exception
|
172
|
+
return nil
|
173
|
+
end
|
174
|
+
resources = response.data.stack_resource_summaries.select{|x| x[:resource_type] == resource_type }
|
175
|
+
resources.map{ |e| e.physical_resource_id }[0]
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
require 'aws-sdk-core'
|
2
|
+
require 'autostacker24'
|
3
|
+
require 'base64'
|
4
|
+
|
5
|
+
require_relative 'configuration'
|
6
|
+
require_relative 'canarystack'
|
7
|
+
|
8
|
+
module AutoCanary24
|
9
|
+
|
10
|
+
class Client
|
11
|
+
def initialize(**params)
|
12
|
+
@configuration = Configuration.new(params) #params.fetch(:configuration, Configuration::new(params))
|
13
|
+
end
|
14
|
+
|
15
|
+
def deploy_stack(parent_stack_name, template, parameters, tags = nil, deployment_check = lambda { |stacks, elb, instances_to_create| true })
|
16
|
+
begin
|
17
|
+
write_log(parent_stack_name, "Starting the deployment")
|
18
|
+
write_log(parent_stack_name, "Using the following configuration #{@configuration.inspect}")
|
19
|
+
|
20
|
+
elb = get_elb(parent_stack_name)
|
21
|
+
raise "No ELB found in stack #{parent_stack_name}" if elb.nil?
|
22
|
+
|
23
|
+
blue_cs = get_canary_stack("#{parent_stack_name}-B")
|
24
|
+
green_cs = get_canary_stack("#{parent_stack_name}-G")
|
25
|
+
|
26
|
+
stacks = get_stacks_to_create_and_to_delete_for(blue_cs, green_cs, elb)
|
27
|
+
|
28
|
+
before_switch(stacks, template, parameters, parent_stack_name, tags)
|
29
|
+
|
30
|
+
failed = switch(stacks, elb, deployment_check)
|
31
|
+
|
32
|
+
after_switch(stacks, failed || @configuration.keep_inactive_stack)
|
33
|
+
|
34
|
+
rescue Exception => e
|
35
|
+
write_log(parent_stack_name, "Unexpected exception #{e}")
|
36
|
+
end
|
37
|
+
write_log(parent_stack_name, "Deployment finished")
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
def get_stacks_to_create_and_to_delete_for(blue_cs, green_cs, elb)
|
42
|
+
|
43
|
+
green_is_attached = green_cs.is_attached_to(elb)
|
44
|
+
blue_is_attached = blue_cs.is_attached_to(elb)
|
45
|
+
|
46
|
+
if green_is_attached
|
47
|
+
stack_to_delete = green_cs
|
48
|
+
stack_to_create = blue_cs
|
49
|
+
elsif blue_is_attached
|
50
|
+
stack_to_delete = blue_cs
|
51
|
+
stack_to_create = green_cs
|
52
|
+
else
|
53
|
+
stack_to_delete = nil
|
54
|
+
stack_to_create = blue_cs
|
55
|
+
end
|
56
|
+
|
57
|
+
write_log(blue_cs.stack_name, blue_is_attached ? "Stack is attached to ELB #{elb}" : "Stack is not attached")
|
58
|
+
write_log(green_cs.stack_name, green_is_attached ? "Stack is attached to ELB #{elb}" : "Stack is not attached")
|
59
|
+
|
60
|
+
write_log(stack_to_create.stack_name, "will be created")
|
61
|
+
write_log(stack_to_delete.stack_name, "will be deleted")
|
62
|
+
|
63
|
+
{stack_to_create: stack_to_create, stack_to_delete: stack_to_delete}
|
64
|
+
end
|
65
|
+
|
66
|
+
def before_switch(stacks, template, parameters, parent_stack_name, tags)
|
67
|
+
|
68
|
+
create_stack(stacks[:stack_to_create].stack_name, template, parameters, parent_stack_name, tags)
|
69
|
+
|
70
|
+
unless stacks[:stack_to_delete].nil?
|
71
|
+
desired = stacks[:stack_to_delete].get_desired_capacity
|
72
|
+
write_log(stacks[:stack_to_delete].stack_name, "Found #{desired} instances")
|
73
|
+
|
74
|
+
stacks[:stack_to_create].set_desired_capacity_and_wait(desired)
|
75
|
+
stacks[:stack_to_delete].suspend_asg_processes
|
76
|
+
end
|
77
|
+
|
78
|
+
stacks[:stack_to_create].suspend_asg_processes
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
def switch(stacks, elb, deployment_check)
|
83
|
+
|
84
|
+
desired = stacks[:stack_to_create].get_desired_capacity
|
85
|
+
|
86
|
+
instances_to_toggle = (desired / 100.0 * @configuration.scaling_instance_percent).round
|
87
|
+
instances_to_toggle = 1 if (instances_to_toggle < 1)
|
88
|
+
|
89
|
+
instances_to_create = stacks[:stack_to_create].get_instance_ids
|
90
|
+
instances_to_delete = stacks[:stack_to_delete].nil? ? [] : stacks[:stack_to_delete].get_instance_ids
|
91
|
+
|
92
|
+
missing = desired
|
93
|
+
while missing > 0
|
94
|
+
|
95
|
+
write_log(stacks[:stack_to_create].stack_name, "Adding #{instances_to_toggle} instances (#{desired-missing+instances_to_toggle}/#{desired})")
|
96
|
+
|
97
|
+
begin
|
98
|
+
stacks[:stack_to_create].attach_instances_to_elb_and_wait(elb, instances_to_create[desired-missing, instances_to_toggle])
|
99
|
+
rescue
|
100
|
+
rollback(stacks, elb, instances_to_create, instances_to_delete)
|
101
|
+
return true
|
102
|
+
end
|
103
|
+
|
104
|
+
unless deployment_check.call(stacks, elb, instances_to_create)
|
105
|
+
rollback(stacks, elb, instances_to_create, instances_to_delete)
|
106
|
+
return true
|
107
|
+
end
|
108
|
+
|
109
|
+
if @configuration.keep_instances_balanced && !stacks[:stack_to_delete].nil?
|
110
|
+
begin
|
111
|
+
write_log(stacks[:stack_to_delete].stack_name, "Removing #{instances_to_toggle} instances (#{instances_to_delete[desired-missing, instances_to_toggle]})")
|
112
|
+
stacks[:stack_to_delete].detach_instances_from_elb(elb, instances_to_delete[desired-missing, instances_to_toggle])
|
113
|
+
rescue Exception => e
|
114
|
+
write_log(stacks[:stack_to_delete].stack_name, "WARNING: #{e}")
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
missing -= instances_to_toggle
|
119
|
+
if missing < instances_to_toggle
|
120
|
+
instances_to_toggle = missing
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
write_log(stacks[:stack_to_create].stack_name, "Attach to ELB #{elb}")
|
125
|
+
stacks[:stack_to_create].attach_asg_to_elb_and_wait(elb)
|
126
|
+
|
127
|
+
unless stacks[:stack_to_delete].nil?
|
128
|
+
write_log(stacks[:stack_to_delete].stack_name, "Detach from ELB #{elb}")
|
129
|
+
stacks[:stack_to_delete].detach_asg_from_elb_and_wait(elb)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def rollback(stacks, elb, instances_to_create, instances_to_delete)
|
134
|
+
begin
|
135
|
+
stacks[:stack_to_create].detach_instances_from_elb(elb, instances_to_create)
|
136
|
+
stacks[:stack_to_delete].attach_instances_to_elb_and_wait(elb, instances_to_delete)
|
137
|
+
rescue Exception => e
|
138
|
+
write_log("", "ROLLBACK FAILED: #{e}")
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def after_switch(stacks, keep_inactive_stack)
|
143
|
+
stacks[:stack_to_create].resume_asg_processes
|
144
|
+
stacks[:stack_to_delete].resume_asg_processes unless stacks[:stack_to_delete].nil?
|
145
|
+
|
146
|
+
if keep_inactive_stack == false
|
147
|
+
delete_stack(stacks[:stack_to_delete])
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def get_elb(stack_name)
|
152
|
+
get_first_resource_id(stack_name, 'AWS::ElasticLoadBalancing::LoadBalancer')
|
153
|
+
end
|
154
|
+
|
155
|
+
def get_first_resource_id(stack_name, resource_type)
|
156
|
+
return nil if stack_name.nil?
|
157
|
+
|
158
|
+
client = Aws::CloudFormation::Client.new
|
159
|
+
resp = client.list_stack_resources({ stack_name: stack_name }).data.stack_resource_summaries
|
160
|
+
|
161
|
+
resource_ids = resp.select{|x| x[:resource_type] == resource_type }.map { |e| e.physical_resource_id }
|
162
|
+
resource_ids[0]
|
163
|
+
end
|
164
|
+
|
165
|
+
def create_stack(stack_name, template, parameters, parent_stack_name, tags)
|
166
|
+
write_log(stack_name, "Create/Update stack")
|
167
|
+
Stacker.create_or_update_stack(stack_name, template, parameters, parent_stack_name, tags)
|
168
|
+
end
|
169
|
+
|
170
|
+
def delete_stack(stack_name)
|
171
|
+
Stacker.delete_stack(stack_name) unless stack_name.nil?
|
172
|
+
end
|
173
|
+
|
174
|
+
def get_canary_stack(stack_name)
|
175
|
+
CanaryStack.new(stack_name, @configuration.wait_timeout)
|
176
|
+
end
|
177
|
+
|
178
|
+
def write_log(stack_name, message)
|
179
|
+
puts "#{Time.now.utc}\t#{stack_name.ljust(20)}\t#{message.ljust(40)}"
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module AutoCanary24
|
2
|
+
class Configuration
|
3
|
+
# Defines what should happen with the inactive stack
|
4
|
+
attr_accessor :keep_inactive_stack
|
5
|
+
# If true a instance from current stack gets removed whenever a new instance from the new stack is added.
|
6
|
+
attr_accessor :keep_instances_balanced
|
7
|
+
# Percent of instances which are added at once (depends on the actual number of instances, read from desired)
|
8
|
+
attr_accessor :scaling_instance_percent
|
9
|
+
# Timeout to wait for checking AWS operations are done before do a rollback
|
10
|
+
attr_accessor :wait_timeout
|
11
|
+
|
12
|
+
def initialize(**params)
|
13
|
+
|
14
|
+
@keep_inactive_stack = false
|
15
|
+
unless params[:keep_inactive_stack].nil?
|
16
|
+
raise "ERR: inactive_stack_state should be a boolean" unless [true, false].include? params[:keep_inactive_stack]
|
17
|
+
@keep_inactive_stack = params[:keep_inactive_stack]
|
18
|
+
end
|
19
|
+
|
20
|
+
@keep_instances_balanced = false
|
21
|
+
unless params[:keep_instances_balanced].nil?
|
22
|
+
raise 'ERR: keep_instances_balanced needs to a boolean' unless [true, false].include? params[:keep_instances_balanced]
|
23
|
+
@keep_instances_balanced = params[:keep_instances_balanced]
|
24
|
+
end
|
25
|
+
|
26
|
+
@scaling_instance_percent = 100
|
27
|
+
unless params[:scaling_instance_percent].nil?
|
28
|
+
raise 'ERR: scaling_instance_percent needs to be a number between 1 and 100' unless params[:scaling_instance_percent].is_a?(Integer) && (1..100).include?(params[:scaling_instance_percent])
|
29
|
+
@scaling_instance_percent = params[:scaling_instance_percent]
|
30
|
+
end
|
31
|
+
|
32
|
+
@wait_timeout = 300
|
33
|
+
unless params[:wait_timeout].nil?
|
34
|
+
raise 'ERR: wait_timeout needs to be a number' unless params[:wait_timeout].is_a?(Integer)
|
35
|
+
@wait_timeout = params[:wait_timeout]
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
metadata
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: autocanary24
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0.pre.alpha
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Philipp Garbe
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-03-29 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.11'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.11'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
description: autocanary24 provides a small convenient module for blue/green and canary
|
56
|
+
deployments with CloudFormation.
|
57
|
+
email:
|
58
|
+
- pgarbe@autoscout24.com
|
59
|
+
executables: []
|
60
|
+
extensions: []
|
61
|
+
extra_rdoc_files: []
|
62
|
+
files:
|
63
|
+
- ".gitignore"
|
64
|
+
- ".rspec"
|
65
|
+
- ".travis.yml"
|
66
|
+
- Gemfile
|
67
|
+
- LICENSE.txt
|
68
|
+
- README.md
|
69
|
+
- Rakefile
|
70
|
+
- autocanary24.gemspec
|
71
|
+
- bin/console
|
72
|
+
- bin/setup
|
73
|
+
- examples/.bundle/config
|
74
|
+
- examples/Gemfile
|
75
|
+
- examples/Gemfile.lock
|
76
|
+
- examples/README.md
|
77
|
+
- examples/Rakefile
|
78
|
+
- examples/asg-stack.json
|
79
|
+
- examples/base-stack.json
|
80
|
+
- lib/autocanary24.rb
|
81
|
+
- lib/autocanary24/canarystack.rb
|
82
|
+
- lib/autocanary24/client.rb
|
83
|
+
- lib/autocanary24/configuration.rb
|
84
|
+
- lib/autocanary24/version.rb
|
85
|
+
homepage: https://github.com/autoscout24/autocanary24
|
86
|
+
licenses:
|
87
|
+
- MIT
|
88
|
+
metadata: {}
|
89
|
+
post_install_message:
|
90
|
+
rdoc_options: []
|
91
|
+
require_paths:
|
92
|
+
- lib
|
93
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - ">="
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0'
|
98
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - ">"
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: 1.3.1
|
103
|
+
requirements: []
|
104
|
+
rubyforge_project:
|
105
|
+
rubygems_version: 2.4.5.1
|
106
|
+
signing_key:
|
107
|
+
specification_version: 4
|
108
|
+
summary: A very narrow interface to AWS ECR
|
109
|
+
test_files: []
|