Rails で利用できる Health Check API 系の Gem である okcomputer について

## ここでいう Health Check API とは

[Pattern: Health Check API - Microservice Architecture](http://microservices.io/patterns/observability/health-check-api.html)

上記は一例ですが、Web アプリケーションが動作をしているかを確認できるエンドポイントを提供することを指しています。

> 他に呼び方があるかもしれませんが。

## Rails で利用できる Gem 

調査時に発見できたものです。拾うワードが少なかった印象はあります。他にもありそう。

:star: 数/last_update は 2017/6/30 時点

|Gem|description|:star:|last_update|
|:--|:--|:--|:--|
|[ianheggie/health_check](https://github.com/ianheggie/health_check)|Simple health check of Rails app for use with uptime checking sites like newrelic and pingdom|191|2016/12/7|
|[jbarnette/pinglish](https://github.com/jbarnette/pinglish)|A simple Rack middleware for checking application health.|154|2016/11/28|
|[sportngin/okcomputer](https://github.com/sportngin/okcomputer)|Provides a health-check endpoint to your Ruby on Rails apps.|137|2017/5/9|
|[lbeder/health-monitor-rails](https://github.com/lbeder/health-monitor-rails)|A Rails plugin which provides a health checking and monitoring API of various services and application aspects.|58|2017/6/21|
|[mitaku/komachi_heartbeat](https://github.com/mitaku/komachi_heartbeat)|Rails アプリケーションとDBサーバーの死活監視するためのURLを提供するEngine|39|2017/5/22|
|[lserman/aws-healthcheck](https://github.com/lserman/aws-healthcheck)|Mounts a 200 response at /healthcheck for AWS load balancers|14|2016/10/31|
|[on-site/server_health_check](https://github.com/on-site/server_health_check)<br>[on-site/server_health_check-rack](https://github.com/on-site/server_health_check-rack)<br>[on-site/server_health_check-rails](https://github.com/on-site/server_health_check-rails)|This gem provides a standard set of health checks for web services|0|2017/5/8|

Rack / Rails Mountable Engine などの違いはありつつも、いずれもあるエンドポイント（大抵は変更可能）に対してリクエストをした際に DB 等（大抵は Migration 管理用のテーブル）にアクセスして、動作確認をし、成功すれば 200 を返すという単純なものです。

それぞれで、ビルトインのチェック処理が異なるので、どれぐらいカスタマイズしたいかや、「面倒だからビルトインが多いほうがいい」等で決めれば良いかと思います。

## okcomputer について

私のチームでは `okcomputer` を選定しました。
[ianheggie/health_check](https://github.com/ianheggie/health_check), [lbeder/health-monitor-rails](https://github.com/lbeder/health-monitor-rails) との比較で悩みましたが、ビルトインのチェックの多さと、 :ok: の場合にもメッセージを返却できる点で [sportngin/okcomputer](https://github.com/sportngin/okcomputer) を選びました。

> 他は、Rails5 リリース以降にメンテナンスが行われていない事や、:star: が少なすぎる等で一時除外しました。ただ、komachi_heartbeat だけは、選定が終わってから見つけたのでなんとも言えないです。

### 使い方

https://github.com/sportngin/okcomputer

README が丁寧にかかれているので特に迷うことは無いと思います。

最も簡単な例であれば `Gemfile` に追加しただけで以下にリクエストすればチェックが行われます。

- `/okcomputer`
- `/okcomputer/all`
- `/okcomputer/default`
- `/okcomputer/database`
  - database チェックだけ個別に行いたければこちら

`/okcomputer` 以外は、`.json` をつければ JSON フォーマットで返されます。

```
% curl http://localhost:3000/okcomputer/default
default: PASSED Application is running (0s)%

% curl http://localhost:3000/okcomputer/default.json
{"default":{"message":"Application is running","success":true,"time":9.889001375995576e-06}}
```

見ての通りで成功時でも `message` を返せるため、単純に boolean の結果としてだけでなく、アプリの内部状態を知るための情報を返させてもいいかもしれません。

## okcomputer の Collection 機能を使う

README には記述されていませんが、Collection という機能が存在します。（なくなる予定なのかもしれません）

### チェックの登録時に collection 名が渡せる

okcomputer ではチェックの登録に以下のように `Registry` を使います。

```rb
OkComputer::Registry.register "database", OkComputer::ActiveRecordCheck.new
```

`Registry#register` は以下のようになっています。

```rb:lib/okcomputer/registry.rb
module OkComputer
  class Registry
    # Public: Register the given check with OkComputer
    #
    # check_name - The name of the check to retrieve
    # check_object - Instance of Checker to register
    # collection_name - The name of the check collection the check should be registered to
    def self.register(check_name, check_object, collection_name=nil)
      check_object.registrant_name = check_name
      find_collection(collection_name).register(check_name, check_object)
    end

    private_class_method def self.find_collection(collection_name=nil)
      collection_name ? default_collection.fetch(collection_name) : default_collection
    rescue KeyError
      raise CollectionNotFound
    end

    def self.default_collection
      @default_collection ||= CheckCollection.new('Default Collection')
    end
```

`#register` は `collection_name` という引数があり、それを使って `default_collection` から `fetch` をしているように見えます。
そして `default_collection` は `CheckCollection` のインスタンスであることがわかります。

続いて、`CheckCollection` を見ていきます。

```rb:lib/okcomputer/check_collection.rb
module OkComputer
  class CheckCollection
    def initialize(display)
      self.display = display
      self.collection = {}
    end

    # Public: Returns a check or collection if it's in the check collection
    #
    # key - a check or collection name
    # throws a KeyError when the key is not found
    def fetch(key, default=nil)
      found_in = self_and_sub_collections.detect{ |c| c[key] }
      raise KeyError unless found_in
      found_in[key]
    end

    def sub_collections
      checks.select{ |c| c.is_a?(CheckCollection)}
    end

    def checks
      collection.values
    end

    def self_and_sub_collections
      [collection] + sub_collections
    end

    def register(name, check)
      collection[name] = check
    end
```

`CheckCollection` は内部に `collection` という Hash を持つだけのようです。 `#fetch` では、渡されたキーから自身が管理する collection と、自身が管理する collection の中にある sub_collections が管理する collection からも検索を行っているようです。

つまり以下のような構造で collection を管理しているようです。

```rb
OkComputer::CheckCollection.new('Default Collection')
@display = 'Default Collection'
@collection = {
  'database' => OkComputer::ActiveRecordCheck.new,
  'sidekiq' => OkComputer::SidekiqCheck.new,
  'sub1' => OkComputer::CheckCollection.new('sub1') # この中でもさらに collection がある
}
```

再帰的に `fetch` するわけではなさそうなので、おそらく第一階層の sub 階層までになりそうです。

よって、`Registry#register` の際に `collection_name` を渡すと、 Default 以外の collection にチェックを登録することができます。

そして Default 以外の collection は、単に `CheckCollection` を `Registry#register` すれがいいことがわかります。

### Collection を追加する

ここまで分かると独自に collection を追加できます。

```rb
OkComputer::Registry.register "special_check", OkComputer::CheckCollection.new('Special Check Collection')
```

collection を追加できれば、この collection にチェックを追加しましょう。

```rb
OkComputer::Registry.register "migration", OkComputer::ActiveRecordMigrationsCheck.new, "special_check"
```

こうすると以下の URL でチェックができます。

```
% curl http://localhost:3000/okcomputer/special_check
% curl http://localhost:3000/okcomputer/migration

# 以下は駄目
% curl http://localhost:3000/okcomputer/special_check/migration
```

所属する Collection が異なるので以下のように default で呼び出しても `migaration` チェックは実行されません。

```
# 以下の結果に migration の結果は含まれない
% curl http://localhost:3000/okcomputer/default
```


### Collection の用途

`okcomputer` はもちろん単純な Health Check 用途に使った方がいいですが、Collection を分けておくことで、監視のエンドポイントを分けられたり、メトリクス用にチェックを作る時に、うまくグルーピングができて便利です。

## 参考

2 年以上前ですが、ドリコムさんは `komachi_heartbeat` を利用していると書かれています。
[Mountable Engine だらけの Rails アプリ開発 - http://blog.onk.ninja/](http://blog.onk.ninja/2014/12/02/mountable_engine)

Gitlab は `health_check` を使っているようです。
https://gitlab.com/gitlab-org/gitlab-ce/blob/master/Gemfile#L382