ecs_deploy 1.0.0 → 1.0.1

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
  SHA256:
3
- metadata.gz: c88001bfd80f75aaeb09c2af4ce4a6e4c44976f465a2812ce1691d4c19e1acd6
4
- data.tar.gz: 1bfad93623edc78868040517a1a41006110e8881d661597b6d9b3416449f7b90
3
+ metadata.gz: 6b008d513950d8b8e5abab3400d9c123dfe22fa7ab0bce2db9fc13d398359ccf
4
+ data.tar.gz: 8b3096d4cbe25b0febf667b560a0202e915ab9eee2a511b50dc50d61938805b3
5
5
  SHA512:
6
- metadata.gz: 95f74dd98955021f9280feac7df80a44af75228b0c6460fb807d0b6f8d9976b981a0a253234b36ebd4e5a5f4a10770ba9f77d6782907a9d453e5a4be7bb5c913
7
- data.tar.gz: 837b94c1bf33af16303e2cf29720480fa17d4aa165eee2d7b92f9f17fc7facd1637d0475af0b003c5c14ba186042a267a192a46cd9f17dd489909c5839eef83e
6
+ metadata.gz: 1822d8b4ef6bf0392565918ee6bfe1df2d04e0ad376cb895497fbdd4029523f1db00452dd11f00c3c5acdedafa17039bfd49aebd8b16b4261cffb03a605a5fe9
7
+ data.tar.gz: 7a395b568354d77868bcd7b9c3d469550f8905fde2640bdace1e014d0639d5ab0a7f6f7b0f94fa6c2928c770a05837783c5994ce702c7fc31c744bc58ee46648
data/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # v1.0
2
2
 
3
+ ## Release v1.0.1 - 2021/05/19
4
+
5
+ ### Enhancement
6
+
7
+ * retry register_task_definition by AWS SDK feature
8
+ https://github.com/reproio/ecs_deploy/pull/67
9
+ * Support Ruby 3.0
10
+ https://github.com/reproio/ecs_deploy/pull/66
11
+ * Wait until stop old tasks
12
+ https://github.com/reproio/ecs_deploy/pull/65
13
+ * Add prioritized_over_upscale_triggers option to triggers
14
+ https://github.com/reproio/ecs_deploy/pull/62
15
+ * Display only unstable services in EcsDeploy::Service#wait_all_running
16
+ https://github.com/reproio/ecs_deploy/pull/61
17
+
3
18
  ## Release v1.0.0 - 2019/12/24
4
19
 
5
20
  ### New feature
data/README.md CHANGED
@@ -257,6 +257,9 @@ spot_fleet_requests:
257
257
  downscale_triggers:
258
258
  - alarm_name: "ECS [repro-worker-production] CPUUtilization (low)"
259
259
  state: OK
260
+ - alarm_name: "Aurora DMLLatency is high"
261
+ state: ALARM
262
+ prioritized_over_upscale_triggers: true
260
263
 
261
264
  # If you specify `spot_instance_intrp_warns_queue_urls` as SQS queue for spot instance interruption warnings,
262
265
  # autoscaler will polls them and set the state of instances to be intrrupted to "DRAINING".
@@ -45,8 +45,9 @@ module EcsDeploy
45
45
  end
46
46
  end
47
47
 
48
- if difference == 0 && desired_count > current_min_task_count
48
+ if desired_count > current_min_task_count
49
49
  downscale_triggers.each do |trigger|
50
+ next if difference > 0 && !trigger.prioritized_over_upscale_triggers?
50
51
  next unless trigger.match?
51
52
 
52
53
  @logger.info "#{log_prefix} Fire downscale trigger by #{trigger.alarm_name} #{trigger.state}"
@@ -5,9 +5,19 @@ require "ecs_deploy/auto_scaler/config_base"
5
5
 
6
6
  module EcsDeploy
7
7
  module AutoScaler
