ecs_deploy 1.0.0 → 1.0.1

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