http_health_check 0.2.1 → 0.3.0

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: dd248ef72a5036149bbeebe0dbbf989691b929f8c9ad49b51505ad01086931f2
4
- data.tar.gz: 566d608465c448b64358d49c89ee3328a586541afc30dedaabd735d8b9f3f846
3
+ metadata.gz: 575cd5eaa80f9a89c3610213e7722f49d8dd05c09a239000129adc3e9eda6977
4
+ data.tar.gz: 97867356d935197b51be6ac31f4a6f9bf5b523dea9af82b4fa1915363d0f0dbc
5
5
  SHA512:
6
- metadata.gz: ac50f1370a1c85b97f1dd420816a008cd560def8e076545ffa2258552ceeb47b2bea22252751ea3db09fc4716aea1b7eb0dadc876d42be43a5d7ac024b140d0e
7
- data.tar.gz: f804bf50fc6a96cab3f8f568008fc6c89a8aee84ad4a6ae8e6258589f474ef290664658515867793cc95dbd7abc768b1b5eacde24c375a0ef89e3ac9259d7837
6
+ metadata.gz: 248742f7916d9a854895b45f761dac4cd6a8f7a47c6419afe844b68ce79e8488cb8ae4beb2f62a711c9b76d723981f1bc33bab46dcecf4e775aa73fa96f33445
7
+ data.tar.gz: c5dac68feccacd62728cc5252ac7c8d3dad9ba65cbb064f37e9db4cbce67047a76652d4c479fdeb476e2cedc1230f74fef05345f04c9ad534c9e46c9bba8816c
data/.bumpversion.cfg CHANGED
@@ -1,5 +1,5 @@
1
1
  [bumpversion]
2
- current_version = 0.2.1
2
+ current_version = 0.3.0
3
3
  commit = True
4
4
  tag = True
5
5
  tag_name = {new_version}
@@ -8,3 +8,7 @@ message = bump version {current_version} → {new_version}
8
8
  [bumpversion:file:lib/http_health_check/version.rb]
9
9
 
10
10
  [bumpversion:file:README.md]
11
+
12
+ [bumpversion:file:Gemfile.lock]
13
+ search = http_health_check ({current_version})
14
+ replace = http_health_check ({new_version})
@@ -0,0 +1,41 @@
1
+ name: ci
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ build:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ ruby: ["2.5", "2.6", "2.7", "3.0"]
15
+ services:
16
+ redis:
17
+ image: bitnami/redis:6.2
18
+ ports:
19
+ - 6379:6379
20
+ env:
21
+ REDIS_PASSWORD: supersecret
22
+ options: >-
23
+ --health-cmd "redis-cli -p 6379 -a 'supersecret' ping"
24
+ --health-interval 1s
25
+ --health-timeout 3s
26
+ --health-retries 10
27
+ steps:
28
+ - uses: actions/checkout@v3
29
+ - name: Set up Ruby
30
+ uses: ruby/setup-ruby@v1
31
+ with:
32
+ bundler-cache: true
33
+ ruby-version: ${{ matrix.ruby }}
34
+ - name: Run all tests
35
+ run: bundle exec rspec
36
+ env:
37
+ REDIS_URL: redis://:supersecret@redis:${{ job.services.redis.ports[6379] }}/0
38
+ # FIXME!
39
+ SKIP_REDIS_SPECS: true
40
+ - name: Run rubocop
41
+ run: bundle exec rubocop
data/.gitignore CHANGED
@@ -7,3 +7,4 @@
7
7
  /spec/reports/
8
8
  /tmp/
9
9
  /.idea
10
+ *.gem
data/.rubocop.yml CHANGED
@@ -6,4 +6,8 @@ Style/Documentation:
6
6
 
7
7
  Metrics/BlockLength:
8
8
  Exclude:
9
- - 'spec/**/*.rb'
9
+ - "spec/**/*.rb"
10
+ - "http_health_check.gemspec"
11
+
12
+ Style/CommentedKeyword:
13
+ Enabled: false
data/CHANGELOG.md CHANGED
@@ -1,12 +1,16 @@
1
1
  # CHANGELOG.md
2
2
 
3
- ## 0.1.1 (2022-07-17)
3
+ ## 0.3.0 (2022-07-19)
4
4
 
5
5
  Features:
6
6
 