8
- TriggerConfig = Struct.new(:alarm_name, :region, :state, :step) do
8
+ TriggerConfig = Struct.new(:alarm_name, :region, :state, :step, :prioritized_over_upscale_triggers) do
9
9
  include ConfigBase
10
10
 
11
+ def match?
12
+ fetch_alarm.state_value == state
13
+ end
14
+
15
+ def prioritized_over_upscale_triggers?
16
+ !!prioritized_over_upscale_triggers
17
+ end
18
+
19
+ private
20
+
11
21
  def client
12
22
  Aws::CloudWatch::Client.new(
13
23
  access_key_id: EcsDeploy.config.access_key_id,
@@ -17,10 +27,6 @@ module EcsDeploy
17
27
  )
18
28
  end
19
29
 
20
- def match?
21
- fetch_alarm.state_value == state
22
- end
23
-
24
30
  def fetch_alarm
25
31
  res = client.describe_alarms(alarm_names: [alarm_name])
26
32
 
@@ -113,7 +113,7 @@ namespace :ecs do
113
113
  service_options[:placement_constraints] = service[:placement_constraints] if service[:placement_constraints]
114
114
  service_options[:placement_strategy] = service[:placement_strategy] if service[:placement_strategy]
115
115
  service_options[:scheduling_strategy] = service[:scheduling_strategy] if service[:scheduling_strategy]
116
- s = EcsDeploy::Service.new(service_options)
116
+ s = EcsDeploy::Service.new(**service_options)
117
117
  s.deploy
118
118
  s
119
119
  end
@@ -178,7 +178,7 @@ namespace :ecs do
178
178
  service_options[:deployment_configuration] = service[:deployment_configuration] if service[:deployment_configuration]
179
179
  service_options[:placement_constraints] = service[:placement_constraints] if service[:placement_constraints]
180
180
  service_options[:placement_strategy] = service[:placement_strategy] if service[:placement_strategy]
181
- s = EcsDeploy::Service.new(service_options)
181
+ s = EcsDeploy::Service.new(**service_options)
182
182
  s.deploy
183
183
  EcsDeploy::TaskDefinition.deregister(current_task_definition_arn, region: r)
184
184
  s
@@ -13,6 +13,9 @@ module EcsDeploy
13
13
  def initialize
14
14
  @log_level = :info
15
15
  @deploy_wait_timeout = 300
16
+ # The following values are the default values of Aws::ECS::Waiters::ServicesStable
17
+ @ecs_wait_until_services_stable_max_attempts = 40
18
+ @ecs_wait_until_services_stable_delay = 15
16
19
  end
17
20
  end
18
21
  end
@@ -8,6 +8,7 @@ module EcsDeploy
8
8
 
9
9
  MAX_UPDATABLE_ECS_CONTAINER_COUNT = 10
10
10
  MAX_DETACHEABLE_EC2_INSTACE_COUNT = 20
11
+ MAX_DESCRIBABLE_ECS_TASK_COUNT = 100
11
12
 
12
13
  def initialize(region:, cluster:, auto_scaling_group_name:, desired_capacity:, logger:)
13
14
  @region = region
@@ -18,7 +19,7 @@ module EcsDeploy
18
19
  end
19
20
 
20
21
  def increase
21
- asg = as_client.describe_auto_scaling_groups(auto_scaling_group_names: [@auto_scaling_group_name]).auto_scaling_groups.first
22
+ asg = fetch_auto_scaling_group
22
23
 
23
24
  @logger.info("Increase desired capacity of #{@auto_scaling_group_name}: #{asg.desired_capacity} => #{asg.max_size}")
24
25
  as_client.update_auto_scaling_group(auto_scaling_group_name: @auto_scaling_group_name, desired_capacity: asg.max_size)
@@ -39,7 +40,7 @@ module EcsDeploy
39
40
  end
40
41
 
41
42
  def decrease
42
- asg = as_client.describe_auto_scaling_groups(auto_scaling_group_names: [@auto_scaling_group_name]).auto_scaling_groups.first
43
+ asg = fetch_auto_scaling_group
43
44
 
