ecs_deploy 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|