ecs_deploy 0.1.0
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/Gemfile +4 -0
- data/README.md +218 -0
- data/Rakefile +2 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/ecs_deploy.gemspec +27 -0
- data/exe/ecs_auto_scaler +6 -0
- data/lib/ecs_deploy.rb +28 -0
- data/lib/ecs_deploy/auto_scaler.rb +276 -0
- data/lib/ecs_deploy/capistrano.rb +134 -0
- data/lib/ecs_deploy/configuration.rb +17 -0
- data/lib/ecs_deploy/service.rb +101 -0
- data/lib/ecs_deploy/task_definition.rb +88 -0
- data/lib/ecs_deploy/version.rb +3 -0
- metadata +129 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 4bde853b69a4346173b9ea603cbdbfe7d6597f48
|
4
|
+
data.tar.gz: e41e68017918637cf37b93478e48f654fbef1349
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b49db5e1393e599ea8f882f4c100c68e503f05e94ff5d1c089f617c4802055fe6c42b83fa894abf5d798cb03bd2d25d9aabee5e86a236b9e945db78b418c85fc
|
7
|
+
data.tar.gz: 48364e5d28e5e1b350d3e8cd3951d77ed88146c59bf1456dc18b5e258e74b86b1bdad13589e91c0a1fae0454c7b92c0d9810d42418de9880933bbc341afbfcb8
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,218 @@
|
|
1
|
+
# EcsDeploy
|
2
|
+
|
3
|
+
Helper script for deployment to Amazon ECS.
|
4
|
+
|
5
|
+
This gem is experimental.
|
6
|
+
|
7
|
+
Main purpose is combination with capistrano API.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Add this line to your application's Gemfile:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
gem 'ecs_deploy', github: "reproio/ecs_deploy"
|
15
|
+
```
|
16
|
+
|
17
|
+
And then execute:
|
18
|
+
|
19
|
+
$ bundle
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
Use by Capistrano.
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
# Capfile
|
27
|
+
require 'ecs_deploy/capistrano'
|
28
|
+
|
29
|
+
# deploy.rb
|
30
|
+
set :ecs_default_cluster, "ecs-cluster-name"
|
31
|
+
set :ecs_access_key_id, "dummy" # optional, if nil, use environment variable
|
32
|
+
set :ecs_secret_access_key, "dummy" # optional, if nil, use environment variable
|
33
|
+
set :ecs_region, %w(ap-northeast-1) # optional, if nil, use environment variable
|
34
|
+
set :ecs_service_role, "customEcsServiceRole" # default: ecsServiceRole
|
35
|
+
set :ecs_deploy_wait_timeout, 600 # default: 300
|
36
|
+
|
37
|
+
set :ecs_tasks, [
|
38
|
+
{
|
39
|
+
name: "myapp-#{fetch(:rails_env)}",
|
40
|
+
container_definitions: [
|
41
|
+
{
|
42
|
+
name: "myapp",
|
43
|
+
image: "#{fetch(:docker_registry_host_with_port)}/myapp:#{fetch(:sha1)}",
|
44
|
+
cpu: 1024,
|
45
|
+
memory: 512,
|
46
|
+
port_mappings: [],
|
47
|
+
essential: true,
|
48
|
+
environment: [
|
49
|
+
{name: "RAILS_ENV", value: fetch(:rails_env)},
|
50
|
+
],
|
51
|
+
mount_points: [
|
52
|
+
{
|
53
|
+
source_volume: "sockets_path",
|
54
|
+
container_path: "/app/tmp/sockets",
|
55
|
+
read_only: false,
|
56
|
+
},
|
57
|
+
],
|
58
|
+
volumes_from: [],
|
59
|
+
log_configuration: {
|
60
|
+
log_driver: "fluentd",
|
61
|
+
options: {
|
62
|
+
"tag" => "docker.#{fetch(:rails_env)}.#{name}.{{.ID}}",
|
63
|
+
},
|
64
|
+
},
|
65
|
+
},
|
66
|
+
{
|
67
|
+
name: "nginx",
|
68
|
+
image: "#{fetch(:docker_registry_host_with_port)}/my-nginx",
|
69
|
+
cpu: 256,
|
70
|
+
memory: 256,
|
71
|
+
links: [],
|
72
|
+
port_mappings: [
|
73
|
+
{container_port: 443, host_port: 443, protocol: "tcp"},
|
74
|
+
],
|
75
|
+
essential: true,
|
76
|
+
environment: {},
|
77
|
+
mount_points: [],
|
78
|
+
volumes_from: [
|
79
|
+
{source_container: "myapp-#{fetch(:rails_env)}", read_only: false},
|
80
|
+
],
|
81
|
+
log_configuration: {
|
82
|
+
log_driver: "fluentd",
|
83
|
+
options: {
|
84
|
+
"tag" => "docker.#{fetch(:rails_env)}.#{name}.{{.ID}}",
|
85
|
+
},
|
86
|
+
},
|
87
|
+
}
|
88
|
+
],
|
89
|
+
volumes: [{name: "sockets_path", host: {}}],
|
90
|
+
executions: [ # execution task on deploy timing
|
91
|
+
{container_overrides: [{name: "myapp", command: ["db_migrate"]}]},
|
92
|
+
]
|
93
|
+
},
|
94
|
+
]
|
95
|
+
|
96
|
+
set :ecs_services, [
|
97
|
+
{
|
98
|
+
name: "myapp-#{fetch(:rails_env)}",
|
99
|
+
elb_name: "service-elb-name",
|
100
|
+
elb_service_port: 443,
|
101
|
+
elb_healthcheck_port: 443,
|
102
|
+
elb_container_name: "nginx",
|
103
|
+
desired_count: 1,
|
104
|
+
deployment_configuration: {maximum_percent: 200, minimum_healthy_percent: 50},
|
105
|
+
},
|
106
|
+
]
|
107
|
+
```
|
108
|
+
|
109
|
+
```sh
|
110
|
+
cap <stage> ecs:register_task_definition # register ecs_tasks as TaskDefinition
|
111
|
+
cap <stage> ecs:deploy # create or update Service by ecs_services info
|
112
|
+
|
113
|
+
cap <stage> ecs:rollback # deregister current task definition and update Service by previous revision of current task definition
|
114
|
+
```
|
115
|
+
|
116
|
+
### Rollback example
|
117
|
+
|
118
|
+
| sequence | taskdef | service | desc |
|
119
|
+
| -------- | -------- | ------------- | ------ |
|
120
|
+
| 1 | myapp:12 | myapp-service | |
|
121
|
+
| 2 | myapp:13 | myapp-service | |
|
122
|
+
| 3 | myapp:14 | myapp-service | current |
|
123
|
+
|
124
|
+
After rollback
|
125
|
+
|
126
|
+
| sequence | taskdef | service | desc |
|
127
|
+
| -------- | -------- | ------------- | ------ |
|
128
|
+
| 1 | myapp:12 | myapp-service | |
|
129
|
+
| 2 | myapp:13 | myapp-service | |
|
130
|
+
| 3 | myapp:14 | myapp-service | deregister |
|
131
|
+
| 4 | myapp:13 | myapp-service | current |
|
132
|
+
|
133
|
+
And rollback again
|
134
|
+
|
135
|
+
| sequence | taskdef | service | desc |
|
136
|
+
| -------- | -------- | ------------- | ------ |
|
137
|
+
| 1 | myapp:12 | myapp-service | |
|
138
|
+
| 2 | myapp:13 | myapp-service | previous |
|
139
|
+
| 3 | myapp:14 | myapp-service | deregister |
|
140
|
+
| 4 | myapp:13 | myapp-service | deregister |
|
141
|
+
| 5 | myapp:12 | myapp-service | current |
|
142
|
+
|
143
|
+
And deploy new version
|
144
|
+
|
145
|
+
| sequence | taskdef | service | desc |
|
146
|
+
| -------- | -------- | ------------- | ------ |
|
147
|
+
| 1 | myapp:12 | myapp-service | |
|
148
|
+
| 2 | myapp:13 | myapp-service | |
|
149
|
+
| 3 | myapp:14 | myapp-service | deregister |
|
150
|
+
| 4 | myapp:13 | myapp-service | deregister |
|
151
|
+
| 5 | myapp:12 | myapp-service | |
|
152
|
+
| 6 | myapp:15 | myapp-service | current |
|
153
|
+
|
154
|
+
And rollback
|
155
|
+
|
156
|
+
| sequence | taskdef | service | desc |
|
157
|
+
| -------- | -------- | ------------- | ------ |
|
158
|
+
| 1 | myapp:12 | myapp-service | |
|
159
|
+
| 2 | myapp:13 | myapp-service | |
|
160
|
+
| 3 | myapp:14 | myapp-service | deregister |
|
161
|
+
| 4 | myapp:13 | myapp-service | deregister |
|
162
|
+
| 5 | myapp:12 | myapp-service | |
|
163
|
+
| 6 | myapp:15 | myapp-service | deregister |
|
164
|
+
| 7 | myapp:12 | myapp-service | current |
|
165
|
+
|
166
|
+
## Autoscaler
|
167
|
+
|
168
|
+
Write config file (YAML format).
|
169
|
+
|
170
|
+
```yaml
|
171
|
+
# ポーリング時にupscale_triggersに指定した状態のalarmがあればstep分serviceとinstanceを増やす (max_task_countまで)
|
172
|
+
# ポーリング時にdownscale_triggersに指定した状態のalarmがあればstep分serviceとinstanceを減らす (min_task_countまで)
|
173
|
+
# max_task_countは段階的にリミットを設けられるようにする
|
174
|
+
# 一回リミットに到達するとcooldown_for_reach_maxを越えても状態が継続したら再開するようにする
|
175
|
+
|
176
|
+
polling_interval: 60
|
177
|
+
|
178
|
+
auto_scaling_groups:
|
179
|
+
- name: ecs-cluster-nodes
|
180
|
+
region: ap-northeast-1
|
181
|
+
buffer: 1 # タスク数に対する余剰のインスタンス数
|
182
|
+
|
183
|
+
services:
|
184
|
+
- name: repro-api-production
|
185
|
+
cluster: ecs-cluster
|
186
|
+
region: ap-northeast-1
|
187
|
+
auto_scaling_group_name: ecs-cluster-nodes
|
188
|
+
step: 1
|
189
|
+
idle_time: 240
|
190
|
+
max_task_count: [10, 25]
|
191
|
+
cooldown_time_for_reach_max: 600
|
192
|
+
min_task_count: 0
|
193
|
+
upscale_triggers:
|
194
|
+
- alarm_name: "ECS [repro-api-production] CPUUtilization"
|
195
|
+
state: ALARM
|
196
|
+
- alarm_name: "ELB repro-api-a HTTPCode_Backend_5XX"
|
197
|
+
state: ALARM
|
198
|
+
step: 2
|
199
|
+
downscale_triggers:
|
200
|
+
- alarm_name: "ECS [repro-api-production] CPUUtilization (low)"
|
201
|
+
state: OK
|
202
|
+
```
|
203
|
+
|
204
|
+
```sh
|
205
|
+
ecs_auto_scaler <config yaml>
|
206
|
+
```
|
207
|
+
|
208
|
+
I recommends deploy `ecs_auto_scaler` on ECS too.
|
209
|
+
|
210
|
+
## Development
|
211
|
+
|
212
|
+
After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
213
|
+
|
214
|
+
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).
|
215
|
+
|
216
|
+
## Contributing
|
217
|
+
|
218
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/joker1007/ecs_deploy.
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "ecs_deploy"
|
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/ecs_deploy.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'ecs_deploy/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "ecs_deploy"
|
8
|
+
spec.version = EcsDeploy::VERSION
|
9
|
+
spec.authors = ["joker1007"]
|
10
|
+
spec.email = ["kakyoin.hierophant@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{AWS ECS deploy helper}
|
13
|
+
spec.description = %q{AWS ECS deploy helper}
|
14
|
+
spec.homepage = "https://github.com/reproio/ecs_deploy"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
17
|
+
spec.bindir = "exe"
|
18
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_runtime_dependency "aws-sdk", "~> 2.2"
|
22
|
+
spec.add_runtime_dependency "terminal-table"
|
23
|
+
spec.add_runtime_dependency "paint"
|
24
|
+
|
25
|
+
spec.add_development_dependency "bundler", "~> 1.11"
|
26
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
27
|
+
end
|
data/exe/ecs_auto_scaler
ADDED
data/lib/ecs_deploy.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require "ecs_deploy/version"
|
2
|
+
require "ecs_deploy/configuration"
|
3
|
+
|
4
|
+
require 'logger'
|
5
|
+
require 'terminal-table'
|
6
|
+
require 'paint'
|
7
|
+
|
8
|
+
module EcsDeploy
|
9
|
+
def self.logger
|
10
|
+
@logger ||= Logger.new(STDOUT).tap do |l|
|
11
|
+
l.level = Logger.const_get(config.log_level.to_s.upcase)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.config
|
16
|
+
@config ||= Configuration.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.configure(&block)
|
20
|
+
if block_given?
|
21
|
+
yield config
|
22
|
+
@logger = nil
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
require "ecs_deploy/task_definition"
|
28
|
+
require "ecs_deploy/service"
|
@@ -0,0 +1,276 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'logger'
|
3
|
+
require 'time'
|
4
|
+
|
5
|
+
module EcsDeploy
|
6
|
+
module AutoScaler
|
7
|
+
class << self
|
8
|
+
attr_reader :logger, :error_logger
|
9
|
+
|
10
|
+
def run(yaml_path, log_file = nil, error_log_file = nil)
|
11
|
+
trap(:TERM) { exit 0 }
|
12
|
+
@logger = Logger.new(log_file || STDOUT)
|
13
|
+
STDOUT.sync = true unless log_file
|
14
|
+
@error_logger = Logger.new(error_log_file || STDERR)
|
15
|
+
STDERR.sync = true unless error_log_file
|
16
|
+
load_config(yaml_path)
|
17
|
+
|
18
|
+
until @stop
|
19
|
+
run_loop
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def stop
|
24
|
+
@stop = true
|
25
|
+
end
|
26
|
+
|
27
|
+
def run_loop
|
28
|
+
service_configs.each do |s|
|
29
|
+
next if s.idle?
|
30
|
+
|
31
|
+
difference = 0
|
32
|
+
s.upscale_triggers.each do |trigger|
|
33
|
+
step = trigger.step || s.step
|
34
|
+
next if difference >= step
|
35
|
+
|
36
|
+
if trigger.match?
|
37
|
+
logger.info "Fire upscale trigger of #{s.name} by #{trigger.alarm_name} #{trigger.state}"
|
38
|
+
difference = step
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
if difference == 0 && s.desired_count > s.current_min_task_count
|
43
|
+
s.downscale_triggers.each do |trigger|
|
44
|
+
if trigger.match?
|
45
|
+
logger.info "Fire downscale trigger of #{s.name} by #{trigger.alarm_name} #{trigger.state}"
|
46
|
+
step = trigger.step || s.step
|
47
|
+
difference = [difference, -(step)].min
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
if s.current_min_task_count > s.desired_count + difference
|
53
|
+
difference = s.current_min_task_count - s.desired_count
|
54
|
+
end
|
55
|
+
|
56
|
+
if difference >= 0 && s.desired_count > s.max_task_count.max
|
57
|
+
difference = s.max_task_count.max - s.desired_count
|
58
|
+
end
|
59
|
+
|
60
|
+
if difference != 0
|
61
|
+
s.update_service(s.desired_count + difference)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
service_configs.group_by { |s| [s.region, s.auto_scaling_group_name] }.each do |(region, auto_scaling_group_name), configs|
|
66
|
+
total_service_count = configs.inject(0) { |sum, s| sum + s.desired_count }
|
67
|
+
asg_config = auto_scaling_group_configs.find { |c| c.name == auto_scaling_group_name && c.region == region }
|
68
|
+
asg_config.update_auto_scaling_group(total_service_count)
|
69
|
+
end
|
70
|
+
|
71
|
+
TriggerConfig.clear_alarm_cache
|
72
|
+
|
73
|
+
sleep @polling_interval
|
74
|
+
end
|
75
|
+
|
76
|
+
def load_config(yaml_path)
|
77
|
+
@config = YAML.load_file(yaml_path)
|
78
|
+
@polling_interval = @config["polling_interval"]
|
79
|
+
end
|
80
|
+
|
81
|
+
def service_configs
|
82
|
+
@service_configs ||= @config["services"].map(&ServiceConfig.method(:new))
|
83
|
+
end
|
84
|
+
|
85
|
+
def auto_scaling_group_configs
|
86
|
+
@auto_scaling_group_configs ||= @config["auto_scaling_groups"].map(&AutoScalingConfig.method(:new))
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
module ConfigBase
|
91
|
+
module ClassMethods
|
92
|
+
def client_table
|
93
|
+
@client_table ||= {}
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def initialize(attributes = {})
|
98
|
+
attributes.each do |key, val|
|
99
|
+
send("#{key}=", val)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
SERVICE_CONFIG_ATTRIBUTES = %i(name cluster region auto_scaling_group_name step max_task_count min_task_count idle_time scheduled_min_task_count cooldown_time_for_reach_max upscale_triggers downscale_triggers desired_count)
|
105
|
+
ServiceConfig = Struct.new(*SERVICE_CONFIG_ATTRIBUTES) do
|
106
|
+
include ConfigBase
|
107
|
+
extend ConfigBase::ClassMethods
|
108
|
+
|
109
|
+
def initialize(attributes = {})
|
110
|
+
super(attributes)
|
111
|
+
self.idle_time ||= 60
|
112
|
+
self.max_task_count = Array(max_task_count)
|
113
|
+
self.upscale_triggers = upscale_triggers.to_a.map do |t|
|
114
|
+
TriggerConfig.new(t.merge(region: region))
|
115
|
+
end
|
116
|
+
self.downscale_triggers = downscale_triggers.to_a.map do |t|
|
117
|
+
TriggerConfig.new(t.merge(region: region))
|
118
|
+
end
|
119
|
+
self.max_task_count.sort!
|
120
|
+
self.desired_count = fetch_service.desired_count
|
121
|
+
@reach_max_at = nil
|
122
|
+
@last_updated_at = nil
|
123
|
+
end
|
124
|
+
|
125
|
+
def client
|
126
|
+
self.class.client_table[region] ||= Aws::ECS::Client.new(
|
127
|
+
access_key_id: EcsDeploy.config.access_key_id,
|
128
|
+
secret_access_key: EcsDeploy.config.secret_access_key,
|
129
|
+
region: region
|
130
|
+
)
|
131
|
+
end
|
132
|
+
|
133
|
+
def idle?
|
134
|
+
return false unless @last_updated_at
|
135
|
+
(Time.now - @last_updated_at) < idle_time
|
136
|
+
end
|
137
|
+
|
138
|
+
def current_min_task_count
|
139
|
+
return min_task_count if scheduled_min_task_count.nil? || scheduled_min_task_count.empty?
|
140
|
+
|
141
|
+
scheduled_min_task_count.find(-> { {"count" => min_task_count} }) { |s|
|
142
|
+
from = Time.parse(s["from"])
|
143
|
+
to = Time.parse(s["to"])
|
144
|
+
(from..to).cover?(Time.now)
|
145
|
+
}["count"]
|
146
|
+
end
|
147
|
+
|
148
|
+
def overheat?
|
149
|
+
return false unless @reach_max_at
|
150
|
+
(Time.now - @reach_max_at) > cooldown_time_for_reach_max
|
151
|
+
end
|
152
|
+
|
153
|
+
def fetch_service
|
154
|
+
res = client.describe_services(cluster: cluster, services: [name])
|
155
|
+
raise "Service \"#{name}\" is not found" if res.services.empty?
|
156
|
+
res.services[0]
|
157
|
+
rescue => e
|
158
|
+
AutoScaler.error_logger.error(e)
|
159
|
+
self.class.client_table[region] = nil
|
160
|
+
end
|
161
|
+
|
162
|
+
def update_service(next_desired_count)
|
163
|
+
current_level = max_task_level(desired_count)
|
164
|
+
next_level = max_task_level(next_desired_count)
|
165
|
+
if current_level < next_level && overheat? # next max
|
166
|
+
level = next_level
|
167
|
+
@reach_max_at = nil
|
168
|
+
AutoScaler.logger.info "Service \"#{name}\" is overheat, uses next max count"
|
169
|
+
elsif current_level < next_level && !overheat? # wait cooldown
|
170
|
+
level = current_level
|
171
|
+
@reach_max_at ||= Time.now
|
172
|
+
AutoScaler.logger.info "Service \"#{name}\" waits cooldown in #{(Time.now - @reach_max_at).to_i}sec"
|
173
|
+
elsif current_level == next_level && next_desired_count >= max_task_count[current_level] # reach current max
|
174
|
+
level = current_level
|
175
|
+
@reach_max_at ||= Time.now
|
176
|
+
AutoScaler.logger.info "Service \"#{name}\" waits cooldown in #{(Time.now - @reach_max_at).to_i}sec"
|
177
|
+
elsif current_level == next_level && next_desired_count < max_task_count[current_level]
|
178
|
+
level = current_level
|
179
|
+
@reach_max_at = nil
|
180
|
+
AutoScaler.logger.info "Service \"#{name}\" clears cooldown state"
|
181
|
+
elsif current_level > next_level
|
182
|
+
level = next_level
|
183
|
+
@reach_max_at = nil
|
184
|
+
AutoScaler.logger.info "Service \"#{name}\" clears cooldown state"
|
185
|
+
end
|
186
|
+
|
187
|
+
next_desired_count = [next_desired_count, max_task_count[level]].min
|
188
|
+
client.update_service(
|
189
|
+
cluster: cluster,
|
190
|
+
service: name,
|
191
|
+
desired_count: next_desired_count,
|
192
|
+
)
|
193
|
+
@last_updated_at = Time.now
|
194
|
+
self.desired_count = next_desired_count
|
195
|
+
AutoScaler.logger.info "Update service \"#{name}\": desired_count -> #{next_desired_count}"
|
196
|
+
rescue => e
|
197
|
+
AutoScaler.error_logger.error(e)
|
198
|
+
self.class.client_table[region] = nil
|
199
|
+
end
|
200
|
+
|
201
|
+
private
|
202
|
+
|
203
|
+
def max_task_level(count)
|
204
|
+
max_task_count.index { |i| count <= i } || max_task_count.size - 1
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
TriggerConfig = Struct.new(:alarm_name, :region, :state, :step) do
|
209
|
+
include ConfigBase
|
210
|
+
extend ConfigBase::ClassMethods
|
211
|
+
|
212
|
+
def self.alarm_cache
|
213
|
+
@alarm_cache ||= {}
|
214
|
+
end
|
215
|
+
|
216
|
+
def self.clear_alarm_cache
|
217
|
+
@alarm_cache.clear if @alarm_cache
|
218
|
+
end
|
219
|
+
|
220
|
+
def client
|
221
|
+
self.class.client_table[region] ||= Aws::CloudWatch::Client.new(
|
222
|
+
access_key_id: EcsDeploy.config.access_key_id,
|
223
|
+
secret_access_key: EcsDeploy.config.secret_access_key,
|
224
|
+
region: region
|
225
|
+
)
|
226
|
+
end
|
227
|
+
|
228
|
+
def match?
|
229
|
+
fetch_alarm.state_value == state
|
230
|
+
end
|
231
|
+
|
232
|
+
def fetch_alarm
|
233
|
+
alarm_cache = self.class.alarm_cache
|
234
|
+
return alarm_cache[region][alarm_name] if alarm_cache[region] && alarm_cache[region][alarm_name]
|
235
|
+
|
236
|
+
res = client.describe_alarms(alarm_names: [alarm_name])
|
237
|
+
raise "Alarm \"#{alarm_name}\" is not found" if res.metric_alarms.empty?
|
238
|
+
res.metric_alarms[0].tap do |alarm|
|
239
|
+
AutoScaler.logger.debug(alarm.to_json)
|
240
|
+
alarm_cache[region] ||= {}
|
241
|
+
alarm_cache[region][alarm_name] = alarm
|
242
|
+
end
|
243
|
+
rescue => e
|
244
|
+
AutoScaler.error_logger.error(e)
|
245
|
+
self.class.client_table[region] = nil
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
AutoScalingConfig = Struct.new(:name, :region, :buffer) do
|
250
|
+
include ConfigBase
|
251
|
+
extend ConfigBase::ClassMethods
|
252
|
+
|
253
|
+
def client
|
254
|
+
self.class.client_table[region] ||= Aws::AutoScaling::Client.new(
|
255
|
+
access_key_id: EcsDeploy.config.access_key_id,
|
256
|
+
secret_access_key: EcsDeploy.config.secret_access_key,
|
257
|
+
region: region
|
258
|
+
)
|
259
|
+
end
|
260
|
+
|
261
|
+
def update_auto_scaling_group(total_service_count)
|
262
|
+
desired_capacity = total_service_count + buffer.to_i
|
263
|
+
client.update_auto_scaling_group(
|
264
|
+
auto_scaling_group_name: name,
|
265
|
+
min_size: desired_capacity,
|
266
|
+
max_size: desired_capacity,
|
267
|
+
desired_capacity: desired_capacity,
|
268
|
+
)
|
269
|
+
AutoScaler.logger.info "Update auto scaling group \"#{name}\": desired_capacity -> #{desired_capacity}"
|
270
|
+
rescue => e
|
271
|
+
AutoScaler.error_logger.error(e)
|
272
|
+
self.class.client_table[region] = nil
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
276
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require 'ecs_deploy'
|
2
|
+
|
3
|
+
namespace :ecs do
|
4
|
+
task :configure do
|
5
|
+
EcsDeploy.configure do |c|
|
6
|
+
c.log_level = fetch(:ecs_log_level) if fetch(:ecs_log_level)
|
7
|
+
c.deploy_wait_timeout = fetch(:ecs_deploy_wait_timeout) if fetch(:ecs_deploy_wait_timeout)
|
8
|
+
c.ecs_service_role = fetch(:ecs_service_role) if fetch(:ecs_service_role)
|
9
|
+
c.default_region = Array(fetch(:ecs_region))[0] if fetch(:ecs_region)
|
10
|
+
end
|
11
|
+
|
12
|
+
if ENV["TARGET_CLUSTER"]
|
13
|
+
set :target_cluster, ENV["TARGET_CLUSTER"].split(",").map(&:strip)
|
14
|
+
end
|
15
|
+
if ENV["TARGET_TASK_DEFINITION"]
|
16
|
+
set :target_task_definition, ENV["TARGET_TASK_DEFINITION"].split(",").map(&:strip)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
task register_task_definition: [:configure] do
|
21
|
+
if fetch(:ecs_tasks)
|
22
|
+
regions = Array(fetch(:ecs_region))
|
23
|
+
regions = [nil] if regions.empty?
|
24
|
+
regions.each do |r|
|
25
|
+
fetch(:ecs_tasks).each do |t|
|
26
|
+
task_definition = EcsDeploy::TaskDefinition.new(
|
27
|
+
region: r,
|
28
|
+
task_definition_name: t[:name],
|
29
|
+
container_definitions: t[:container_definitions],
|
30
|
+
volumes: t[:volumes]
|
31
|
+
)
|
32
|
+
task_definition.register
|
33
|
+
|
34
|
+
t[:executions].to_a.each do |exec|
|
35
|
+
exec[:cluster] ||= fetch(:ecs_default_cluster)
|
36
|
+
task_definition.run(exec)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
task deploy: [:configure, :register_task_definition] do
|
44
|
+
if fetch(:ecs_services)
|
45
|
+
regions = Array(fetch(:ecs_region))
|
46
|
+
regions = [nil] if regions.empty?
|
47
|
+
regions.each do |r|
|
48
|
+
services = fetch(:ecs_services).map do |service|
|
49
|
+
if fetch(:target_cluster) && fetch(:target_cluster).size > 0
|
50
|
+
next unless fetch(:target_cluster).include?(service[:cluster])
|
51
|
+
end
|
52
|
+
if fetch(:target_task_definition) && fetch(:target_task_definition).size > 0
|
53
|
+
next unless fetch(:target_task_definition).include?(service[:task_definition_name])
|
54
|
+
end
|
55
|
+
|
56
|
+
service_options = {
|
57
|
+
region: r,
|
58
|
+
cluster: service[:cluster] || fetch(:ecs_default_cluster),
|
59
|
+
service_name: service[:name],
|
60
|
+
task_definition_name: service[:task_definition_name],
|
61
|
+
elb_name: service[:elb_name],
|
62
|
+
elb_service_port: service[:elb_service_port],
|
63
|
+
elb_healthcheck_port: service[:elb_healthcheck_port],
|
64
|
+
elb_container_name: service[:elb_container_name],
|
65
|
+
desired_count: service[:desired_count],
|
66
|
+
}
|
67
|
+
service_options[:deployment_configuration] = service[:deployment_configuration] if service[:deployment_configuration]
|
68
|
+
s = EcsDeploy::Service.new(service_options)
|
69
|
+
s.deploy
|
70
|
+
s
|
71
|
+
end
|
72
|
+
EcsDeploy::Service.wait_all_running(services)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
task rollback: [:configure] do
|
78
|
+
if fetch(:ecs_services)
|
79
|
+
regions = Array(fetch(:ecs_region))
|
80
|
+
regions = [nil] if regions.empty?
|
81
|
+
regions.each do |r|
|
82
|
+
services = fetch(:ecs_services).map do |service|
|
83
|
+
if fetch(:target_cluster) && fetch(:target_cluster).size > 0
|
84
|
+
next unless fetch(:target_cluster).include?(service[:cluster])
|
85
|
+
end
|
86
|
+
if fetch(:target_task_definition) && fetch(:target_task_definition).size > 0
|
87
|
+
next unless fetch(:target_task_definition).include?(service[:task_definition_name])
|
88
|
+
end
|
89
|
+
|
90
|
+
task_definition_arns = EcsDeploy::TaskDefinition.new(
|
91
|
+
region: r,
|
92
|
+
task_definition_name: service[:task_definition_name] || service[:name],
|
93
|
+
).recent_task_definition_arns
|
94
|
+
|
95
|
+
rollback_step = (ENV["STEP"] || 1).to_i
|
96
|
+
|
97
|
+
current_task_definition_arn = EcsDeploy::Service.new(
|
98
|
+
region: r,
|
99
|
+
cluster: service[:cluster] || fetch(:ecs_default_cluster),
|
100
|
+
service_name: service[:name],
|
101
|
+
).current_task_definition_arn
|
102
|
+
|
103
|
+
current_arn_index = task_definition_arns.index do |arn|
|
104
|
+
arn == current_task_definition_arn
|
105
|
+
end
|
106
|
+
|
107
|
+
rollback_arn = task_definition_arns[current_arn_index + rollback_step]
|
108
|
+
|
109
|
+
EcsDeploy.logger.info "#{current_task_definition_arn} -> #{rollback_arn}"
|
110
|
+
|
111
|
+
raise "Past task_definition_arns is nothing" unless rollback_arn
|
112
|
+
|
113
|
+
service_options = {
|
114
|
+
region: r,
|
115
|
+
cluster: service[:cluster] || fetch(:ecs_default_cluster),
|
116
|
+
service_name: service[:name],
|
117
|
+
task_definition_name: rollback_arn,
|
118
|
+
elb_name: service[:elb_name],
|
119
|
+
elb_service_port: service[:elb_service_port],
|
120
|
+
elb_healthcheck_port: service[:elb_healthcheck_port],
|
121
|
+
elb_container_name: service[:elb_container_name],
|
122
|
+
desired_count: service[:desired_count],
|
123
|
+
}
|
124
|
+
service_options[:deployment_configuration] = service[:deployment_configuration] if service[:deployment_configuration]
|
125
|
+
s = EcsDeploy::Service.new(service_options)
|
126
|
+
s.deploy
|
127
|
+
EcsDeploy::TaskDefinition.deregister(current_task_definition_arn, region: r)
|
128
|
+
s
|
129
|
+
end
|
130
|
+
EcsDeploy::Service.wait_all_running(services)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module EcsDeploy
|
2
|
+
class Configuration
|
3
|
+
attr_accessor \
|
4
|
+
:log_level,
|
5
|
+
:access_key_id,
|
6
|
+
:secret_access_key,
|
7
|
+
:default_region,
|
8
|
+
:deploy_wait_timeout,
|
9
|
+
:ecs_service_role
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@log_level = :info
|
13
|
+
@deploy_wait_timeout = 300
|
14
|
+
@ecs_service_role = "ecsServiceRole"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
|
3
|
+
module EcsDeploy
|
4
|
+
class Service
|
5
|
+
CHECK_INTERVAL = 5
|
6
|
+
attr_reader :cluster, :region, :service_name
|
7
|
+
|
8
|
+
def initialize(
|
9
|
+
cluster:, service_name:, task_definition_name: nil, revision: nil,
|
10
|
+
elb_name: nil, elb_service_port: nil, elb_healthcheck_port: nil, elb_container_name: nil,
|
11
|
+
desired_count: nil, deployment_configuration: {maximum_percent: 200, minimum_healthy_percent: 100},
|
12
|
+
region: nil
|
13
|
+
)
|
14
|
+
@cluster = cluster
|
15
|
+
@service_name = service_name
|
16
|
+
@task_definition_name = task_definition_name || service_name
|
17
|
+
@elb_name = elb_name
|
18
|
+
@elb_service_port = elb_service_port
|
19
|
+
@elb_healthcheck_port = elb_healthcheck_port
|
20
|
+
@elb_container_name = elb_container_name
|
21
|
+
@desired_count = desired_count
|
22
|
+
@deployment_configuration = deployment_configuration
|
23
|
+
@revision = revision
|
24
|
+
@region = region || EcsDeploy.config.default_region || ENV["AWS_DEFAULT_REGION"]
|
25
|
+
@response = nil
|
26
|
+
|
27
|
+
@client = Aws::ECS::Client.new(region: @region)
|
28
|
+
end
|
29
|
+
|
30
|
+
def current_task_definition_arn
|
31
|
+
res = @client.describe_services(cluster: @cluster, services: [@service_name])
|
32
|
+
res.services[0].task_definition
|
33
|
+
end
|
34
|
+
|
35
|
+
def deploy
|
36
|
+
res = @client.describe_services(cluster: @cluster, services: [@service_name])
|
37
|
+
service_options = {
|
38
|
+
cluster: @cluster,
|
39
|
+
task_definition: task_definition_name_with_revision,
|
40
|
+
deployment_configuration: @deployment_configuration,
|
41
|
+
}
|
42
|
+
if res.services.empty?
|
43
|
+
service_options.merge!({service_name: @service_name})
|
44
|
+
if @elb_name
|
45
|
+
service_options.merge!({
|
46
|
+
role: EcsDeploy.config.ecs_service_role,
|
47
|
+
desired_count: @desired_count.to_i,
|
48
|
+
load_balancers: [
|
49
|
+
{
|
50
|
+
load_balancer_name: @elb_name,
|
51
|
+
container_name: @elb_container_name,
|
52
|
+
container_port: @elb_service_port,
|
53
|
+
}
|
54
|
+
],
|
55
|
+
})
|
56
|
+
end
|
57
|
+
@response = @client.create_service(service_options)
|
58
|
+
EcsDeploy.logger.info "create service [#{@service_name}] [#{@region}] [#{Paint['OK', :green]}]"
|
59
|
+
else
|
60
|
+
service_options.merge!({service: @service_name})
|
61
|
+
service_options.merge!({desired_count: @desired_count}) if @desired_count
|
62
|
+
@response = @client.update_service(service_options)
|
63
|
+
EcsDeploy.logger.info "update service [#{@service_name}] [#{@region}] [#{Paint['OK', :green]}]"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def wait_running
|
68
|
+
return if @response.nil?
|
69
|
+
|
70
|
+
service = @response.service
|
71
|
+
deployment = nil
|
72
|
+
|
73
|
+
@client.wait_until(:services_stable, cluster: @cluster, services: [service.service_name]) do |w|
|
74
|
+
w.delay = 10
|
75
|
+
|
76
|
+
w.before_attempt do
|
77
|
+
EcsDeploy.logger.info "wait service stable [#{service.service_name}]"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.wait_all_running(services)
|
83
|
+
services.group_by { |s| [s.cluster, s.region] }.each do |(cl, region), ss|
|
84
|
+
client = Aws::ECS::Client.new(region: region)
|
85
|
+
service_names = ss.map(&:service_name)
|
86
|
+
client.wait_until(:services_stable, cluster: cl, services: service_names) do |w|
|
87
|
+
w.before_attempt do
|
88
|
+
EcsDeploy.logger.info "wait service stable [#{service_names.join(", ")}]"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def task_definition_name_with_revision
|
97
|
+
suffix = @revision ? ":#{@revision}" : ""
|
98
|
+
"#{@task_definition_name}#{suffix}"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module EcsDeploy
|
2
|
+
class TaskDefinition
|
3
|
+
def self.deregister(arn, region: nil)
|
4
|
+
region = region || EcsDeploy.config.default_region || ENV["AWS_DEFAULT_REGION"]
|
5
|
+
client = Aws::ECS::Client.new(region: region)
|
6
|
+
client.deregister_task_definition({
|
7
|
+
task_definition: arn,
|
8
|
+
})
|
9
|
+
EcsDeploy.logger.info "deregister task definition [#{arn}] [#{region}] [#{Paint['OK', :green]}]"
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(
|
13
|
+
task_definition_name:, region: nil,
|
14
|
+
volumes: [], container_definitions: []
|
15
|
+
)
|
16
|
+
@task_definition_name = task_definition_name
|
17
|
+
@region = region || EcsDeploy.config.default_region || ENV["AWS_DEFAULT_REGION"]
|
18
|
+
|
19
|
+
@container_definitions = container_definitions.map do |cd|
|
20
|
+
if cd[:docker_labels]
|
21
|
+
cd[:docker_labels] = cd[:docker_labels].map { |k, v| [k.to_s, v] }.to_h
|
22
|
+
end
|
23
|
+
if cd[:log_configuration] && cd[:log_configuration][:options]
|
24
|
+
cd[:log_configuration][:options] = cd[:log_configuration][:options].map { |k, v| [k.to_s, v] }.to_h
|
25
|
+
end
|
26
|
+
cd
|
27
|
+
end
|
28
|
+
@volumes = volumes
|
29
|
+
|
30
|
+
@client = Aws::ECS::Client.new(region: @region)
|
31
|
+
end
|
32
|
+
|
33
|
+
def recent_task_definition_arns
|
34
|
+
resp = @client.list_task_definitions(
|
35
|
+
family_prefix: @task_definition_name,
|
36
|
+
sort: "DESC"
|
37
|
+
)
|
38
|
+
resp.task_definition_arns
|
39
|
+
rescue
|
40
|
+
[]
|
41
|
+
end
|
42
|
+
|
43
|
+
def register
|
44
|
+
@client.register_task_definition({
|
45
|
+
family: @task_definition_name,
|
46
|
+
container_definitions: @container_definitions,
|
47
|
+
volumes: @volumes,
|
48
|
+
})
|
49
|
+
EcsDeploy.logger.info "register task definition [#{@task_definition_name}] [#{@region}] [#{Paint['OK', :green]}]"
|
50
|
+
end
|
51
|
+
|
52
|
+
def run(info)
|
53
|
+
resp = @client.run_task({
|
54
|
+
cluster: info[:cluster],
|
55
|
+
task_definition: @task_definition_name,
|
56
|
+
overrides: {
|
57
|
+
container_overrides: info[:container_overrides] || []
|
58
|
+
},
|
59
|
+
count: info[:count] || 1,
|
60
|
+
started_by: "capistrano",
|
61
|
+
})
|
62
|
+
unless resp.failures.empty?
|
63
|
+
resp.failures.each do |f|
|
64
|
+
raise "#{f.arn}: #{f.reason}"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
wait_targets = Array(info[:wait_stop])
|
69
|
+
unless wait_targets.empty?
|
70
|
+
@client.wait_until(:tasks_running, cluster: info[:cluster], tasks: resp.tasks.map { |t| t.task_arn })
|
71
|
+
@client.wait_until(:tasks_stopped, cluster: info[:cluster], tasks: resp.tasks.map { |t| t.task_arn })
|
72
|
+
|
73
|
+
resp = @client.describe_tasks(cluster: info[:cluster], tasks: resp.tasks.map { |t| t.task_arn })
|
74
|
+
resp.tasks.each do |t|
|
75
|
+
t.containers.each do |c|
|
76
|
+
next unless wait_targets.include?(c.name)
|
77
|
+
|
78
|
+
unless c.exit_code.zero?
|
79
|
+
raise "Task has errors: #{c.reason}"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
EcsDeploy.logger.info "run task [#{@task_definition_name} #{info.inspect}] [#{@region}] [#{Paint['OK', :green]}]"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
metadata
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ecs_deploy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- joker1007
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-06-26 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: aws-sdk
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: terminal-table
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: paint
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.11'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.11'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '10.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '10.0'
|
83
|
+
description: AWS ECS deploy helper
|
84
|
+
email:
|
85
|
+
- kakyoin.hierophant@gmail.com
|
86
|
+
executables:
|
87
|
+
- ecs_auto_scaler
|
88
|
+
extensions: []
|
89
|
+
extra_rdoc_files: []
|
90
|
+
files:
|
91
|
+
- ".gitignore"
|
92
|
+
- Gemfile
|
93
|
+
- README.md
|
94
|
+
- Rakefile
|
95
|
+
- bin/console
|
96
|
+
- bin/setup
|
97
|
+
- ecs_deploy.gemspec
|
98
|
+
- exe/ecs_auto_scaler
|
99
|
+
- lib/ecs_deploy.rb
|
100
|
+
- lib/ecs_deploy/auto_scaler.rb
|
101
|
+
- lib/ecs_deploy/capistrano.rb
|
102
|
+
- lib/ecs_deploy/configuration.rb
|
103
|
+
- lib/ecs_deploy/service.rb
|
104
|
+
- lib/ecs_deploy/task_definition.rb
|
105
|
+
- lib/ecs_deploy/version.rb
|
106
|
+
homepage: https://github.com/reproio/ecs_deploy
|
107
|
+
licenses: []
|
108
|
+
metadata: {}
|
109
|
+
post_install_message:
|
110
|
+
rdoc_options: []
|
111
|
+
require_paths:
|
112
|
+
- lib
|
113
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
119
|
+
requirements:
|
120
|
+
- - ">="
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
version: '0'
|
123
|
+
requirements: []
|
124
|
+
rubyforge_project:
|
125
|
+
rubygems_version: 2.5.1
|
126
|
+
signing_key:
|
127
|
+
specification_version: 4
|
128
|
+
summary: AWS ECS deploy helper
|
129
|
+
test_files: []
|