44
45
  decrease_count = asg.desired_capacity - @desired_capacity
45
46
  if decrease_count <= 0
@@ -48,13 +49,12 @@ module EcsDeploy
48
49
  end
49
50
  @logger.info("Decrease desired capacity of #{@auto_scaling_group_name}: #{asg.desired_capacity} => #{@desired_capacity}")
50
51
 
51
- container_instance_arns = ecs_client.list_container_instances(
52
- cluster: @cluster
53
- ).container_instance_arns
54
- container_instances = ecs_client.describe_container_instances(
55
- cluster: @cluster,
56
- container_instances: container_instance_arns
57
- ).container_instances
52
+ container_instances = ecs_client.list_container_instances(cluster: @cluster).flat_map do |resp|
53
+ ecs_client.describe_container_instances(
54
+ cluster: @cluster,
55
+ container_instances: resp.container_instance_arns
56
+ ).container_instances
57
+ end
58
58
 
59
59
  az_to_container_instances = container_instances.sort_by {|ci| - ci.running_tasks_count }.group_by do |ci|
60
60
  ci.attributes.find {|attribute| attribute.name == "ecs.availability-zone" }.value
@@ -66,24 +66,22 @@ module EcsDeploy
66
66
 
67
67
  target_container_instances = extract_target_container_instances(decrease_count, az_to_container_instances)
68
68
 
69
- threads = []
69
+ @logger.info("running tasks: #{ecs_client.list_tasks(cluster: @cluster).task_arns.size}")
70
70
  all_running_task_arns = []
71
71
  target_container_instances.map(&:container_instance_arn).each_slice(MAX_UPDATABLE_ECS_CONTAINER_COUNT) do |arns|
72
+ @logger.info(arns)
72
73
  ecs_client.update_container_instances_state(
73
74
  cluster: @cluster,
74
75
  container_instances: arns,
75
76
  status: "DRAINING"
76
77
  )
77
78
  arns.each do |arn|
78
- threads << Thread.new(arn) do |a|
79
- all_running_task_arns.concat(stop_tasks(a))
80
- end
79
+ all_running_task_arns.concat(list_running_task_arns(arn))
81
80
  end
82
81
  end
83
82
 
84
- threads.each(&:join)
85
- ecs_client.wait_until(:tasks_stopped, cluster: @cluster, tasks: all_running_task_arns) unless all_running_task_arns.empty?
86
- @logger.info("All running tasks are stopped")
83
+ stop_tasks_not_belonging_service(all_running_task_arns)
84
+ wait_until_tasks_stopped(all_running_task_arns)
87
85
 
88
86
  instance_ids = target_container_instances.map(&:ec2_instance_id)
89
87
  terminate_instances(instance_ids)
@@ -115,6 +113,10 @@ module EcsDeploy
115
113
  @ecs_client ||= Aws::ECS::Client.new(aws_params)
116
114
  end
117
115
 
116
+ def fetch_auto_scaling_group
117
+ as_client.describe_auto_scaling_groups(auto_scaling_group_names: [@auto_scaling_group_name]).auto_scaling_groups.first
118
+ end
119
+
118
120
  # Extract container instances to terminate considering AZ balance
119
121
  def extract_target_container_instances(decrease_count, az_to_container_instances)
120
122
  target_container_instances = []
@@ -132,16 +134,36 @@ module EcsDeploy
132
134
  target_container_instances
133
135
  end
134
136
 
