kubernetes-health 3.5.0 → 3.7.2

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: b35fdf1eae585364a30777a0f0c8d67210ab99d97c492c33ea7e9c5a71b7d01c
4
- data.tar.gz: a4b4f532380c9295e6077d4aba889abb9a17c36889a713d7e84d864b8f9c8e6d
3
+ metadata.gz: dd3b71bfaba5d0455cb081dfd694554370f858167f3aeafa3e34deb66de9a7b0
4
+ data.tar.gz: 030dd33dda0725a7d711d808a5e776fd3cd467a610fd17d3ab47c20d9629d756
5
5
  SHA512:
6
- metadata.gz: 67cd817d32945e1f17f0b9363c393a408df9aec7352af14567d52a7562eab4969719b637f3c7acf3870595951f33a292ba9855c863b70bc125705f2b9f813ef1
7
- data.tar.gz: ca42a6d7350435fb33c40521e4e2c72f2046af6fda5c634b3b4994960b8dea4af93d0015b037e80344559149ce329673dccdee6156baf9cabb98607f87519a6a
6
+ metadata.gz: ea3434326f57be5a3ebe8c40fce7865484e405862c8a81f93c49d79c0497e6d3723069de9ba2ca77f308e56f7791ff786134c458f5d8011e6cff611191cd6db8
7
+ data.tar.gz: bb146f7557012fb1b1bdd9ac4dcf7627f2a8385e8f86608e5905bae799f558647e66217356f73350a55b5094bdca37779617a6928f533a6ab837262b86db6c34
data/README.md CHANGED
@@ -1,21 +1,22 @@
1
1
  # Kubernetes::Health
2
-
3
- This gem allows kubernetes monitoring your app while it is running migrates and after it starts.
2
+ This gem open a HTTP port for monitoring your rails app while it is running Migrates, Sidekiq and Puma.
4
3
 
5
4
  # Features
