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 +4 -4
- data/CHANGELOG.md +15 -0
- data/README.md +3 -0
- data/lib/ecs_deploy/auto_scaler/service_config.rb +2 -1
- data/lib/ecs_deploy/auto_scaler/trigger_config.rb +11 -5
- data/lib/ecs_deploy/capistrano.rb +2 -2
- data/lib/ecs_deploy/configuration.rb +3 -0
- data/lib/ecs_deploy/instance_fluctuation_manager.rb +45 -23
- data/lib/ecs_deploy/service.rb +17 -23
- data/lib/ecs_deploy/task_definition.rb +10 -3
- data/lib/ecs_deploy/version.rb +1 -1
- metadata +6 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6b008d513950d8b8e5abab3400d9c123dfe22fa7ab0bce2db9fc13d398359ccf
|
4
|
+
data.tar.gz: 8b3096d4cbe25b0febf667b560a0202e915ab9eee2a511b50dc50d61938805b3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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 =
|
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 =
|
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
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
85
|
-
|
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
|
-
|
136
|
-
|
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
|
-
|
139
|
-
|
140
|
-
|
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)
|
data/lib/ecs_deploy/service.rb
CHANGED
@@ -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] }.
|
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
|
-
|
160
|
-
|
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
|
-
|
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
|
|
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: 1.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:
|
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.
|
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: []
|