ecs_deploy 0.2.0 → 0.3.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 +4 -4
- data/README.md +1 -1
- data/ecs_deploy.gemspec +1 -1
- data/lib/ecs_deploy.rb +1 -0
- data/lib/ecs_deploy/auto_scaler.rb +18 -34
- data/lib/ecs_deploy/capistrano.rb +41 -5
- data/lib/ecs_deploy/scheduled_task.rb +85 -0
- data/lib/ecs_deploy/service.rb +8 -5
- data/lib/ecs_deploy/task_definition.rb +7 -2
- data/lib/ecs_deploy/version.rb +1 -1
- metadata +6 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a565132ff142d819e1bdc64368264ebc89de1030
|
4
|
+
data.tar.gz: d0b5d389d5a9311853d8524e45c56bd54943450a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 50c6f3c9aecdacc26de0d61cb487f4196bf31031b8c43b798b4bebae1e2246e35ebf1bb1c6079a62b7d5f54c3f504fefb69ca08f08689d9717c601746db63bdb
|
7
|
+
data.tar.gz: f4fa32bf5a67e71a6265842eaffbff31647d17337fdb23e01007a6082f13b894a45ec127ca098a7c9125935937f9ac04c5d88025223915938da591a3259119e2
|
data/README.md
CHANGED
@@ -225,4 +225,4 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
225
225
|
|
226
226
|
## Contributing
|
227
227
|
|
228
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
228
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/reproio/ecs_deploy.
|
data/ecs_deploy.gemspec
CHANGED
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
-
spec.add_runtime_dependency "aws-sdk", "~> 2.
|
21
|
+
spec.add_runtime_dependency "aws-sdk", "~> 2.9"
|
22
22
|
spec.add_runtime_dependency "terminal-table"
|
23
23
|
spec.add_runtime_dependency "paint"
|
24
24
|
|
data/lib/ecs_deploy.rb
CHANGED
@@ -23,7 +23,7 @@ module EcsDeploy
|
|
23
23
|
config_groups = service_configs.group_by { |s| [s.region, s.auto_scaling_group_name] }
|
24
24
|
ths = config_groups.map do |(region, auto_scaling_group_name), configs|
|
25
25
|
asg_config = auto_scaling_group_configs.find { |c| c.name == auto_scaling_group_name && c.region == region }
|
26
|
-
Thread.new(asg_config, configs, &method(:main_loop))
|
26
|
+
Thread.new(asg_config, configs, &method(:main_loop)).tap { |th| th.abort_on_exception = true }
|
27
27
|
end
|
28
28
|
|
29
29
|
ths.each(&:join)
|
@@ -33,10 +33,13 @@ module EcsDeploy
|
|
33
33
|
loop_with_polling_interval("loop of #{asg_config.name}") do
|
34
34
|
ths = configs.map do |service_config|
|
35
35
|
Thread.new(service_config) do |s|
|
36
|
-
next if s.idle?
|
37
|
-
|
38
36
|
@logger.debug "Start service scaling of #{s.name}"
|
39
37
|
|
38
|
+
if s.idle?
|
39
|
+
@logger.debug "#{s.name} is idling"
|
40
|
+
next
|
41
|
+
end
|
42
|
+
|
40
43
|
difference = 0
|
41
44
|
s.upscale_triggers.each do |trigger|
|
42
45
|
step = trigger.step || s.step
|
@@ -71,6 +74,7 @@ module EcsDeploy
|
|
71
74
|
end
|
72
75
|
end
|
73
76
|
end
|
77
|
+
ths.each { |th| th.abort_on_exception = true }
|
74
78
|
|
75
79
|
ths.each(&:join)
|
76
80
|
|
@@ -113,6 +117,7 @@ module EcsDeploy
|
|
113
117
|
next if wait_polling_interval?(last_executed_at)
|
114
118
|
yield
|
115
119
|
last_executed_at = Process.clock_gettime(Process::CLOCK_MONOTONIC, :second)
|
120
|
+
@logger.debug "#{name} is last executed at #{last_executed_at}"
|
116
121
|
end
|
117
122
|
|
118
123
|
@logger.debug "Stop #{name}"
|
@@ -148,17 +153,13 @@ module EcsDeploy
|
|
148
153
|
end
|
149
154
|
|
150
155
|
def client
|
151
|
-
|
156
|
+
Aws::ECS::Client.new(
|
152
157
|
access_key_id: EcsDeploy.config.access_key_id,
|
153
158
|
secret_access_key: EcsDeploy.config.secret_access_key,
|
154
159
|
region: region
|
155
160
|
)
|
156
161
|
end
|
157
162
|
|
158
|
-
def clear_client
|
159
|
-
Thread.current["ecs_auto_scaler_ecs_#{region}"] = nil
|
160
|
-
end
|
161
|
-
|
162
163
|
def idle?
|
163
164
|
return false unless @last_updated_at
|
164
165
|
|
@@ -187,7 +188,6 @@ module EcsDeploy
|
|
187
188
|
res.services[0]
|
188
189
|
rescue => e
|
189
190
|
AutoScaler.error_logger.error(e)
|
190
|
-
clear_client
|
191
191
|
end
|
192
192
|
|
193
193
|
def update_service(difference)
|
@@ -218,13 +218,14 @@ module EcsDeploy
|
|
218
218
|
AutoScaler.logger.info "Service \"#{name}\" clears cooldown state"
|
219
219
|
end
|
220
220
|
|
221
|
+
cl = client
|
221
222
|
next_desired_count = [next_desired_count, max_task_count[level]].min
|
222
|
-
|
223
|
+
cl.update_service(
|
223
224
|
cluster: cluster,
|
224
225
|
service: name,
|
225
226
|
desired_count: next_desired_count,
|
226
227
|
)
|
227
|
-
|
228
|
+
cl.wait_until(:services_stable, cluster: cluster, services: [name]) do |w|
|
228
229
|
w.before_wait do
|
229
230
|
AutoScaler.logger.debug "wait service stable [#{name}]"
|
230
231
|
end
|
@@ -234,16 +235,16 @@ module EcsDeploy
|
|
234
235
|
AutoScaler.logger.info "Update service \"#{name}\": desired_count -> #{next_desired_count}"
|
235
236
|
rescue => e
|
236
237
|
AutoScaler.error_logger.error(e)
|
237
|
-
clear_client
|
238
238
|
end
|
239
239
|
|
240
240
|
def fetch_container_instances
|
241
241
|
arns = []
|
242
242
|
resp = nil
|
243
|
+
cl = client
|
243
244
|
loop do
|
244
245
|
options = {cluster: cluster}
|
245
246
|
options.merge(next_token: resp.next_token) if resp && resp.next_token
|
246
|
-
resp =
|
247
|
+
resp = cl.list_container_instances(options)
|
247
248
|
arns.concat(resp.container_instance_arns)
|
248
249
|
break unless resp.next_token
|
249
250
|
end
|
@@ -251,7 +252,7 @@ module EcsDeploy
|
|
251
252
|
chunk_size = 50
|
252
253
|
container_instances = []
|
253
254
|
arns.each_slice(chunk_size) do |arn_chunk|
|
254
|
-
is =
|
255
|
+
is = cl.describe_container_instances(cluster: cluster, container_instances: arn_chunk).container_instances
|
255
256
|
container_instances.concat(is)
|
256
257
|
end
|
257
258
|
|
@@ -269,17 +270,13 @@ module EcsDeploy
|
|
269
270
|
include ConfigBase
|
270
271
|
|
271
272
|
def client
|
272
|
-
|
273
|
+
Aws::CloudWatch::Client.new(
|
273
274
|
access_key_id: EcsDeploy.config.access_key_id,
|
274
275
|
secret_access_key: EcsDeploy.config.secret_access_key,
|
275
276
|
region: region
|
276
277
|
)
|
277
278
|
end
|
278
279
|
|
279
|
-
def clear_client
|
280
|
-
Thread.current["ecs_auto_scaler_cloud_watch_#{region}"] = nil
|
281
|
-
end
|
282
|
-
|
283
280
|
def match?
|
284
281
|
fetch_alarm.state_value == state
|
285
282
|
end
|
@@ -301,29 +298,21 @@ module EcsDeploy
|
|
301
298
|
include ConfigBase
|
302
299
|
|
303
300
|
def client
|
304
|
-
|
301
|
+
Aws::AutoScaling::Client.new(
|
305
302
|
access_key_id: EcsDeploy.config.access_key_id,
|
306
303
|
secret_access_key: EcsDeploy.config.secret_access_key,
|
307
304
|
region: region
|
308
305
|
)
|
309
306
|
end
|
310
307
|
|
311
|
-
def clear_client
|
312
|
-
Thread.current["ecs_auto_scaler_auto_scaling_#{region}"] = nil
|
313
|
-
end
|
314
|
-
|
315
308
|
def ec2_client
|
316
|
-
|
309
|
+
Aws::EC2::Client.new(
|
317
310
|
access_key_id: EcsDeploy.config.access_key_id,
|
318
311
|
secret_access_key: EcsDeploy.config.secret_access_key,
|
319
312
|
region: region
|
320
313
|
)
|
321
314
|
end
|
322
315
|
|
323
|
-
def clear_ec2_client
|
324
|
-
Thread.current["ecs_auto_scaler_ec2_#{region}"] = nil
|
325
|
-
end
|
326
|
-
|
327
316
|
def instances(reload: false)
|
328
317
|
if reload || @instances.nil?
|
329
318
|
resp = client.describe_auto_scaling_groups({
|
@@ -378,7 +367,6 @@ module EcsDeploy
|
|
378
367
|
end
|
379
368
|
rescue => e
|
380
369
|
AutoScaler.error_logger.error(e)
|
381
|
-
clear_client
|
382
370
|
end
|
383
371
|
|
384
372
|
def detach_and_terminate_instances(instance_ids)
|
@@ -398,8 +386,6 @@ module EcsDeploy
|
|
398
386
|
AutoScaler.logger.info "Terminated instances: #{instance_ids.inspect}"
|
399
387
|
rescue => e
|
400
388
|
AutoScaler.error_logger.error(e)
|
401
|
-
clear_client
|
402
|
-
clear_ec2_client
|
403
389
|
end
|
404
390
|
|
405
391
|
def detach_and_terminate_orphan_instances(service_config)
|
@@ -415,8 +401,6 @@ module EcsDeploy
|
|
415
401
|
detach_and_terminate_instances(targets.map(&:instance_id))
|
416
402
|
rescue => e
|
417
403
|
AutoScaler.error_logger.error(e)
|
418
|
-
clear_client
|
419
|
-
clear_ec2_client
|
420
404
|
end
|
421
405
|
end
|
422
406
|
end
|
@@ -20,24 +20,60 @@ namespace :ecs do
|
|
20
20
|
task register_task_definition: [:configure] do
|
21
21
|
if fetch(:ecs_tasks)
|
22
22
|
regions = Array(fetch(:ecs_region))
|
23
|
-
regions = [
|
23
|
+
regions = [EcsDeploy.config.default_region || ENV["AWS_DEFAULT_REGION"]] if regions.empty?
|
24
|
+
ecs_registered_tasks = {}
|
24
25
|
regions.each do |r|
|
26
|
+
ecs_registered_tasks[region] = {}
|
25
27
|
fetch(:ecs_tasks).each do |t|
|
26
28
|
task_definition = EcsDeploy::TaskDefinition.new(
|
27
|
-
region:
|
29
|
+
region: region,
|
28
30
|
task_definition_name: t[:name],
|
29
31
|
container_definitions: t[:container_definitions],
|
30
32
|
task_role_arn: t[:task_role_arn],
|
31
|
-
volumes: t[:volumes]
|
33
|
+
volumes: t[:volumes],
|
34
|
+
network_mode: t[:network_mode],
|
35
|
+
placement_constraints: t[:placement_constraints],
|
32
36
|
)
|
33
|
-
task_definition.register
|
37
|
+
result = task_definition.register
|
38
|
+
ecs_registered_tasks[region][t[:name]] = result
|
34
39
|
|
35
|
-
t[:executions].to_a
|
40
|
+
executions = t[:executions].to_a
|
41
|
+
unless executions.empty?
|
42
|
+
warn "`executions` config is deprecated. I will remove this in near future"
|
43
|
+
end
|
44
|
+
executions.each do |exec|
|
36
45
|
exec[:cluster] ||= fetch(:ecs_default_cluster)
|
37
46
|
task_definition.run(exec)
|
38
47
|
end
|
39
48
|
end
|
40
49
|
end
|
50
|
+
|
51
|
+
set :ecs_registered_tasks, ecs_registered_tasks
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
task deploy_scheduled_task: [:configure, :register_task_definition] do
|
56
|
+
if fetch(:ecs_tasks)
|
57
|
+
regions = Array(fetch(:ecs_region))
|
58
|
+
regions = [nil] if regions.empty?
|
59
|
+
regions.each do |r|
|
60
|
+
fetch(:ecs_scheduled_tasks).each do |t|
|
61
|
+
scheduled_task = EcsDeploy::ScheduledTask.new(
|
62
|
+
region: r,
|
63
|
+
cluster: t[:cluster],
|
64
|
+
rule_name: t[:rule_name],
|
65
|
+
schedule_expression: t[:schedule_expression],
|
66
|
+
enabled: t[:enabled] != false,
|
67
|
+
description: t[:description],
|
68
|
+
target_id: t[:target_id],
|
69
|
+
task_definition_name: t[:task_definition_name],
|
70
|
+
revision: t[:revision],
|
71
|
+
task_count: t[:task_count],
|
72
|
+
role_arn: t[:role_arn],
|
73
|
+
)
|
74
|
+
scheduled_task.deploy
|
75
|
+
end
|
76
|
+
end
|
41
77
|
end
|
42
78
|
end
|
43
79
|
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
|
3
|
+
module EcsDeploy
|
4
|
+
class ScheduledTask
|
5
|
+
class PutTargetsFailure < StandardError; end
|
6
|
+
|
7
|
+
attr_reader :cluster, :region, :schedule_rule_name
|
8
|
+
|
9
|
+
def initialize(
|
10
|
+
cluster:, rule_name:, schedule_expression:, enabled: true, description: nil, target_id: nil,
|
11
|
+
task_definition_name:, revision: nil, task_count: nil, role_arn:,
|
12
|
+
region: nil
|
13
|
+
)
|
14
|
+
@cluster = cluster
|
15
|
+
@rule_name = rule_name
|
16
|
+
@schedule_expression = schedule_expression
|
17
|
+
@enabled = enabled
|
18
|
+
@description = description
|
19
|
+
@target_id = target_id || task_definition_name
|
20
|
+
@task_definition_name = task_definition_name
|
21
|
+
@task_count = task_count || 1
|
22
|
+
@revision = revision
|
23
|
+
@role_arn = role_arn
|
24
|
+
@region = region || EcsDeploy.config.default_region || ENV["AWS_DEFAULT_REGION"]
|
25
|
+
|
26
|
+
@client = Aws::ECS::Client.new(region: @region)
|
27
|
+
@cloud_watch_events = Aws::CloudWatchEvents::Client.new(region: @region)
|
28
|
+
end
|
29
|
+
|
30
|
+
def deploy
|
31
|
+
put_rule
|
32
|
+
put_targets
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def cluster_arn
|
38
|
+
cl = @client.describe_clusters(clusters: [@cluster]).clusters[0]
|
39
|
+
if cl
|
40
|
+
cl.cluster_arn
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def task_definition_arn
|
45
|
+
suffix = @revision ? ":#{@revision}" : ""
|
46
|
+
name = "#{@task_definition_name}#{suffix}"
|
47
|
+
@client.describe_task_definition(task_definition: name).task_definition.task_definition_arn
|
48
|
+
end
|
49
|
+
|
50
|
+
def put_rule
|
51
|
+
res = @cloud_watch_events.put_rule(
|
52
|
+
name: @rule_name,
|
53
|
+
schedule_expression: @schedule_expression,
|
54
|
+
state: @enabled ? "ENABLED" : "DISABLED",
|
55
|
+
description: @description,
|
56
|
+
)
|
57
|
+
EcsDeploy.logger.info "create cloudwatch event rule [#{res.rule_arn}] [#{@region}] [#{Paint['OK', :green]}]"
|
58
|
+
end
|
59
|
+
|
60
|
+
def put_targets
|
61
|
+
res = @cloud_watch_events.put_targets(
|
62
|
+
rule: @rule_name,
|
63
|
+
targets: [
|
64
|
+
{
|
65
|
+
id: @target_id,
|
66
|
+
arn: cluster_arn,
|
67
|
+
role_arn: @role_arn,
|
68
|
+
ecs_parameters: {
|
69
|
+
task_definition_arn: task_definition_arn,
|
70
|
+
task_count: @task_count,
|
71
|
+
},
|
72
|
+
}
|
73
|
+
]
|
74
|
+
)
|
75
|
+
if res.failed_entry_count.zero?
|
76
|
+
EcsDeploy.logger.info "create cloudwatch event target [#{@target_id}] [#{@region}] [#{Paint['OK', :green]}]"
|
77
|
+
else
|
78
|
+
res.failed_entries.each do |entry|
|
79
|
+
EcsDeploy.logger.error "failed to create cloudwatch event target [#{@region}] target_id=#{entry.target_id} error_code=#{entry.error_code} error_message=#{entry.error_message}"
|
80
|
+
end
|
81
|
+
raise PutTargetsFailure
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
data/lib/ecs_deploy/service.rb
CHANGED
@@ -3,6 +3,8 @@ require 'timeout'
|
|
3
3
|
module EcsDeploy
|
4
4
|
class Service
|
5
5
|
CHECK_INTERVAL = 5
|
6
|
+
MAX_DESCRIBE_SERVICES = 10
|
7
|
+
|
6
8
|
attr_reader :cluster, :region, :service_name
|
7
9
|
|
8
10
|
def initialize(
|
@@ -36,7 +38,7 @@ module EcsDeploy
|
|
36
38
|
task_definition: task_definition_name_with_revision,
|
37
39
|
deployment_configuration: @deployment_configuration,
|
38
40
|
}
|
39
|
-
if res.services.empty?
|
41
|
+
if res.services.select{ |s| s.status == 'ACTIVE' }.empty?
|
40
42
|
service_options.merge!({
|
41
43
|
service_name: @service_name,
|
42
44
|
desired_count: @desired_count.to_i,
|
@@ -74,10 +76,11 @@ module EcsDeploy
|
|
74
76
|
def self.wait_all_running(services)
|
75
77
|
services.group_by { |s| [s.cluster, s.region] }.each do |(cl, region), ss|
|
76
78
|
client = Aws::ECS::Client.new(region: region)
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
79
|
+
ss.map(&:service_name).each_slice(MAX_DESCRIBE_SERVICES) do |chunked_service_names|
|
80
|
+
client.wait_until(:services_stable, cluster: cl, services: chunked_service_names) do |w|
|
81
|
+
w.before_attempt do
|
82
|
+
EcsDeploy.logger.info "wait service stable [#{chunked_service_names.join(", ")}]"
|
83
|
+
end
|
81
84
|
end
|
82
85
|
end
|
83
86
|
end
|
@@ -11,7 +11,7 @@ module EcsDeploy
|
|
11
11
|
|
12
12
|
def initialize(
|
13
13
|
task_definition_name:, region: nil,
|
14
|
-
volumes: [], container_definitions: [],
|
14
|
+
network_mode: "bridge", volumes: [], container_definitions: [], placement_constraints: [],
|
15
15
|
task_role_arn: nil
|
16
16
|
)
|
17
17
|
@task_definition_name = task_definition_name
|
@@ -28,6 +28,8 @@ module EcsDeploy
|
|
28
28
|
cd
|
29
29
|
end
|
30
30
|
@volumes = volumes
|
31
|
+
@network_mode = network_mode
|
32
|
+
@placement_constraints = placement_constraints
|
31
33
|
|
32
34
|
@client = Aws::ECS::Client.new(region: @region)
|
33
35
|
end
|
@@ -43,13 +45,16 @@ module EcsDeploy
|
|
43
45
|
end
|
44
46
|
|
45
47
|
def register
|
46
|
-
@client.register_task_definition({
|
48
|
+
res = @client.register_task_definition({
|
47
49
|
family: @task_definition_name,
|
50
|
+
network_mode: @network_mode,
|
48
51
|
container_definitions: @container_definitions,
|
49
52
|
volumes: @volumes,
|
53
|
+
placement_constraints: @placement_constraints,
|
50
54
|
task_role_arn: @task_role_arn,
|
51
55
|
})
|
52
56
|
EcsDeploy.logger.info "register task definition [#{@task_definition_name}] [#{@region}] [#{Paint['OK', :green]}]"
|
57
|
+
res.task_definition
|
53
58
|
end
|
54
59
|
|
55
60
|
def run(info)
|
data/lib/ecs_deploy/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ecs_deploy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- joker1007
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2017-08-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: aws-sdk
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '2.
|
19
|
+
version: '2.9'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '2.
|
26
|
+
version: '2.9'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: terminal-table
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -100,6 +100,7 @@ files:
|
|
100
100
|
- lib/ecs_deploy/auto_scaler.rb
|
101
101
|
- lib/ecs_deploy/capistrano.rb
|
102
102
|
- lib/ecs_deploy/configuration.rb
|
103
|
+
- lib/ecs_deploy/scheduled_task.rb
|
103
104
|
- lib/ecs_deploy/service.rb
|
104
105
|
- lib/ecs_deploy/task_definition.rb
|
105
106
|
- lib/ecs_deploy/version.rb
|
@@ -122,7 +123,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
122
123
|
version: '0'
|
123
124
|
requirements: []
|
124
125
|
rubyforge_project:
|
125
|
-
rubygems_version: 2.6.
|
126
|
+
rubygems_version: 2.6.12
|
126
127
|
signing_key:
|
127
128
|
specification_version: 4
|
128
129
|
summary: AWS ECS deploy helper
|