6
- - add routes `/_readiness`, `/_liveness` on rails stack.
7
- - add routes `/_readiness`, `/_liveness` and `/_metrics` as a puma plugin.
8
- - metrics are prometheus compatible (code copied from `puma-metrics` gem) or json.
9
- - allow custom checks for `/_readiness` and `/_liveness`.
5
+ - Puma and Sidekiq metrics for autoscaling.
6
+ - Prometheus and JSON metrics (tested using https://github.com/zalando-incubator/kube-metrics-adapter and JSON format).
7
+ - add routes `/_readiness`, `/_liveness` on Rails Stack.
8
+ - add routes `/_readiness`, `/_liveness` and `/_metrics` as a puma plugin at another port to avoid problems when your app get busy. (code copied from `puma-metrics` gem).
10
9
  - add routes `/_readiness` and `/_liveness` while `rake db:migrate` runs. (optional)
11
- - add support to avoid parallel running of `rake db:migrate` while keep kubernetes waiting. (optional)
12
-
10
+ - add routes `/_readiness` and `/_liveness` while `sidekiq` runs. (optional)
11
+ - add support to avoid parallel running of `rake db:migrate` while keep kubernetes waiting (PostgreSQL required).
12
+ - allow custom checks for `/_readiness` and `/_liveness`.
13
+
13
14
  ## Installation
14
15
 
15
16
  Add this line to your application's Gemfile:
16
17
 
17
18
  ```ruby
18
- gem 'kubernetes-health', '~> 3.5'
19
+ gem 'kubernetes-health', '~> 3.6'
19
20
  ```
20
21
 
21
22
  ## Enabling puma plugin
@@ -48,6 +49,7 @@ In Kubernetes you need to configure your deployment `readinessProbe` and `livene
48
49
  ```
49
50
 
50
51
  Setting `failureThreshold` is import to avoid problems when app finish migrates and is starting the web process.
52
+
51
53
  ## Enabling monitoring while `rake db:migrate` runs
52
54
 
53
55
  Your Dockerfile's entry script needs to run migrates before start your web app.
@@ -60,9 +62,22 @@ or add in your `application.rb`.
60
62
  # default: false
61
63
  Kubernetes::Health::Config.enable_rack_on_migrate = true
62
64
  ```
65
+ The defined port at `config/puma.rb` will be used.
66
+
67
+ ## Enabling monitoring for `sidekiq`
68
+
69
+ Add `KUBERNETES_HEALTH_ENABLE_RACK_ON_SIDEKIQ=true` environment variable.
70
+
71
+ or add in your `application.rb`.
72
+
73
+ ```
74
+ # default: false
75
+ Kubernetes::Health::Config.enable_rack_on_sidekiq = true
76
+ ```
77
+ The defined port at `config/puma.rb` will be used.
63
78
 
64
- ### How `rake db:migrate` monitoring works
65
- It will run a RACK server for `/_readiness` and `/_liveness` routes while `rake db:migrate` is running.
79
+ ### How `rake db:migrate` and `sidekiq` monitoring works
80
+ It will run a RACK server for `/_readiness`, `/_liveness` and `/_metrics`.
66
81
 
67
82
  ## Avoiding migrations running in parallel and making kubernetes happy.
68
83
  Rails already avoid migrations running in parallel, but it raise exceptions. This gem will just wait for other migrations without exit.
data/Rakefile CHANGED
@@ -12,7 +12,7 @@ task :server do
12
12
  require 'puma'
13
13
  require 'puma/configuration'
14
14
  require 'puma/events'
15
- require 'puma/plugin/kubernetes.rb'
15
+ require 'puma/plugin/kubernetes'
16
16
 
17
17
  configuration = Puma::Configuration.new do |config|
18
18
  config.bind 'tcp://127.0.0.1:0'
@@ -0,0 +1,7 @@
1
+ require 'kubernetes/health/rack_on_sidekiq'
2
+
3
+ if Kubernetes::Health::Config.enable_rack_on_sidekiq && Sidekiq.options[:concurrency].positive?
4
+ Thread.new do
5
+ Rack::Handler.default.run Kubernetes::Health::RackOnSidekiq.new
6
+ end
7
+ end
@@ -31,6 +31,6 @@ Gem::Specification.new do |spec|
31
31
  spec.add_development_dependency "rspec"
32
32
  spec.add_dependency "rack"
33
33
  spec.add_dependency "rails"
34
- spec.add_runtime_dependency 'prometheus-client', '~> 0.9.0'
34
+ spec.add_runtime_dependency 'prometheus-client', '~> 1.0'
35
35
  spec.add_runtime_dependency 'puma'
36
36
  end
@@ -5,6 +5,7 @@ module Kubernetes
5
5
  @@ready_if = lambda { true }
6
6
  @@enable_lock_on_migrate = [true, 'true'].include? ENV['KUBERNETES_HEALTH_ENABLE_LOCK_ON_MIGRATE']
7
7
  @@enable_rack_on_migrate = [true, 'true'].include? ENV['KUBERNETES_HEALTH_ENABLE_RACK_ON_MIGRATE']
8
+ @@enable_rack_on_sidekiq = [true, 'true'].include? ENV['KUBERNETES_HEALTH_ENABLE_RACK_ON_SIDEKIQ']
8
9
  @@route_liveness = '/_liveness'
9
10
  @@route_readiness = '/_readiness'
10
11
  @@route_metrics = '/_metrics'
@@ -57,6 +58,14 @@ module Kubernetes
57
58
  @@enable_rack_on_migrate = value
58
59
  end
59
60
 
61
+ def self.enable_rack_on_sidekiq
62
+ @@enable_rack_on_sidekiq
63
+ end
64
+
65
+ def self.enable_rack_on_sidekiq=(value)
66
+ @@enable_rack_on_sidekiq = value
67
+ end
68
+
60
69
  def self.route_metrics
61
70
  @@route_metrics
62
71
  end
@@ -1,10 +1,11 @@
1
1
  require 'rack'
2
2
  require "kubernetes/health/rack_on_migrate"
3
+
3
4
  namespace :kubernetes_health do
4
5
  task :rack_on_migrate do
5
- Thread.new {
6
+ Thread.new do
6
7
  Rack::Handler.default.run Kubernetes::Health::RackOnMigrate.new
7
- }
8
+ end
8
9
  end
9
10
  end
10
11
  Rake::Task['db:migrate'].enhance(['kubernetes_health:rack_on_migrate'])
@@ -0,0 +1,56 @@
1
+ require 'rack'
2
+ require 'prometheus/client'
3
+ require 'prometheus/client/formats/text'
4
+
5
+ module Kubernetes
6
+ module Health
7
+ class RackOnSidekiq
8
+ def call(env)
9
+ req = ::Rack::Request.new(env)
10
+ content = ''
11
+ type = ::Kubernetes::Health::Config.response_format == 'json' ? { 'Content-Type' => 'application/json' } : { 'Content-Type' => 'text/plain' }
12
+ case req.path_info
13
+ when Kubernetes::Health::Config.route_metrics
14
+ http_code = 200
15
+
16
+ sidekiq_metrics = generate_sidekiq_metrics
17
+
18
+ if ::Kubernetes::Health::Config.response_format == 'json'
19
+ content = sidekiq_metrics.to_json
20
+ else
21
+ prometheus_registry = generate_prometheus_registry
22
+ prometheus_registry.get(:sidekiq_capacity).set({}, sidekiq_metrics[:sidekiq_capacity])
23
+ prometheus_registry.get(:sidekiq_busy).set({}, sidekiq_metrics[:sidekiq_busy])
24
+ prometheus_registry.get(:sidekiq_usage).set({}, sidekiq_metrics[:sidekiq_usage])
25
+ content = Prometheus::Client::Formats::Text.marshal(prometheus_registry)
26
+ end
27
+ else
28
+ http_code = 404
29
+ end
30
+ ::Kubernetes::Health::Config.request_log_callback.call(req, http_code, content)
31
+
32
+ [http_code, type, [content]]
33
+ end
34
+
35
+ def generate_prometheus_registry
36
+ prometheus_registry = Prometheus::Client.registry
37
+ prometheus_registry.gauge(:sidekiq_capacity, 'Sidekiq Threads Number', index: 0)
38
+ prometheus_registry.gauge(:sidekiq_busy, 'Sidekiq Busy Threads', index: 0)
39
+ prometheus_registry.gauge(:sidekiq_usage, 'Result of sidekiq_busy/sidekiq_capacity', index: 0)
40
+ prometheus_registry
41
+ end
42
+
43
+ def generate_sidekiq_metrics
44
+ sidekiq_info = Sidekiq::ProcessSet.new.to_a.filter { |p| p.identity == Sidekiq.options[:identity] }
45
+
46
+ stats = {
47
+ sidekiq_capacity: Sidekiq.options[:concurrency],
48
+ sidekiq_busy: sidekiq_info.size.zero? ? 0 : sidekiq_info[0]['busy']
49
+ }
50
+
51
+ stats[:sidekiq_usage] = (stats[:sidekiq_busy] / stats[:sidekiq_capacity].to_f).round(2)
52
+ stats
53
+ end
54
+ end
55
+ end
56
+ end
@@ -1,5 +1,5 @@
1
1
  module Kubernetes
2
2
  module Health
3
- VERSION = "3.5.0"
3
+ VERSION = '3.7.2'.freeze
4
4
  end
5
5
  end
@@ -1,4 +1,4 @@
1
- require "kubernetes/health/config"
1
+ require 'kubernetes/health/config'
2
2
  require 'prometheus/client/formats/text'
3
3
  require 'puma/kubernetes/parser'
4
4
  require 'rack'
@@ -9,7 +9,7 @@ module Puma
9
9
  def initialize(launcher)
10
10
  @launcher = launcher
11
11
  clustered = (@launcher.options[:workers] || 0) > 0
12
- @parser = Parser.new clustered
12
+ @parser = Parser.new(clustered: clustered)
13
13
  end
14
14
 
15
15
  def call(_env)
@@ -18,25 +18,26 @@ module Puma
18
18
  type = {}
19
19
  content = ''
20
20
  type = ::Kubernetes::Health::Config.response_format == 'json' ? { 'Content-Type' => 'application/json' } : { 'Content-Type' => 'text/plain' }
21
+ extended_puma_stats = generate_extended_puma_stats
21
22
  case req.path_info
22
23
  when ::Kubernetes::Health::Config.route_liveness
23
- i_am_live = ::Kubernetes::Health::Config.live_if.arity == 0 ? ::Kubernetes::Health::Config.live_if.call : ::Kubernetes::Health::Config.live_if.call(req.params)
24
- http_code = i_am_live ? 200 : 503
24
+ i_am_live = ::Kubernetes::Health::Config.live_if.arity.zero? ? ::Kubernetes::Health::Config.live_if.call : ::Kubernetes::Health::Config.live_if.call(req.params)
25
+ http_code = puma_already_started?(extended_puma_stats) && i_am_live ? 200 : 503
25
26
  when ::Kubernetes::Health::Config.route_readiness
26
- i_am_ready = ::Kubernetes::Health::Config.ready_if.arity == 0 ? ::Kubernetes::Health::Config.ready_if.call : ::Kubernetes::Health::Config.ready_if.call(req.params)
27
- http_code = i_am_ready ? 200 : 503
27
+ i_am_ready = ::Kubernetes::Health::Config.ready_if.arity.zero? ? ::Kubernetes::Health::Config.ready_if.call : ::Kubernetes::Health::Config.ready_if.call(req.params)
28
+ http_code = puma_already_started?(extended_puma_stats) && i_am_ready ? 200 : 503
28
29
  when ::Kubernetes::Health::Config.route_metrics
29
30
  http_code = 200
30
31
  if ::Kubernetes::Health::Config.response_format == 'json'
31
- content = include_puma_key_prefix(include_usage(merge_worker_status_if_needed(@launcher.stats))).to_json
32
+ content = puma_status_json(extended_puma_stats)
32
33
  else
33
- @parser.parse include_usage(merge_worker_status_if_needed(@launcher.stats))
34
+ prometheus_parse_status!(extended_puma_stats)
34
35
  content = Prometheus::Client::Formats::Text.marshal(Prometheus::Client.registry)
35
36
  end
36
37
  else
37
38
  http_code = 404
38
39
  end
39
- rescue => e
40
+ rescue StandardError => e
40
41
  puts e.message
41
42
  puts e.backtrace.join("\n")
42
43
  http_code = 500
@@ -46,33 +47,52 @@ module Puma
46
47
  [http_code, type, [content]]
47
48
  end
48
49
 
49
- def merge_worker_status_if_needed(stats)
50
- return stats unless stats[:worker_status]
50
+ private
51
51
 
52
+ def prometheus_parse_status!(extended_puma_stats)
53
+ @parser.parse(extended_puma_stats)
54
+ end
55
+
56
+ def generate_extended_puma_stats
57
+ puma_stats = @launcher.stats
58
+ # On puma <= 4 puma_stats is a String
59
+ puma_stats = JSON.parse(puma_stats, symbolize_names: true) unless puma_stats.is_a?(Hash)
60
+ # Including usage stats.
61
+ puma_stats = merge_worker_status(puma_stats) if puma_stats[:worker_status].present?
62
+ puma_stats[:usage] = (1 - puma_stats[:pool_capacity].to_f / puma_stats[:max_threads]).round(2) if puma_stats[:pool_capacity].present?
63
+ puma_stats
64
+ end
65
+
66
+ def merge_worker_status(stats)
52
67
  merded_stats = stats[:worker_status].map { |ws| ws[:last_status] }.inject({}) { |sum, hash| sum.merge(hash) { |_key, val1, val2| val1+val2 } }
53
- merded_stats[:puma_started_at] = stats[:puma_started_at]
54
- merded_stats[:worker_status] = stats[:worker_status]
68
+ stats.each_key do |k|
69
+ merded_stats[k] = stats[k]
70
+ end
71
+
55
72
  merded_stats
56
73
  end
57
74
 
58
- def include_usage(stats)
59
- if stats.is_a?(String)
60
- # puma <= 4.
61
- stats = JSON.parse(stats)
62
- else
63
- # Puma >=5 uses symbol.
64
- stats = JSON.parse(stats.to_json)
65
- end
66
- stats['usage'] = (1 - stats['pool_capacity'].to_f / stats['max_threads']).round(2)
67
- stats
75
+ def puma_status_json(extended_puma_stats)
76
+ include_puma_key_prefix(extended_puma_stats).to_json
68
77
  end
78
+
69
79
  def include_puma_key_prefix(stats)
70
80
  result = {}
71
- stats.each do |k,v|
81
+ stats.each do |k, v|
72
82
  result["puma_#{k}"] = v
73
83
  end
74
84
  result
75
85
  end
86
+
87
+ def puma_already_started?(extended_puma_stats)
88
+ if extended_puma_stats[:booted_workers].present?
89
+ # Cluster Mode
90
+ extended_puma_stats[:booted_workers].positive?
91
+ else
92
+ # Single Mode
93
+ extended_puma_stats[:running].positive?
94
+ end
95
+ end
76
96
  end
77
97
  end
78
98
  end
@@ -3,33 +3,58 @@ require 'prometheus/client'
3
3
  module Puma
4
4
  module Kubernetes
5
5
  class Parser
6
- def initialize(clustered = false)
7
- register_default_kubernetes
8
- register_clustered_kubernetes if clustered
6
+ def initialize(clustered: false)
7
+ register_default_metrics
8
+ register_clustered_metrics if clustered
9
9
  end
10
10
 
11
- def parse(stats, labels = {})
12
- stats.each do |key, value|
13
- value.each { |s| parse(s, labels.merge(index: s['index'])) } if key == 'worker_status'
14
- parse(value, labels) if key == 'last_status'
11
+ def parse(symbol_keyed_stats, labels = {})
12
+ symbol_keyed_stats.each do |key, value|
13
+ value.each { |s| parse(s, labels.merge(index: s[:index])) } if key == :worker_status
14
+ parse(value, labels) if key == :last_status
15
15
  update_metric(key, value, labels)
16
16
  end
17
17
  end
18
18
 
19
19
  private
20
20
 
21
- def register_clustered_kubernetes
22
- registry.gauge(:puma_booted_workers, 'Number of booted workers').set({}, 1)
23
- registry.gauge(:puma_old_workers, 'Number of old workers').set({}, 0)
21
+ def register_clustered_metrics
22
+ registry.gauge(:puma_booted_workers,
23
+ docstring: 'Number of booted workers')
24
+ .set(1)
25
+ registry.gauge(:puma_old_workers,
26
+ docstring: 'Number of old workers')
27
+ .set(0)
24
28
  end
25
29
 
26
- def register_default_kubernetes
27
- registry.gauge(:puma_backlog, 'Number of established but unaccepted connections in the backlog', index: 0)
28
- registry.gauge(:puma_running, 'Number of running worker threads', index: 0)
29
- registry.gauge(:puma_pool_capacity, 'Number of allocatable worker threads', index: 0)
30
- registry.gauge(:puma_max_threads, 'Maximum number of worker threads', index: 0)
31
- registry.gauge(:puma_workers, 'Number of configured workers').set({}, 1)
32
- registry.gauge(:puma_usage, 'Result of (1 - puma_pool_capacity/puma_max_threads)', index: 0)
30
+ def register_default_metrics # rubocop:disable Metrics/MethodLength
31
+ registry.gauge(:puma_backlog,
32
+ docstring: 'Number of established but unaccepted connections in the backlog',
33
+ labels: [:index],
34
+ preset_labels: { index: 0 })
35
+ registry.gauge(:puma_running,
36
+ docstring: 'Number of running worker threads',
37
+ labels: [:index],
38
+ preset_labels: { index: 0 })
39
+ registry.gauge(:puma_pool_capacity,
40
+ docstring: 'Number of allocatable worker threads',
41
+ labels: [:index],
42
+ preset_labels: { index: 0 })
43
+ registry.gauge(:puma_max_threads,
44
+ docstring: 'Maximum number of worker threads',
45
+ labels: [:index],
46
+ preset_labels: { index: 0 })
47
+ registry.gauge(:puma_requests_count,
48
+ docstring: 'Number of processed requests',
49
+ labels: [:index],
50
+ preset_labels: { index: 0 })
51
+ registry.gauge(:puma_usage,
52
+ docstring: 'Result of (1 - puma_pool_capacity/puma_max_threads)',
53
+ labels: [:index],
54
+ preset_labels: { index: 0 })
55
+ registry.gauge(:puma_workers,
56
+ docstring: 'Number of configured workers')
57
+ .set(1)
33
58
  end
34
59
 
35
60
  def registry
@@ -39,7 +64,7 @@ module Puma
39
64
  def update_metric(key, value, labels)
40
65
  return if registry.get("puma_#{key}").nil?
41
66
 
42
- registry.get("puma_#{key}").set(labels, value)
67
+ registry.get("puma_#{key}").set(value, labels: labels)
43
68
  end
44
69
  end
45
70
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kubernetes-health
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.5.0
4
+ version: 3.7.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Wagner Caixeta
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-01-08 00:00:00.000000000 Z
11
+ date: 2022-08-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -86,14 +86,14 @@ dependencies:
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: 0.9.0
89
+ version: '1.0'
90
90
  type: :runtime
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: 0.9.0
96
+ version: '1.0'
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: puma
99
99
  requirement: !ruby/object:Gem::Requirement
@@ -128,6 +128,7 @@ files:
128
128
  - app/controllers/kubernetes/health_controller.rb
129
129
  - bin/console
130
130
  - bin/setup
131
+ - config/initializers/enable_rack_on_sidekiq.rb
131
132
  - config/routes.rb
132
133
  - kubernetes-health.gemspec
133
134
  - lib/kubernetes/health.rb
@@ -136,6 +137,7 @@ files:
136
137
  - lib/kubernetes/health/lock_on_migrate.rake
137
138
  - lib/kubernetes/health/rack_on_migrate.rake
138
139
  - lib/kubernetes/health/rack_on_migrate.rb
140
+ - lib/kubernetes/health/rack_on_sidekiq.rb
139
141
  - lib/kubernetes/health/railtie.rb
140
142
  - lib/kubernetes/health/version.rb
141
143
  - lib/puma/kubernetes/app.rb
@@ -160,7 +162,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
160
162
  - !ruby/object:Gem::Version
161
163
  version: '0'
162
164
  requirements: []
163
- rubygems_version: 3.1.4
165
+ rubygems_version: 3.0.3.1
164
166
  signing_key:
165
167
  specification_version: 4
166
168
  summary: This gem allows kubernetes monitoring your app while it is running migrates