135
- def stop_tasks(arn)
136
- running_task_arns = ecs_client.list_tasks(cluster: @cluster, container_instance: arn).task_arns
137
+ # list tasks whose desired_status is "RUNNING" or
138
+ # whoose desired_status is "STOPPED" but last_status is "RUNNING" on the ECS container
139
+ def list_running_task_arns(container_instance_arn)
140
+ running_tasks_arn = ecs_client.list_tasks(cluster: @cluster, container_instance: container_instance_arn).flat_map(&:task_arns)
141
+ stopped_tasks_arn = ecs_client.list_tasks(cluster: @cluster, container_instance: container_instance_arn, desired_status: "STOPPED").flat_map(&:task_arns)
142
+ stopped_running_task_arns = stopped_tasks_arn.each_slice(MAX_DESCRIBABLE_ECS_TASK_COUNT).flat_map do |arns|
143
+ ecs_client.describe_tasks(cluster: @cluster, tasks: arns).tasks.select do |task|
144
+ task.desired_status == "STOPPED" && task.last_status == "RUNNING"
145
+ end
146
+ end.map(&:task_arn)
147
+ running_tasks_arn + stopped_running_task_arns
148
+ end
149
+
150
+ def wait_until_tasks_stopped(task_arns)
151
+ @logger.info("All old tasks: #{task_arns.size}")
152
+ task_arns.each_slice(MAX_DESCRIBABLE_ECS_TASK_COUNT).each do |arns|
153
+ ecs_client.wait_until(:tasks_stopped, cluster: @cluster, tasks: arns)
154
+ end
155
+ @logger.info("All old tasks are stopped")
156
+ end
157
+
158
+ def stop_tasks_not_belonging_service(running_task_arns)
159
+ @logger.info("Running tasks: #{running_task_arns.size}")
137
160
  unless running_task_arns.empty?
138
- running_tasks = ecs_client.describe_tasks(cluster: @cluster, tasks: running_task_arns).tasks
139
- running_tasks.each do |task|
140
- ecs_client.stop_task(cluster: @cluster, task: task.task_arn) if task.group.start_with?("family:")
161
+ running_task_arns.each_slice(MAX_DESCRIBABLE_ECS_TASK_COUNT).each do |arns|
162
+ ecs_client.describe_tasks(cluster: @cluster, tasks: arns).tasks.each do |task|
163
+ ecs_client.stop_task(cluster: @cluster, task: task.task_arn) if task.group.start_with?("family:")
164
+ end
141
165
  end
142
166
  end
143
- @logger.info("Tasks running on #{arn.split('/').last} will be stopped")
144
- running_task_arns
145
167
  end
146
168
 
147
169
  def terminate_instances(instance_ids)
@@ -5,6 +5,8 @@ module EcsDeploy
5
5
  CHECK_INTERVAL = 5
6
6
  MAX_DESCRIBE_SERVICES = 10
7
7
 
8
+ class TooManyAttemptsError < StandardError; end
9
+
8
10
  attr_reader :cluster, :region, :service_name, :delete
9
11
 