7
- - implement basic functionality
8
- - add builtin sidekiq probe
9
- - add builtin delayed job probe
7
+ - add ruby-kafka probe
8
+
9
+ ## 0.2.1 (2022-07-18)
10
+
11
+ Fix:
12
+
13
+ - fix gemspec
10
14
 
11
15
  ## 0.2.0 (2022-07-18)
12
16
 
@@ -18,8 +22,10 @@ Fix:
18
22
 
19
23
  - fix builtin probes requirement
20
24
 
21
- ## 0.2.1 (2022-07-18)
25
+ ## 0.1.1 (2022-07-17)
22
26
 
23
- Fix:
27
+ Features:
24
28
 
25
- - fix gemspec
29
+ - implement basic functionality
30
+ - add builtin sidekiq probe
31
+ - add builtin delayed job probe
data/Gemfile.lock CHANGED
@@ -1,16 +1,26 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- http_health_check (0.2.0)
4
+ http_health_check (0.3.0)
5
5
  rack (~> 2.0)
6
+ webrick
6
7
 
7
8
  GEM
8
9
  remote: https://rubygems.org/
9
10
  specs:
11
+ activesupport (5.2.8.1)
12
+ concurrent-ruby (~> 1.0, >= 1.0.2)
13
+ i18n (>= 0.7, < 2)
14
+ minitest (~> 5.1)
15
+ tzinfo (~> 1.1)
10
16
  ast (2.4.2)
17
+ concurrent-ruby (1.1.10)
11
18
  diff-lcs (1.5.0)
12
19
  docile (1.4.0)
13
20
  dotenv (2.7.6)
21
+ i18n (1.12.0)
22
+ concurrent-ruby (~> 1.0)
23
+ minitest (5.15.0)
14
24
  parallel (1.22.1)
15
25
  parser (3.1.2.0)
16
26
  ast (~> 2.4.1)
@@ -56,12 +66,19 @@ GEM
56
66
  simplecov (~> 0.19)
57
67
  simplecov-html (0.12.3)
58
68
  simplecov_json_formatter (0.1.4)
69
+ thread_safe (0.3.6)
70
+ tzinfo (1.2.9)
71
+ thread_safe (~> 0.1)
59
72
  unicode-display_width (1.8.0)
73
+ webrick (1.7.0)
60
74
 
61
75
  PLATFORMS
76
+ ruby
62
77
  x86_64-darwin-21
78
+ x86_64-linux
63
79
 
64
80
  DEPENDENCIES
81
+ activesupport (~> 5.0)
65
82
  dotenv (~> 2.7.6)
66
83
  http_health_check!
67
84
  rake (~> 13.0)
