最近、弊社でも Slack が使われはじめている。 弊社のアラート通知は今まで IRC (とメール)にだけ流していたが、Slack にも流せるように Ikachan 互換 API を持ち、IRC, Slack 両方に流せるサーバとしてDaioikachan というものを作った。
要件
以下のような要件を満たす
- Ikachan 互換 API
- 複数バックエンド対応
- Slack だけではなく、IRCにも流す(移行期間中用。いずれは Slack に完全移行したい)
- ルーティング機能
- どのチャンネルをどのSlackチームに流すかルーティング設定できる
- プラグイン機構
- IRC, Slack 以外のバックエンドにも対応出来る(せっかくなので)
特に、弊社では草の根活動的に、Slack 利用が広まったという背景もあり、チーム毎に別の Slack チーム(ドメイン) を使っていることがある。 そのため、「このチャンネルは、あのSlackチームのあのチャンネル」という風にルーティングできる機能が欲しかった。
設計
以下のような設計にした
- Ruby 1プロセスで捌く。ミドルウェアに依存しない。
- アプリの特徴として IRC, Slack といった外部リソースに対して I/O ブロックされるが、CPU リソースを多く使うようなものではないので1プロセスで十分そう
- バックエンドへの post は非同期に捌く
- Web API は、enqueue だけして即座にレスポンスを返す
- IRC, Slack といったバックエンドへの書き込みは、非同期に処理する(non-blocking I/O or スレッド)
- 投稿制限があるので sleep をいれてゆっくり処理する
- データの validation は(そんなに)やらない
- バックエンドへの post が失敗した場合にログに warn を吐いて、ログを別途監視して対応
- 設定ファイルでルーティングを記述できる
使い方
README にも書いているけど、いちおう書いておくと、こんなかんじ
$ gem install daioikachan
$ daioikachan -g daioikachan.conf # サンプル設定生成
$ vim .env
IRC_SERVER=XX.XX.XX.XX
SLACK_API_TOKEN=XXX-XXXXX-XXXXXX-XXXXX
$ daioikachan -c daioikachan.conf # 起動
で、あとは
$ curl -d "channel=#channel&message=test message" http://localhost:4979/notice
とかするだけ。デフォルト daioikachan.conf は IRC と Slack 両方に投げるけど、Slack だけに投げたかったら設定ファイルをいじってIRC側を消してもらえればいい。
daioikachan.conf
IRCと2つのSlackチームに流すサンプルはこんなかんじになる。あれ、なんか Fluentd っぽい。
<source>
type daioikachan
bind 0.0.0.0
port 4979
min_threads 0
max_threads 4
backlog 1024
@label @raw
</source>
<label @raw>
<match **>
type copy
<store>
type stdout
</store>
<store>
type relabel
@label @slack
</store>
<store>
type relabel
@label @irc
</store>
</match>
</label>
<label @irc>
<match **>
type irc
host "#{ENV['IRC_SERVER']}"
port 6667
nick daioikachan
user daioikachan
real daioikachan
command %s
command_keys command
channel %s
channel_keys channel
message %s
out_keys message
send_interval 2s # IRC would return Excess Flood, so sleep
</match>
</label>
<label @slack>
# #team1_warn => team1.slack.com#general
<match *.team1_warn>
type slack
token "#{ENV['TEAM1_TOKEN']}"
username daioikachan
channel general
color good
icon_emoji :ghost:
flush_interval 2s # slack API has limit as a post / sec
</match>
# other channels => team2.slack.com#${channel}
<match **>
type slack
token "#{ENV['TEAM2_TOKEN']}"
username daioikachan
channel %s
channel_keys channel
color good
icon_emoji :ghost:
flush_interval 2s # slack API has limit as a post / sec
</match>
</label>
daioikachan の実装
勘の良い人は気づいたかもしれないが、daioikachan は Fluentd の仕組みに乗っかって実装されている。
- 非同期処理は BufferedOutput プラグインという仕組みがすでにある
- プラグイン機構がすでにある
- tag や label といった設定ファイルでのルーティング機能がすでにある
- IRC, Slack プラグインもすでにあるし、
- 他にも hipchat、twitter、mail、 twilio プラグインなどもすでにある
あとは Ikachan 互換の HTTP を受ける口だけあれば良い、ということで、in_daioikachan というプラグインを書いた。 プラグイン内部で http サーバ puma を動かしつつ、生 rack なコードを書いて実現している。
in_daioikachan
にこんなかんじで post すると、
curl -d "channel=#channel&message=test message" http://localhost:4979/notice
内部的には以下のようなメッセージが emit されるので、
notice.channel {"command":"notice","channel":"channel","message":"test message"}
それを output プラグインで処理すれば良いというわけだ。ちなみに、パラメタを増やすと、emit メッセージのフィールドも増えるので、out_mail 用に subject を増やすとかもできるかもしれない。
fluent-plugin-irc, fluent-plugin-slack
この2つは機能拡張、性能検証もしっかりやって、daioikachan にデフォルトで bundle している。
- fluent-plugin-irc
- fluent-plugin-slack
まだ、マージされていないものがあるので、その辺はあとで更新しますが、お使いいただける状態です。
まとめ
複数のバックエンド(IRC, Slack, etc)に対応する Ikachan 互換サーバ、Daioikachan を書いた。 バックエンドを増やしたい時には、Fluentd のプラグインとして書けば良いので、既存の資産に乗っかれて大変便利。
どうぞご利用ください。