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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a5027056d7641256dc566be9a2fd16dff8046f42
4
- data.tar.gz: 009c12a42f0fdcd18f3c8a53b35f0e48055112a8
3
+ metadata.gz: a565132ff142d819e1bdc64368264ebc89de1030
4
+ data.tar.gz: d0b5d389d5a9311853d8524e45c56bd54943450a
5
5
  SHA512:
6
- metadata.gz: c53395003a3e8953aef1d63cdf7a59d89b1f45e90b0acef1a00ff4420a4e96139e854427e204650aecfbf02f6a064348a6dea9f5919ef6b161a4e962e9d2b6bf
7
- data.tar.gz: 82ce007f7e3e5ac6f4c30a705a6c16ca16889302d4c316a08990fae786bb403f9679582788584a23032d6894f04bcb6b321c82155c759844283c2400279162e2
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/joker1007/ecs_deploy.
228
+ Bug reports and pull requests are welcome on GitHub at https://github.com/reproio/ecs_deploy.
@@ -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.4"
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
 
@@ -27,3 +27,4 @@ end
27
27
 
28
28
  require "ecs_deploy/task_definition"
29
29
  require "ecs_deploy/service"
30
+ require "ecs_deploy/scheduled_task"
@@ -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
- Thread.current["ecs_auto_scaler_ecs_#{region}"] ||= Aws::ECS::Client.new(
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
- client.update_service(
223
+ cl.update_service(
223
224
  cluster: cluster,
224
225
  service: name,
225
226
  desired_count: next_desired_count,
226
227
  )
227
- client.wait_until(:services_stable, cluster: cluster, services: [name]) do |w|
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 = client.list_container_instances(options)
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 = client.describe_container_instances(cluster: cluster, container_instances: arn_chunk).container_instances
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
- Thread.current["ecs_auto_scaler_cloud_watch_#{region}"] ||= Aws::CloudWatch::Client.new(
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
- Thread.current["ecs_auto_scaler_auto_scaling_#{region}"] ||= Aws::AutoScaling::Client.new(
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
- Thread.current["ecs_auto_scaler_ec2_#{region}"] ||= Aws::EC2::Client.new(
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 = [nil] if regions.empty?
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: r,
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.each do |exec|
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
@@ -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
- service_names = ss.map(&:service_name)
78
- client.wait_until(:services_stable, cluster: cl, services: service_names) do |w|
79
- w.before_attempt do
80
- EcsDeploy.logger.info "wait service stable [#{service_names.join(", ")}]"
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)
@@ -1,3 +1,3 @@
1
1
  module EcsDeploy
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
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.2.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: 2016-10-30 00:00:00.000000000 Z
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.4'
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.4'
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.4
126
+ rubygems_version: 2.6.12
126
127
  signing_key:
127
128
  specification_version: 4
128
129
  summary: AWS ECS deploy helper