sidekiq-ecs-scaler 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 6e788a43a1d4013b88babd60929d35dc465dca7dbe73daa633566929be57f501
4
+ data.tar.gz: 9a6c856115de94d05edb69662d2de03ea0ae536d40d3b36a1f74f1f2f2c9239b
5
+ SHA512:
6
+ metadata.gz: 137a41c9d0c6fbc53bb16652b7d9c711347618e2c3ce37130ce1087ec774c09956c0746e345d6bbd7bca5395aaa36b02c1f1a6a08e9a4c65a3eb51dd347a0280
7
+ data.tar.gz: bdab45f712315863eb32d08e3aba9edb35db1d9d50f2966206baf6948ea1ce97cc9a6672402ffd09cd66ab27d5a222a02fa3fb8d902d9d7e9287bfb4045824e1
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,21 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.6
3
+ NewCops: enable
4
+
5
+ Style/StringLiterals:
6
+ Enabled: true
7
+ EnforcedStyle: double_quotes
8
+
9
+ Style/StringLiteralsInInterpolation:
10
+ Enabled: true
11
+ EnforcedStyle: double_quotes
12
+
13
+ Layout/LineLength:
14
+ Max: 120
15
+
16
+ Metrics/BlockLength:
17
+ Exclude:
18
+ - spec/**/*_spec.rb
19
+
20
+ Naming/FileName:
21
+ Enabled: false
data/.yardopts ADDED
@@ -0,0 +1,3 @@
1
+ --fail-on-warning
2
+ --markup markdown
3
+ --private
data/CHANGELOG.md ADDED
@@ -0,0 +1,4 @@
1
+ ## [0.1.0] - 2021-11-29
2
+
3
+ - initial release
4
+ - only supported when deploying Sidekiq workers to AWS ECS Fargate (1.14.0+)
data/Dockerfile ADDED
@@ -0,0 +1,16 @@
1
+ FROM ruby:3.0.3-slim
2
+
3
+ RUN apt-get update \
4
+ && apt-get install -y build-essential git \
5
+ && apt-get clean \
6
+ && rm -rf /var/lib/apt/lists/*
7
+
8
+ WORKDIR /usr/src/sidekiq-ecs-scaler
9
+
10
+ RUN mkdir -p lib/sidekiq-ecs-scaler \
11
+ && echo "module SidekiqEcsScaler\n VERSION = \"0.1.0\"\nend\n" > lib/sidekiq-ecs-scaler/version.rb
12
+
13
+ COPY bin/setup ./bin/
14
+ COPY Gemfile Gemfile.lock sidekiq-ecs-scaler.gemspec .
15
+
16
+ RUN bin/setup
data/Gemfile ADDED
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in sidekiq-ecs-scaler.gemspec
6
+ gemspec
7
+
8
+ gem "rake"
9
+ gem "rspec"
10
+ gem "rubocop"
11
+ gem "rubocop-rake"
12
+ gem "rubocop-rspec"
13
+ gem "simplecov"
14
+ gem "steep"
15
+ gem "yard"
data/Gemfile.lock ADDED
@@ -0,0 +1,128 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ sidekiq-ecs-scaler (0.1.0)
5
+ aws-sdk-ecs (> 1, < 2)
6
+ sidekiq (> 5, < 7)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ activesupport (6.1.4.1)
12
+ concurrent-ruby (~> 1.0, >= 1.0.2)
13
+ i18n (>= 1.6, < 2)
14
+ minitest (>= 5.1)
15
+ tzinfo (~> 2.0)
16
+ zeitwerk (~> 2.3)
17
+ ast (2.4.2)
18
+ aws-eventstream (1.2.0)
19
+ aws-partitions (1.535.0)
20
+ aws-sdk-core (3.123.0)
21
+ aws-eventstream (~> 1, >= 1.0.2)
22
+ aws-partitions (~> 1, >= 1.525.0)
23
+ aws-sigv4 (~> 1.1)
24
+ jmespath (~> 1.0)
25
+ aws-sdk-ecs (1.91.0)
26
+ aws-sdk-core (~> 3, >= 3.122.0)
27
+ aws-sigv4 (~> 1.1)
28
+ aws-sigv4 (1.4.0)
29
+ aws-eventstream (~> 1, >= 1.0.2)
30
+ concurrent-ruby (1.1.9)
31
+ connection_pool (2.2.5)
32
+ diff-lcs (1.4.4)
33
+ docile (1.4.0)
34
+ ffi (1.15.4)
35
+ i18n (1.8.11)
36
+ concurrent-ruby (~> 1.0)
37
+ jmespath (1.4.0)
38
+ language_server-protocol (3.16.0.3)
39
+ listen (3.7.0)
40
+ rb-fsevent (~> 0.10, >= 0.10.3)
41
+ rb-inotify (~> 0.9, >= 0.9.10)
42
+ minitest (5.14.4)
43
+ parallel (1.21.0)
44
+ parser (3.0.3.0)
45
+ ast (~> 2.4.1)
46
+ rack (2.2.3)
47
+ rainbow (3.0.0)
48
+ rake (13.0.6)
49
+ rb-fsevent (0.11.0)
50
+ rb-inotify (0.10.1)
51
+ ffi (~> 1.0)
52
+ rbs (1.7.1)
53
+ redis (4.5.1)
54
+ regexp_parser (2.1.1)
55
+ rexml (3.2.5)
56
+ rspec (3.10.0)
57
+ rspec-core (~> 3.10.0)
58
+ rspec-expectations (~> 3.10.0)
59
+ rspec-mocks (~> 3.10.0)
60
+ rspec-core (3.10.1)
61
+ rspec-support (~> 3.10.0)
62
+ rspec-expectations (3.10.1)
63
+ diff-lcs (>= 1.2.0, < 2.0)
64
+ rspec-support (~> 3.10.0)
65
+ rspec-mocks (3.10.2)
66
+ diff-lcs (>= 1.2.0, < 2.0)
67
+ rspec-support (~> 3.10.0)
68
+ rspec-support (3.10.3)
69
+ rubocop (1.23.0)
70
+ parallel (~> 1.10)
71
+ parser (>= 3.0.0.0)
72
+ rainbow (>= 2.2.2, < 4.0)
73
+ regexp_parser (>= 1.8, < 3.0)
74
+ rexml
75
+ rubocop-ast (>= 1.12.0, < 2.0)
76
+ ruby-progressbar (~> 1.7)
77
+ unicode-display_width (>= 1.4.0, < 3.0)
78
+ rubocop-ast (1.13.0)
79
+ parser (>= 3.0.1.1)
80
+ rubocop-rake (0.6.0)
81
+ rubocop (~> 1.0)
82
+ rubocop-rspec (2.6.0)
83
+ rubocop (~> 1.19)
84
+ ruby-progressbar (1.11.0)
85
+ sidekiq (6.3.1)
86
+ connection_pool (>= 2.2.2)
87
+ rack (~> 2.0)
88
+ redis (>= 4.2.0)
89
+ simplecov (0.21.2)
90
+ docile (~> 1.1)
91
+ simplecov-html (~> 0.11)
92
+ simplecov_json_formatter (~> 0.1)
93
+ simplecov-html (0.12.3)
94
+ simplecov_json_formatter (0.1.3)
95
+ steep (0.46.0)
96
+ activesupport (>= 5.1)
97
+ language_server-protocol (>= 3.15, < 4.0)
98
+ listen (~> 3.0)
99
+ parallel (>= 1.0.0)
100
+ parser (>= 3.0)
101
+ rainbow (>= 2.2.2, < 4.0)
102
+ rbs (>= 1.2.0)
103
+ terminal-table (>= 2, < 4)
104
+ terminal-table (3.0.2)
105
+ unicode-display_width (>= 1.1.1, < 3)
106
+ tzinfo (2.0.4)
107
+ concurrent-ruby (~> 1.0)
108
+ unicode-display_width (2.1.0)
109
+ yard (0.9.26)
110
+ zeitwerk (2.5.1)
111
+
112
+ PLATFORMS
113
+ x86_64-darwin-19
114
+ x86_64-linux
115
+
116
+ DEPENDENCIES
117
+ rake
118
+ rspec
119
+ rubocop
120
+ rubocop-rake
121
+ rubocop-rspec
122
+ sidekiq-ecs-scaler!
123
+ simplecov
124
+ steep
125
+ yard
126
+
127
+ BUNDLED WITH
128
+ 2.2.32
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2021 shoma07
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,72 @@
1
+ # SidekiqEcsScaler
2
+
3
+ Auto scaler of [Sidekiq](https://github.com/mperham/sidekiq) worker deploymented to AWS ECS.
4
+
5
+ Only supported when deploying Sidekiq workers to AWS ECS Fargate (1.14.0+)
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'sidekiq-ecs-scaler'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle install
18
+
19
+ ## Usage
20
+
21
+ ### Configuration
22
+
23
+ ```ruby
24
+ SidekiqEcsScaler.configure do |config|
25
+ # queue to monitor latency, default is "default"
26
+ config.queue_name = "default"
27
+
28
+ # minimum number of tasks, default is 1
29
+ config.min_count = 1
30
+
31
+ # maximum number of tasks, default is 1
32
+ config.max_count = 3
33
+
34
+ # maximum latency(seconds), default is 3600
35
+ config.max_latency = 3600
36
+
37
+ # custom ECS Client
38
+ config.ecs_client = Aws::ECS::Client.new
39
+
40
+ # Set worker options for scaling
41
+ config.sidekiq_options = { "retry" => true, "queue" => "scheduler" }
42
+ end
43
+ ```
44
+
45
+ ### With Sidekiq Scheduler
46
+
47
+ When using [sidekiq-scheduler](https://github.com/moove-it/sidekiq-scheduler), schedule the scale by setting as follows.
48
+
49
+ ```yml
50
+ # sidekiq.yml
51
+ # example
52
+
53
+ :schedule:
54
+ SidekiqEcsScaler::Worker:
55
+ cron: "0 */15 * * * *"
56
+ # It is safe to set this queue to have a higher priority than the monitored queue.
57
+ queue: scheduler
58
+ ```
59
+
60
+ ## Development
61
+
62
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
63
+
64
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
65
+
66
+ ## Contributing
67
+
68
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/sidekiq-ecs-scaler.
69
+
70
+ ## License
71
+
72
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+
5
+ # rspec
6
+ require "rspec/core/rake_task"
7
+ RSpec::Core::RakeTask.new(:spec)
8
+
9
+ # rubocop
10
+ require "rubocop/rake_task"
11
+ RuboCop::RakeTask.new(:lint) do |t|
12
+ t.options = %w[--parallel]
13
+ end
14
+ namespace :lint do
15
+ desc "Lint fix (Rubocop)"
16
+ task fix: :auto_correct
17
+ end
18
+
19
+ # steep
20
+ # @see https://github.com/soutaro/steep/blob/master/exe/steep
21
+ require "steep"
22
+ require "steep/cli"
23
+ desc "Typecheck (Steep)"
24
+ task :typecheck do
25
+ Steep::CLI.new(
26
+ argv: %w[check -j4], stdout: $stdout, stderr: $stderr, stdin: $stdin
27
+ ).run.zero? || exit(1)
28
+ end
29
+
30
+ # yard
31
+ ## custom rake task
32
+ require "yard"
33
+ desc "document"
34
+ task :doc do
35
+ YARD::CLI::CommandParser.run
36
+ `yard`.lines(chomp: true).last.match(/\d+/)[0].to_i == 100 || exit(1)
37
+ end
38
+
39
+ task default: %i[lint typecheck spec doc]
data/Steepfile ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ target :lib do
4
+ signature "sig", "sig-private"
5
+
6
+ check "lib"
7
+
8
+ library "json"
9
+ end
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "sidekiq-ecs-scaler"
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require "irb"
15
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,9 @@
1
+ version: "3"
2
+
3
+ services:
4
+ sidekiq-ecs-scaler:
5
+ build:
6
+ context: .
7
+ volumes:
8
+ - .:/usr/src/sidekiq-ecs-scaler
9
+ command: /bin/bash
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "sidekiq-ecs-scaler"
5
+
6
+ raise "`sidekiq-ecs-scaler` command is not yet supported!"
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SidekiqEcsScaler
4
+ # SidekiqEcsScaler::Client
5
+ class Client
6
+ # @param config [SidekiqEcsScaler::Configuration]
7
+ # @return [void]
8
+ def initialize(config)
9
+ @config = config
10
+ end
11
+
12
+ # @return [Integer, nil]
13
+ def update_desired_count
14
+ return unless config.task_meta
15
+
16
+ desired_count_by_latency.then do |desired_count|
17
+ describe_service.then do |service|
18
+ update_service(service: service, desired_count: desired_count) if service.desired_count != desired_count
19
+
20
+ desired_count
21
+ end
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ # @!attribute [r] config
28
+ # @return [SidekiqEcsScaler::Configuration]
29
+ attr_reader :config
30
+
31
+ # @return [Float]
32
+ def queue_latency
33
+ Sidekiq::Queue.new(config.queue_name).latency
34
+ end
35
+
36
+ # @return [Integer]
37
+ def desired_count_by_latency
38
+ (config.min_count..config.max_count).to_a.at(
39
+ (queue_latency.to_f / config.latency_per_count).floor.to_i
40
+ ) || config.max_count
41
+ end
42
+
43
+ # @return [String]
44
+ def service_name
45
+ config.task_meta!.then do |task_meta|
46
+ config.ecs_client.describe_tasks(
47
+ { cluster: task_meta.cluster, tasks: [task_meta.task_arn] }
48
+ ).tasks.first&.then { |task| task.group.delete_prefix("service:") } || (
49
+ raise Error, "Task(#{task_meta.task_arn}) does not exist in cluster!"
50
+ )
51
+ end
52
+ end
53
+
54
+ # @return [Aws::ECS::Types::Service]
55
+ def describe_service
56
+ config.ecs_client.describe_services(
57
+ { cluster: config.task_meta!.cluster, services: [service_name] }
58
+ ).services.first || (raise Error, "Service(#{service_name}) does not exist in cluster!")
59
+ end
60
+
61
+ # @param service [Aws::ECS::Types::Service]
62
+ # @param desired_count [Integer]
63
+ # @return [Aws::ECS::Types::UpdateServiceResponse]
64
+ def update_service(service:, desired_count:)
65
+ config.ecs_client.update_service(
66
+ {
67
+ cluster: service.cluster_arn,
68
+ service: service.service_name,
69
+ desired_count: desired_count
70
+ }
71
+ )
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SidekiqEcsScaler
4
+ # SidekiqEcsScaler::Configuration
5
+ class Configuration
6
+ # @!attribute [r] queue_name
7
+ # @return [String]
8
+ attr_reader :queue_name
9
+ # @!attribute [r] min_count
10
+ # @return [Integer]
11
+ attr_reader :min_count
12
+ # @!attribute [r] max_count
13
+ # @return [Integer]
14
+ attr_reader :max_count
15
+ # @!attribute [r] max_latency
16
+ # @return [Integer]
17
+ attr_reader :max_latency
18
+ # @!attribute [r] task_meta
19
+ # @return [SidekiqEcsScaler::TaskMetaV4, nil]
20
+ attr_reader :task_meta
21
+ # @!attribute [r] ecs_client
22
+ # @return [Aws::ECS::Client]
23
+ attr_reader :ecs_client
24
+
25
+ # @return [void]
26
+ def initialize
27
+ @queue_name = "default"
28
+ @min_count = 1
29
+ @max_count = 1
30
+ @max_latency = 3600
31
+ @ecs_client = Aws::ECS::Client.new
32
+ @task_meta = TaskMetaV4.build_or_null
33
+ end
34
+
35
+ # @param queue_name [String]
36
+ # @return [void]
37
+ def queue_name=(queue_name)
38
+ raise ArgumentError unless queue_name.instance_of?(String)
39
+
40
+ @queue_name = queue_name
41
+ end
42
+
43
+ # @param min_count [Integer]
44
+ # @return [void]
45
+ # @raise [ArgumentError]
46
+ def min_count=(min_count)
47
+ raise ArgumentError unless min_count.positive?
48
+
49
+ @min_count = min_count
50
+ @max_count = min_count if min_count > max_count
51
+ end
52
+
53
+ # @param max_count [Integer]
54
+ # @return [void]
55
+ # @raise [ArgumentError]
56
+ def max_count=(max_count)
57
+ raise ArgumentError unless max_count.positive?
58
+
59
+ @max_count = max_count
60
+ @min_count = max_count if max_count < min_count
61
+ end
62
+
63
+ # @param max_latency [Integer]
64
+ # @return [void]
65
+ # @raise [ArgumentError]
66
+ def max_latency=(max_latency)
67
+ raise ArgumentError if max_count > max_latency
68
+
69
+ @max_latency = max_latency
70
+ end
71
+
72
+ # @param ecs_client [Aws::ECS::Client]
73
+ # @return [void]
74
+ # @raise [ArgumentError]
75
+ def ecs_client=(ecs_client)
76
+ raise ArgumentError unless ecs_client.is_a?(Aws::ECS::Client)
77
+
78
+ @ecs_client = ecs_client
79
+ end
80
+
81
+ # @return [Integer]
82
+ def latency_per_count
83
+ (max_latency / (1 + max_count - min_count)).tap do |value|
84
+ value.positive? || (raise Error, "latency per count isn't positive!")
85
+ end
86
+ end
87
+
88
+ # @return [SidekiqEcsScaler::TaskMetaV4]
89
+ def task_meta!
90
+ task_meta || (raise Error, "task metadata is null!")
91
+ end
92
+
93
+ # @return [Hash]
94
+ def sidekiq_options
95
+ ::SidekiqEcsScaler::Worker.sidekiq_options
96
+ end
97
+
98
+ # @param sidekiq_options [Hash]
99
+ # @return [void]
100
+ def sidekiq_options=(sidekiq_options)
101
+ raise ArgumentError unless sidekiq_options.is_a?(Hash)
102
+
103
+ ::SidekiqEcsScaler::Worker.sidekiq_options(sidekiq_options)
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SidekiqEcsScaler
4
+ # SidekiqEcsScaler::TaskMetaV4
5
+ #
6
+ # @see https://docs.aws.amazon.com/AmazonECS/latest/userguide/task-metadata-endpoint-v4-fargate.html
7
+ class TaskMetaV4
8
+ class << self
9
+ # @todo If the metadata acquisition fails, an error will be output to the log.
10
+ #
11
+ # @return [SidekiqEcsScaler::TaskMetaV4, nil]
12
+ def build_or_null
13
+ ENV.fetch("ECS_CONTAINER_METADATA_URI_V4", nil)&.then do |uri|
14
+ new(JSON.parse(Net::HTTP.get(URI.parse("#{uri}/task"))))
15
+ end
16
+ rescue StandardError
17
+ nil
18
+ end
19
+ end
20
+
21
+ # @!attribute [r] cluster
22
+ # @return [String]
23
+ attr_reader :cluster
24
+ # @!attribute [r] task_arn
25
+ # @return [String]
26
+ attr_reader :task_arn
27
+
28
+ # @param resp [Hash]
29
+ # @return [void]
30
+ def initialize(resp)
31
+ @cluster = resp.fetch("Cluster")
32
+ @task_arn = resp.fetch("TaskARN")
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SidekiqEcsScaler
4
+ # @return [String]
5
+ VERSION = "0.1.0"
6
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SidekiqEcsScaler
4
+ # SidekiqEcsScaler::Worker
5
+ class Worker
6
+ include Sidekiq::Worker
7
+
8
+ # @return [Integer, nil]
9
+ def perform
10
+ SidekiqEcsScaler.client.update_desired_count
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+ require "net/http"
5
+ require "json"
6
+ require "sidekiq"
7
+ require "sidekiq/api"
8
+ require "aws-sdk-ecs"
9
+ require_relative "sidekiq-ecs-scaler/version"
10
+ require_relative "sidekiq-ecs-scaler/task_meta_v4"
11
+ require_relative "sidekiq-ecs-scaler/configuration"
12
+ require_relative "sidekiq-ecs-scaler/client"
13
+ require_relative "sidekiq-ecs-scaler/worker"
14
+
15
+ # SidekiqEcsScaler
16
+ module SidekiqEcsScaler
17
+ # SidekiqEcsScaler::Error
18
+ class Error < StandardError; end
19
+
20
+ class << self
21
+ # @return [SidekiqEcsScaler::Configuration]
22
+ def config
23
+ @config ||= Configuration.new
24
+ end
25
+
26
+ # @yieldparam config [SidekiqEcsScaler::Configuration]
27
+ # @yieldreturn [void]
28
+ # @return [void]
29
+ def configure
30
+ raise Error, "No block is given!" unless block_given?
31
+
32
+ yield config
33
+ end
34
+
35
+ # @return [SidekiqEcsScaler::Client]
36
+ def client
37
+ Client.new(config)
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/sidekiq-ecs-scaler/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "sidekiq-ecs-scaler"
7
+ spec.version = SidekiqEcsScaler::VERSION
8
+ spec.authors = ["shoma07"]
9
+ spec.email = ["23730734+shoma07@users.noreply.github.com"]
10
+
11
+ spec.summary = "auto scaler of sidekiq worker deploymented to aws ecs"
12
+ spec.description = "auto scaler of sidekiq worker deploymented to aws ecs"
13
+ spec.homepage = "https://github.com/shoma07/sidekiq-ecs-scaler"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = ">= 2.6.0"
16
+
17
+ spec.metadata["homepage_uri"] = spec.homepage
18
+ spec.metadata["source_code_uri"] = spec.homepage
19
+ spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/v#{spec.version}/CHANGELOG.md"
20
+
21
+ # Specify which files should be added to the gem when it is released.
22
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
23
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
24
+ `git ls-files -z`.split("\x0").reject do |f|
25
+ (f == __FILE__) || f.match(%r{\A(?:(?:test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
26
+ end
27
+ end
28
+ spec.bindir = "exe"
29
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
30
+ spec.require_paths = ["lib"]
31
+
32
+ spec.add_dependency "aws-sdk-ecs", "> 1", "< 2"
33
+ spec.add_dependency "sidekiq", "> 5", "< 7"
34
+
35
+ # For more information and examples about making a new gem, checkout our
36
+ # guide at: https://bundler.io/guides/creating_gem.html
37
+ spec.metadata = {
38
+ "rubygems_mfa_required" => "true"
39
+ }
40
+ end
@@ -0,0 +1,82 @@
1
+ module SidekiqEcsScaler
2
+ VERSION: ::String
3
+
4
+ class Error < StandardError
5
+ end
6
+
7
+ def self.config: () -> ::SidekiqEcsScaler::Configuration
8
+
9
+ def self.configure: () { (::SidekiqEcsScaler::Configuration) -> void } -> void
10
+
11
+ def self.client: () -> ::SidekiqEcsScaler::Client
12
+
13
+ class Client
14
+ def initialize: (::SidekiqEcsScaler::Configuration) -> void
15
+
16
+ def update_desired_count: () -> ::Integer?
17
+
18
+ private
19
+
20
+ attr_reader config: ::SidekiqEcsScaler::Configuration
21
+
22
+ def queue_latency: () -> ::Float
23
+
24
+ def desired_count_by_latency: () -> ::Integer
25
+
26
+ def service_name: () -> ::String
27
+
28
+ def describe_service: () -> ::Aws::ECS::Types::Service
29
+
30
+ def update_service: (service: ::Aws::ECS::Types::Service, desired_count: ::Integer) -> ::Aws::ECS::Types::UpdateServiceResponse
31
+ end
32
+
33
+ class Configuration
34
+ attr_accessor queue_name: ::String
35
+
36
+ attr_reader min_count: ::Integer
37
+
38
+ attr_reader max_count: ::Integer
39
+
40
+ attr_reader max_latency: ::Integer
41
+
42
+ attr_reader task_meta: ::SidekiqEcsScaler::TaskMetaV4?
43
+
44
+ attr_reader ecs_client: ::Aws::ECS::Client
45
+
46
+ def initialize: () -> void
47
+
48
+ def min_count=: (::Integer) -> void
49
+
50
+ def max_count=: (::Integer) -> void
51
+
52
+ def max_latency=: (::Integer) -> void
53
+
54
+ def ecs_client=: (::Aws::ECS::Client) -> void
55
+
56
+ def latency_per_count: () -> ::Integer
57
+
58
+ def task_meta!: () -> ::SidekiqEcsScaler::TaskMetaV4
59
+
60
+ def sidekiq_options: () -> ::Hash[untyped, untyped]
61
+
62
+ def sidekiq_options=: (::Hash[untyped, untyped]) -> void
63
+ end
64
+
65
+ class TaskMetaV4
66
+ def self.build_or_null: () -> ::SidekiqEcsScaler::TaskMetaV4?
67
+
68
+ attr_reader cluster: ::String
69
+
70
+ attr_reader task_arn: ::String
71
+
72
+ def initialize: (::Hash[untyped, untyped]) -> void
73
+ end
74
+
75
+ class Worker
76
+ include ::Sidekiq::Worker
77
+
78
+ def self.sidekiq_options: (?::Hash[untyped, untyped]) -> ::Hash[untyped, untyped]
79
+
80
+ def perform: () -> ::Integer?
81
+ end
82
+ end
@@ -0,0 +1,36 @@
1
+ module Aws
2
+ module ECS
3
+ class Client
4
+ def describe_tasks: ({ cluster: ::String, tasks: ::Array[::String] }) -> ::Aws::ECS::Types::DescribeTasksResponse
5
+
6
+ def describe_services: ({ cluster: ::String, services: ::Array[::String] }) -> ::Aws::ECS::Types::DescribeServicesResponse
7
+
8
+ def update_service: ({ cluster: ::String, service: ::String, desired_count: ::Integer }) -> ::Aws::ECS::Types::UpdateServiceResponse
9
+ end
10
+
11
+ module Types
12
+ class DescribeServicesResponse
13
+ def services: () -> ::Array[::Aws::ECS::Types::Service]
14
+ end
15
+
16
+ class DescribeTasksResponse
17
+ def tasks: () -> ::Array[::Aws::ECS::Types::Task]
18
+ end
19
+
20
+ class Service
21
+ def cluster_arn: () -> ::String
22
+
23
+ def service_name: () -> ::String
24
+
25
+ def desired_count: () -> ::Integer
26
+ end
27
+
28
+ class Task
29
+ def group: () -> ::String
30
+ end
31
+
32
+ class UpdateServiceResponse
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,5 @@
1
+ module Net
2
+ class HTTP
3
+ def self.get: (::URI::HTTP) -> ::String
4
+ end
5
+ end
@@ -0,0 +1,11 @@
1
+ module Sidekiq
2
+ module Worker
3
+ def self.sidekiq_options: (?::Hash[untyped, untyped]) -> ::Hash[untyped, untyped]
4
+ end
5
+
6
+ class Queue
7
+ def initialize: (::String) -> void
8
+
9
+ def latency: () -> ::Float
10
+ end
11
+ end
@@ -0,0 +1,6 @@
1
+ module ::URI
2
+ def self.parse: (String) -> ::URI::HTTP
3
+
4
+ class ::URI::HTTP
5
+ end
6
+ end
metadata ADDED
@@ -0,0 +1,112 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sidekiq-ecs-scaler
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - shoma07
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2021-11-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: aws-sdk-ecs
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">"
18
+ - !ruby/object:Gem::Version
19
+ version: '1'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '2'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">"
28
+ - !ruby/object:Gem::Version
29
+ version: '1'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '2'
33
+ - !ruby/object:Gem::Dependency
34
+ name: sidekiq
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">"
38
+ - !ruby/object:Gem::Version
39
+ version: '5'
40
+ - - "<"
41
+ - !ruby/object:Gem::Version
42
+ version: '7'
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">"
48
+ - !ruby/object:Gem::Version
49
+ version: '5'
50
+ - - "<"
51
+ - !ruby/object:Gem::Version
52
+ version: '7'
53
+ description: auto scaler of sidekiq worker deploymented to aws ecs
54
+ email:
55
+ - 23730734+shoma07@users.noreply.github.com
56
+ executables:
57
+ - sidekiq-ecs-scaler
58
+ extensions: []
59
+ extra_rdoc_files: []
60
+ files:
61
+ - ".rspec"
62
+ - ".rubocop.yml"
63
+ - ".yardopts"
64
+ - CHANGELOG.md
65
+ - Dockerfile
66
+ - Gemfile
67
+ - Gemfile.lock
68
+ - LICENSE.txt
69
+ - README.md
70
+ - Rakefile
71
+ - Steepfile
72
+ - bin/console
73
+ - bin/setup
74
+ - docker-compose.yml
75
+ - exe/sidekiq-ecs-scaler
76
+ - lib/sidekiq-ecs-scaler.rb
77
+ - lib/sidekiq-ecs-scaler/client.rb
78
+ - lib/sidekiq-ecs-scaler/configuration.rb
79
+ - lib/sidekiq-ecs-scaler/task_meta_v4.rb
80
+ - lib/sidekiq-ecs-scaler/version.rb
81
+ - lib/sidekiq-ecs-scaler/worker.rb
82
+ - sidekiq-ecs-scaler.gemspec
83
+ - sig-private/aws-sdk-ecs.rbs
84
+ - sig-private/net/http.rbs
85
+ - sig-private/sidekiq.rbs
86
+ - sig-private/uri.rbs
87
+ - sig/sidekiq-ecs-scaler.rbs
88
+ homepage: https://github.com/shoma07/sidekiq-ecs-scaler
89
+ licenses:
90
+ - MIT
91
+ metadata:
92
+ rubygems_mfa_required: 'true'
93
+ post_install_message:
94
+ rdoc_options: []
95
+ require_paths:
96
+ - lib
97
+ required_ruby_version: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: 2.6.0
102
+ required_rubygems_version: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: '0'
107
+ requirements: []
108
+ rubygems_version: 3.2.32
109
+ signing_key:
110
+ specification_version: 4
111
+ summary: auto scaler of sidekiq worker deploymented to aws ecs
112
+ test_files: []