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 +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: []
|