http_health_check 0.2.0 → 0.3.1

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: 15d5d70900a1cdd00cc1121e6304e7660660ed6b1cb79295eb07865eaff51a9e
4
- data.tar.gz: 6d275eab1b0c357c8883648ad3e04c59d0ce2298f0f351d3f9985533372cc336
3
+ metadata.gz: f139d8fa2b538a20c22d2cf53a84d16a7872cd7f60173c4d78b4142350ae68ee
4
+ data.tar.gz: 27d1a8172548424d135124278662ff5558488245adca6361ca1f7c2c53382872
5
5
  SHA512:
6
- metadata.gz: 7e7a7e0510233606c94af8990e76262eafbe122a9130a0908ad9ee23679f5f2852d877acdaef51b1845d858a1bec4057f3ca268d676d243d3ca5042330b4c917
7
- data.tar.gz: 9ef706f14f7afcefafa45f4278eb603b3c88bf01d238ccbe1b0d1f865df1ea78049479ee6bca2693ce030dc3776b0b46fd283b5ac24d6ebeb42789d12226761f
6
+ metadata.gz: 4b8c69c5d3144a65471c2862c41874773d9ecec184d6ae882605f42411c9c114924bb4c33866f8cf90f168b0d5f6d6492e2c9338a58bf0a370de276c5d11272e
7
+ data.tar.gz: eebeab8973c4b956e49f0cc28cb17dc54de82b0f9569cd81508c52e7be5db3c1b3735a23ab1c68b68549ca6ede8cf208ca1cd12c65ddacc476794eb2ccfa5060
data/.bumpversion.cfg CHANGED
@@ -1,5 +1,5 @@
1
1
  [bumpversion]
2
- current_version = 0.2.0
2
+ current_version = 0.3.1
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
 
@@ -17,3 +21,11 @@ Features:
17
21
  Fix:
18
22
 
19
23
  - fix builtin probes requirement
24
+
25
+ ## 0.1.1 (2022-07-17)
26
+
27
+ Features:
28
+
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.1)
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
@@ -1,11 +1,17 @@
1
1
  # HttpHealthCheck
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/http_health_check.svg)](https://badge.fury.io/rb/http_health_check)
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
+
3
9
  ## Installation
4
10
 
5
11
  Add this line to your application's Gemfile:
6
12
 
7
13
  ```ruby
8
- gem 'http_health_check', '~> 0.2.0'
14
+ gem 'http_health_check', '~> 0.3.1'
9
15
  ```
10
16
 
11
17
  And then execute:
@@ -43,6 +49,91 @@ module Delayed::AfterFork
43
49
  end
44
50
  ```
45
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
+
46
137
  ### Changing global configuration
47
138
 
48
139
  ```ruby
@@ -55,7 +146,7 @@ HttpHealthCheck.configure do |c|
55
146
  [200, {}, ['OK']]
56
147
  end
57
148
 
58
- # optionally add builtin probes
149
+ # optionally add built-in probes
59
150
  HttpHealthCheck.add_builtin_probes(c)
60
151
 
61
152
  # optionally override fallback (route not found) handler
@@ -81,7 +172,7 @@ HttpHealthCheck.run_server_async(port: 5555, rack_app: rack_app)
81
172
 
82
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)
83
174
  and should return [HttpHealthCheck::Probe::Result](./lib/http_health_check/probe/result.rb) or rack-compatible response (status-headers-body tuple).
84
- 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.
85
176
  Any exception (StandardError) will be captured and converted into error-result.
86
177
 
87
178
  ```ruby
@@ -103,12 +194,12 @@ HttpHealthCheck.configure do |config|
103
194
  end
104
195
  ```
105
196
 
106
- ### Builtin probes
197
+ ### Built-in probes
107
198
 
108
199
  #### [Sidekiq](./lib/http_health_check/probes/sidekiq.rb)
109
200
 
110
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.
111
- 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.
112
203
 
113
204
  ```ruby
114
205
  HttpHealthCheck.configure do |config|
@@ -119,8 +210,8 @@ end
119
210
  #### [DelayedJob](./lib/http_health_check/probes/delayed_job.rb) (active record)
120
211
 
121
212
  Delayed Job probe is intended to work with [active record backend](https://github.com/collectiveidea/delayed_job_active_record).
122
- 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.
123
- 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.
124
215
 
125
216
  ```ruby
126
217
  HttpHealthCheck.configure do |config|
@@ -128,6 +219,20 @@ HttpHealthCheck.configure do |config|
128
219
  end
129
220
  ```
130
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
+
131
236
  ## Development
132
237
 
133
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.
@@ -139,8 +244,6 @@ docker-compose up redis
139
244
 
140
245
  ## Deployment
141
246
 
142
- 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.
143
-
144
247
  1. Update changelog and git add it
145
248
  2.
146
249
 
@@ -149,3 +252,5 @@ bump2version patch --allow-dirty
149
252
  ```
150
253
 
151
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:
@@ -5,6 +5,7 @@ require_relative 'lib/http_health_check/version'
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = 'http_health_check'
7
7
  spec.version = HttpHealthCheck::VERSION
8
+ spec.licenses = ['MIT']
8
9
  spec.authors = ['SberMarket team']
9
10
  spec.email = ['pochi.73@gmail.com']
10
11
 
@@ -27,7 +28,9 @@ Gem::Specification.new do |spec|
27
28
  spec.require_paths = ['lib']
28
29
 
29
30
  spec.add_dependency 'rack', '~> 2.0'
31
+ spec.add_dependency 'webrick'
30
32
 
33
+ spec.add_development_dependency 'activesupport', '~> 5.0'
31
34
  spec.add_development_dependency 'dotenv', '~> 2.7.6'
32
35
  spec.add_development_dependency 'redis', '~> 4.2.5'
33
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.0'
4
+ VERSION = '0.3.1'
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.0
4
+ version: 0.3.1
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,11 +138,13 @@ 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
115
145
  homepage: https://github.com/SberMarket-Tech/http_health_check
116
- licenses: []
146
+ licenses:
147
+ - MIT
117
148
  metadata:
118
149
  allowed_push_host: https://rubygems.org/
119
150
  homepage_uri: https://github.com/SberMarket-Tech/http_health_check