data/README.md CHANGED
@@ -2,12 +2,16 @@
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/http_health_check.svg)](https://badge.fury.io/rb/http_health_check)
4
4
 
5
+ HttpHealthCheck is a tiny framework for building health check for your application components. It provides a set of built-in checkers (a.k.a. probes) and utilities for building your own.
6
+
7
+ HttpHealthCheck is built with kubernetes health probes in mind, but it can be used with http health checker.
8
+
5
9
  ## Installation
6
10
 
7
11
  Add this line to your application's Gemfile:
8
12
 
9
13
  ```ruby
10
- gem 'http_health_check', '~> 0.2.1'
14
+ gem 'http_health_check', '~> 0.3.0'
11
15
  ```
12
16
 
13
17
  And then execute:
@@ -45,6 +49,91 @@ module Delayed::AfterFork
45
49
  end
46
50
  ```
47
51
 
52
+ ### Karafka ~> 1.4
53
+
54
+ Ruby-kafka probe is disabled by default as it requires app-specific configuration to work properly. Example usage with karafka framework:
55
+
56
+ ```ruby
57
+ # ./karafka.rb
58
+
59
+ class KarafkaApp < Karafka::App
60
+ # ...
61
+ # karafka app configuration
62
+ # ...
63
+ end
64
+
65
+ KarafkaApp.boot!
66
+
67
+ HttpHealthCheck.run_server_async(
68
+ port: 5555,
69
+ rack_app: HttpHealthCheck::RackApp.configure do |c|
70
+ c.probe '/readiness/karafka', HttpHealthCheck::Probes::RubyKafka.new(
71
+ consumer_groups: KarafkaApp.consumer_groups.map(&:id),
72
+ # default heartbeat interval is 3 seconds, but we want to give it
73
+ # an ability to skip a few before failing the probe
74
+ heartbeat_interval_sec: 10,
75
+ # includes a list of topics and partitions into response for every consumer thread. false by default
76
+ verbose: false
77
+ )
78
+ end
79
+ )
80
+ ```
81
+
82
+ Ruby kafka probe supports multi-threaded setups, i.e. if you are using karafka and you define multiple blocks with the same consumer group like
83
+
84
+ ```ruby
85
+ class KarafkaApp < Karafka::App
86
+ consumer_groups.draw do
87
+ consumer_group 'foo' do
88
+ # ...
89
+ end
90
+ end
91
+
92
+ consumer_groups.draw do
93
+ consumer_group 'foo' do
94
+ # ...
95
+ end
96
+ end
97
+ end
98
+
99
+ KarafkaApp.consumer_groups.map(&:id)
100
+ # => ['foo', 'foo']
101
+ ```
102
+
103
+ ruby-kafka probe will count heartbeats from multiple threads.
104
+
105
+ ### Kubernetes deployment example
106
+
107
+ ```yaml
108
+ apiVersion: apps/v1
109
+ kind: Deployment
110
+ metadata:
111
+ name: sidekiq
112
+ spec:
113
+ replicas: 1
114
+ selector:
115
+ matchLabels:
116
+ app: sidekiq
117
+ template:
118
+ metadata:
119
+ labels:
120
+ app: sidekiq
121
+ spec:
122
+ containers:
123
+ - name: sidekiq
124
+ image: my-app:latest
125
+ livenessProbe:
126
+ httpGet:
127
+ path: /liveness
128
+ port: 5555
129
+ scheme: HTTP
130
+ readinessProbe:
131
+ httpGet:
132
+ path: /readiness/sidekiq
133
+ port: 5555
134
+ scheme: HTTP
135
+ ```
136
+
48
137
  ### Changing global configuration
49
138
 
50
139
  ```ruby
@@ -57,7 +146,7 @@ HttpHealthCheck.configure do |c|
57
146
  [200, {}, ['OK']]
58
147
  end
59
148
 
60
- # optionally add builtin probes
149
+ # optionally add built-in probes
61
150
  HttpHealthCheck.add_builtin_probes(c)
62
151
 
63
152
  # optionally override fallback (route not found) handler
@@ -83,7 +172,7 @@ HttpHealthCheck.run_server_async(port: 5555, rack_app: rack_app)
83
172
 
84
173
  Probes are built around [HttpHealthCheck::Probe](./lib/http_health_check/probe.rb) mixin. Every probe defines **probe** method which receives [rack env](https://www.rubydoc.info/gems/rack/Rack/Request/Env)
85
174
  and should return [HttpHealthCheck::Probe::Result](./lib/http_health_check/probe/result.rb) or rack-compatible response (status-headers-body tuple).
86
- Probe-mixin provides convenience methods `probe_ok` and `probe_error` for creating [HttpHealthCheck::Probe::Result](./lib/http_health_check/probe/result.rb) instance. Both of them accept optional metadata hash that will be added to response body.
175
+ Probe-mixin provides convenience methods `probe_ok` and `probe_error` for creating [HttpHealthCheck::Probe::Result](./lib/http_health_check/probe/result.rb) instance. Both of them accept optional metadata hash that will be added to the response body.
87
176
  Any exception (StandardError) will be captured and converted into error-result.
88
177
 
89
178
  ```ruby
@@ -105,12 +194,12 @@ HttpHealthCheck.configure do |config|
105
194
  end
106
195
  ```
107
196
 
108
- ### Builtin probes
197
+ ### Built-in probes
109
198
 
110
199
  #### [Sidekiq](./lib/http_health_check/probes/sidekiq.rb)
111
200
 
112
201
  Sidekiq probe ensures that sidekiq is ready by checking redis is available and writable. It uses sidekiq's redis connection pool to avoid spinning up extra connections.
113
- Be aware, that this approach does not cover issues with sidekiq being stuck processing slow/endless jobs. Such cases are nearly impossible to cover without false-positive alerts.
202
+ Be aware that this approach does not cover issues with sidekiq being stuck processing slow/endless jobs. Such cases are nearly impossible to cover without false-positive alerts.
114
203
 
115
204
  ```ruby
116
205
  HttpHealthCheck.configure do |config|
@@ -121,8 +210,8 @@ end
121
210
  #### [DelayedJob](./lib/http_health_check/probes/delayed_job.rb) (active record)
122
211
 
123
212
  Delayed Job probe is intended to work with [active record backend](https://github.com/collectiveidea/delayed_job_active_record).
124
- It checks DelayedJob is healthy by enqueuing an empty job which will be deleted right after insertion. This allows us to be sure that underlying database is connectable and writable.
125
- Be aware, that by enqueuing a new job with every health check we are incrementing primary key sequence.
213
+ It checks DelayedJob is healthy by enqueuing an empty job which will be deleted right after insertion. This allows us to be sure that the underlying database is connectable and writable.
214
+ Be aware that by enqueuing a new job with every health check, we are incrementing the primary key sequence.
126
215
 
127
216
  ```ruby
128
217
  HttpHealthCheck.configure do |config|
@@ -130,6 +219,20 @@ HttpHealthCheck.configure do |config|
130
219
  end
131
220
  ```
132
221
 
222
+ #### [ruby-kafka](./lib/http_health_check/probes/ruby_kafka.rb)
223
+
224
+ ruby-kafka probe is expected to be configured with consumer groups list. It subscribes to ruby-kafka's `heartbeat.consumer.kafka` ActiveSupport notification and tracks heartbeats for every given consumer group.
225
+ It expects a heartbeat every `:heartbeat_interval_sec` (10 seconds by default).
226
+
227
+ ```ruby
228
+ heartbeat_app = HttpHealthCheck::RackApp.configure do |c|
229
+ c.probe '/readiness/kafka', HttpHealthCheck::Probes::Karafka.new(
230
+ consumer_groups: ['consumer-one', 'consumer-two'],
231
+ heartbeat_interval_sec: 42
232
+ )
233
+ end
234
+ ```
235
+
133
236
  ## Development
134
237
 
135
238
  After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -141,8 +244,6 @@ docker-compose up redis
141
244
 
142
245
  ## Deployment
143
246
 
144
- Every new (git) tag will be built and deployed automatically via gitlab CI pipeline. We recommend using [bump2version](https://github.com/c4urself/bump2version) to tag new releases.
145
-
146
247
  1. Update changelog and git add it
147
248
  2.
148
249
 
@@ -151,3 +252,5 @@ bump2version patch --allow-dirty
151
252
  ```
152
253
 
153
254
  3. git push && git push --tags
255
+ 4. gem build
256
+ 5. gem push http_health_check-x.x.x.gem
data/docker-compose.yml CHANGED
@@ -1,7 +1,7 @@
1
1
  version: '3.5'
2
2
  services:
3
3
  redis:
4
- image: dreg.sbmt.io/dhub/bitnami/redis:6.2
4
+ image: bitnami/redis:6.2
5
5
  environment:
6
6
  REDIS_PASSWORD: supersecret
7
7
  volumes:
@@ -28,7 +28,9 @@ Gem::Specification.new do |spec|
28
28
  spec.require_paths = ['lib']
29
29
 
30
30
  spec.add_dependency 'rack', '~> 2.0'
31
+ spec.add_dependency 'webrick'
31
32
 
33
+ spec.add_development_dependency 'activesupport', '~> 5.0'
32
34
  spec.add_development_dependency 'dotenv', '~> 2.7.6'
33
35
  spec.add_development_dependency 'redis', '~> 4.2.5'
34
36
  spec.add_development_dependency 'rspec', '~> 3.2'
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HttpHealthCheck
4
+ module Probes
5
+ class RubyKafka
6
+ Heartbeat = Struct.new(:time, :group, :topic_partitions)
7
+ include ::HttpHealthCheck::Probe
8
+
9
+ def initialize(opts = {})
10
+ @heartbeat_event_name = opts.fetch(:heartbeat_event_name, /heartbeat.consumer.kafka/)
11
+ @heartbeat_interval_sec = opts.fetch(:heartbeat_interval_sec, 10)
12
+ @verbose = opts.fetch(:verbose, false)
13
+ @consumer_groups = opts.fetch(:consumer_groups)
14
+ .each_with_object(Hash.new(0)) { |group, hash| hash[group] += 1 }
15
+ @heartbeats = {}
16
+ @timer = opts.fetch(:timer, Time)
17
+
18
+ setup_subscriptions
19
+ end
20
+
21
+ def probe(_env)
22
+ now = @timer.now
23
+ failed_heartbeats = select_failed_heartbeats(now)
24
+ return probe_ok groups: meta_from_heartbeats(@heartbeats, now) if failed_heartbeats.empty?
25
+
26
+ probe_error failed_groups: meta_from_heartbeats(failed_heartbeats, now)
27
+ end
28
+
29
+ private
30
+
31
+ def select_failed_heartbeats(now)
32
+ @consumer_groups.each_with_object({}) do |(group, concurrency), hash|
33
+ heartbeats = @heartbeats[group] || {}
34
+ ok_heartbeats_count = heartbeats.count { |_id, hb| hb.time + @heartbeat_interval_sec >= now }
35
+ hash[group] = heartbeats if ok_heartbeats_count < concurrency
36
+ end
37
+ end
38
+
39
+ def meta_from_heartbeats(heartbeats_hash, now) # rubocop: disable Metrics/MethodLength, Metrics/AbcSize
40
+ heartbeats_hash.each_with_object({}) do |(group, heartbeats), hash|
41
+ concurrency = @consumer_groups[group]
42
+ if heartbeats.empty?
43
+ hash[group] = { had_heartbeat: false, concurrency: concurrency }
44
+ next
45
+ end
46
+
47
+ hash[group] = { had_heartbeat: true, concurrency: concurrency, threads: {} }
48
+ heartbeats.each do |thread_id, heartbeat|
49
+ thread_meta = { seconds_since_last_heartbeat: now - heartbeat.time }
50
+ thread_meta[:topic_partitions] = heartbeat.topic_partitions if @verbose
51
+ hash[group][:threads][thread_id] = thread_meta
52
+ end
53
+ end
54
+ end
55
+
56
+ def setup_subscriptions
57
+ ActiveSupport::Notifications.subscribe(@heartbeat_event_name) do |*args|
58
+ event = ActiveSupport::Notifications::Event.new(*args)
59
+ group = event.payload[:group_id]
60
+
61
+ @heartbeats[group] ||= {}
62
+ @heartbeats[group][event.transaction_id] = Heartbeat.new(event.time, group, event.payload[:topic_partitions])
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative 'probes/sidekiq' if defined?(::Sidekiq)
4
4
  require_relative 'probes/delayed_job' if defined?(::Delayed::Job)
5
+ require_relative 'probes/ruby_kafka' if defined?(::Kafka::Consumer)
5
6
 
6
7
  module HttpHealthCheck
7
8
  module Probes; end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HttpHealthCheck
4
- VERSION = '0.2.1'
4
+ VERSION = '0.3.0'
5
5
  end
@@ -36,7 +36,7 @@ module HttpHealthCheck
36
36
  end
37
37
 
38
38
  def self.run_server_async(opts)
39
- Thread.new { run_server(opts) }
39
+ Thread.new { run_server(**opts) }
40
40
  end
41
41
 
42
42
  def self.run_server(port:, host: '0.0.0.0', rack_app: nil)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: http_health_check
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - SberMarket team
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-07-18 00:00:00.000000000 Z
11
+ date: 2022-07-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -24,6 +24,34 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: webrick
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: activesupport
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '5.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '5.0'
27
55
  - !ruby/object:Gem::Dependency
28
56
  name: dotenv
29
57
  requirement: !ruby/object:Gem::Requirement
@@ -89,6 +117,7 @@ extra_rdoc_files: []
89
117
  files:
90
118
  - ".bumpversion.cfg"
91
119
  - ".env"
120
+ - ".github/workflows/ci.yml"
92
121
  - ".gitignore"
93
122
  - ".rspec"
94
123
  - ".rubocop.yml"
@@ -109,6 +138,7 @@ files:
109
138
  - lib/http_health_check/probe/result.rb
110
139
  - lib/http_health_check/probes.rb
111
140
  - lib/http_health_check/probes/delayed_job.rb
141
+ - lib/http_health_check/probes/ruby_kafka.rb
112
142
  - lib/http_health_check/probes/sidekiq.rb
113
143
  - lib/http_health_check/rack_app.rb
114
144
  - lib/http_health_check/version.rb