10
12
  def initialize(
@@ -137,35 +139,27 @@ module EcsDeploy
137
139
  end
138
140
  end
139
141
 
140
- def wait_running
141
- return if @response.nil?
142
-
143
- service = @response.service
144
-
145
- @client.wait_until(:services_stable, cluster: @cluster, services: [service.service_name]) do |w|
146
- w.delay = EcsDeploy.config.ecs_wait_until_services_stable_delay if EcsDeploy.config.ecs_wait_until_services_stable_delay
147
- w.max_attempts = EcsDeploy.config.ecs_wait_until_services_stable_max_attempts if EcsDeploy.config.ecs_wait_until_services_stable_max_attempts
148
-
149
- w.before_attempt do
150
- EcsDeploy.logger.info "wait service stable [#{service.service_name}]"
151
- end
152
- end
153
- end
154
-
155
142
  def self.wait_all_running(services)
156
- services.group_by { |s| [s.cluster, s.region] }.each do |(cl, region), ss|
143
+ services.group_by { |s| [s.cluster, s.region] }.flat_map do |(cl, region), ss|
157
144
  client = Aws::ECS::Client.new(region: region)
158
- ss.reject(&:delete).map(&:service_name).each_slice(MAX_DESCRIBE_SERVICES) do |chunked_service_names|
159
- client.wait_until(:services_stable, cluster: cl, services: chunked_service_names) do |w|
160
- w.delay = EcsDeploy.config.ecs_wait_until_services_stable_delay if EcsDeploy.config.ecs_wait_until_services_stable_delay
161
- w.max_attempts = EcsDeploy.config.ecs_wait_until_services_stable_max_attempts if EcsDeploy.config.ecs_wait_until_services_stable_max_attempts
162
-
163
- w.before_attempt do
145
+ ss.reject(&:delete).map(&:service_name).each_slice(MAX_DESCRIBE_SERVICES).map do |chunked_service_names|
146
+ Thread.new do
147
+ EcsDeploy.config.ecs_wait_until_services_stable_max_attempts.times do
164
148
  EcsDeploy.logger.info "wait service stable [#{chunked_service_names.join(", ")}]"
149
+ resp = client.describe_services(cluster: cl, services: chunked_service_names)
150
+ resp.services.each do |s|
151
+ # cf. https://github.com/aws/aws-sdk-ruby/blob/master/gems/aws-sdk-ecs/lib/aws-sdk-ecs/waiters.rb#L91-L96
152
+ if s.deployments.size == 1 && s.running_count == s.desired_count
153
+ chunked_service_names.delete(s.service_name)
154
+ end
155
+ end
156
+ break if chunked_service_names.empty?
157
+ sleep EcsDeploy.config.ecs_wait_until_services_stable_delay
165
158
  end
159
+ raise TooManyAttemptsError unless chunked_service_names.empty?
166
160
  end
167
161
  end
168
- end
162
+ end.each(&:join)
169
163
  end
170
164
 
171
165
  private
@@ -1,8 +1,15 @@
1
1
  module EcsDeploy
2
2
  class TaskDefinition
3
+ RETRY_BACKOFF = lambda do |c|
4
+ sleep(1)
5
+ end
6
+
7
+ RETRY_LIMIT = 10
8
+
3
9
  def self.deregister(arn, region: nil)
4
10
  region ||= EcsDeploy.config.default_region
5
- client = region ? Aws::ECS::Client.new(region: region) : Aws::ECS::Client.new
11
+ param = {retry_backoff: RETRY_BACKOFF, retry_limit: RETRY_LIMIT}
12
+ client = region ? Aws::ECS::Client.new(param.merge(region: region)) : Aws::ECS::Client.new(param)
6
13
  client.deregister_task_definition({
7
14
  task_definition: arn,
8
15
  })
@@ -39,8 +46,8 @@ module EcsDeploy
39
46
  @cpu = cpu&.to_s
40
47
  @memory = memory&.to_s
41
48
  @tags = tags
42
-
43
- @client = region ? Aws::ECS::Client.new(region: region) : Aws::ECS::Client.new
49
+ param = {retry_backoff: RETRY_BACKOFF, retry_limit: RETRY_LIMIT}
50
+ @client = region ? Aws::ECS::Client.new(param.merge(region: region)) : Aws::ECS::Client.new(param)
44
51
  @region = @client.config.region
45
52
  end
46
53
 
@@ -1,3 +1,3 @@
1
1
  module EcsDeploy
2
- VERSION = "1.0.0"
2
+ VERSION = "1.0.1"
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: 1.0.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - joker1007
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-12-26 00:00:00.000000000 Z
11
+ date: 2021-05-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aws-sdk-autoscaling
@@ -207,7 +207,7 @@ files:
207
207
  homepage: https://github.com/reproio/ecs_deploy
208
208
  licenses: []
209
209
  metadata: {}
210
- post_install_message:
210
+ post_install_message:
211
211
  rdoc_options: []
212
212
  require_paths:
213
213
  - lib
@@ -222,8 +222,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
222
222
  - !ruby/object:Gem::Version
223
223
  version: '0'
224
224
  requirements: []
225
- rubygems_version: 3.1.0
226
- signing_key:
225
+ rubygems_version: 3.2.15
226
+ signing_key:
227
227
  specification_version: 4
228
228
  summary: AWS ECS deploy helper
229
229
  test_files: []