kubernetes-health 3.5.0 → 3.7.2
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/README.md +26 -11
- data/Rakefile +1 -1
- data/config/initializers/enable_rack_on_sidekiq.rb +7 -0
- data/kubernetes-health.gemspec +1 -1
- data/lib/kubernetes/health/config.rb +9 -0
- data/lib/kubernetes/health/rack_on_migrate.rake +3 -2
- data/lib/kubernetes/health/rack_on_sidekiq.rb +56 -0
- data/lib/kubernetes/health/version.rb +1 -1
- data/lib/puma/kubernetes/app.rb +44 -24
- data/lib/puma/kubernetes/parser.rb +43 -18
- metadata +7 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dd3b71bfaba5d0455cb081dfd694554370f858167f3aeafa3e34deb66de9a7b0
|
4
|
+
data.tar.gz: 030dd33dda0725a7d711d808a5e776fd3cd467a610fd17d3ab47c20d9629d756
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
-
|
7
|
-
-
|
8
|
-
-
|
9
|
-
-
|
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
|
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.
|
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
|
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
data/kubernetes-health.gemspec
CHANGED
@@ -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', '~>
|
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
|
data/lib/puma/kubernetes/app.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
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
|
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
|
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 =
|
32
|
+
content = puma_status_json(extended_puma_stats)
|
32
33
|
else
|
33
|
-
|
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
|
-
|
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
|
-
|
54
|
-
|
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
|
59
|
-
|
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
|
7
|
-
|
8
|
-
|
6
|
+
def initialize(clustered: false)
|
7
|
+
register_default_metrics
|
8
|
+
register_clustered_metrics if clustered
|
9
9
|
end
|
10
10
|
|
11
|
-
def parse(
|
12
|
-
|
13
|
-
value.each { |s| parse(s, labels.merge(index: s[
|
14
|
-
parse(value, labels) if key ==
|
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
|
22
|
-
registry.gauge(:puma_booted_workers,
|
23
|
-
|
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
|
27
|
-
registry.gauge(:puma_backlog,
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
registry.gauge(:
|
32
|
-
|
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(
|
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.
|
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-
|
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:
|
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:
|
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
|
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
|