とんちゃんといっしょ

Cloudに関する技術とか日常とかについて書いたり書かなかったり

Slackチームつなげるワームホールを開発した話

この記事はSlack Advent Calendar 2018の8日目の記事です。

TL;DR

  • Slack有料版のゲスト機能相当を無料で実現する、複数Slackチーム間のチャンネルをつなげるプログラムを開発(下図参照)
  • 環境は紆余曲折を経て現在はGCP上でGCE, Cloud PubSub, Cloud Datastore, docker, slack-ruby-client, Slack RTM APIを使って実現
  • 使い方はレポジトリのREADMEを参照
  • 今後も機能拡張や安定稼働に向けて開発は続けていくつもりだけど実装ペースは気分次第

f:id:mazinlabs:20181204155652p:plainf:id:mazinlabs:20181204155726p:plain
左:Slackチーム A、右:Slackチーム B

レポジトリ

github.com

背景

とあるグループ会社に所属するエンジニア有志のSlackがあり、 グループ会社を卒業するとそのSlackチームも卒業し、OB/OGのSlackチームに移動する決まりになっている。

しかしながら、卒業した人から「困ったときに聞ける人がいるのが良かった」や「情報交換の機会が減った」などという声が上がっていたため、なにか方法がないかと検討したが

私「あるWorkspaceの特定のチャンネルにだけ別Workspaceの人達を入れたいな!」

Slack「できるよ!お金払えば!!」

  • とあるSlackチームのユーザ数(2018/12/4時点):772人
  • 現在のアクティブユーザ数(2018/12/4時点): 396人
  • Slackのスタンダードプランの1userあたりのお値段: 850円 / 月

f:id:mazinlabs:20181204151227p:plain
とあるSlackチームが課金する場合の額

払えるか!!!

というわけで、無料で有料相当の機能を実現すべくSlack APIを使ったメッセージ転送に思い至る。

コンセプト

「あるSlackチームの特定のチャンネルで発言したものが、別のチームの同じチャンネルに転送されて会話ができる」

これだけです。

某Slackチームには #blackhole という発言が一定時間で消えていく人気チャンネルがあるので、2点間をつなぐ、つまりwormholeだなと安直な感じで名前をつけてみた。なおwikipediaで見ても大体あってる。

ワームホール (wormhole) は、時空構造の位相幾何学として考えうる構造の一つで、時空のある一点から別の離れた一点へと直結する空間領域でトンネルのような抜け道である。 ワームホール - Wikipedia

ただ、2点間ではなく複数Slackチームがつながるような設計にしてあったりする。

現在実装されている機能

  • メッセージ処理(CRUD)の転送
  • リアクション転送(文字として転送される)
  • Thread対応

実装されていない機能

  • ファイル転送
  • join / left を検知した通知
  • member listの共有

実装1

最初期の実装は以下を使って実装していた

このときのメッセージ転送の設計こうなっていたらしい

f:id:mazinlabs:20181204160426p:plain
wormholeの初期のメッセージング設計

雑な発表資料

speakerdeck.com

レポジトリ

github.com

当初はBOTの名前とアイコンで転送していたのだが、postMessage APIにユーザ名やアイコンを指定できることがわかったので、発言したユーザの名前とアイコンを使うようにしたことで一気に使いやすくなった。 そして当初うまく動いているように見えたのだが、数日ごとにRabbitMQのプロセスが張り付いて再起動を余儀なくされることに。。。

RabbitMQなんて捨ててやる!時代はマネージドなメッセージングサービスだという思いにいたりCloudnを捨ててマネージドなメッセージングサービスかつ無料で使えそうなGCPにお引越しをして実装2に移行。

実装2

GCP上で以下を使って実装

  • Hubot(CoffeeScript)
  • Docker
  • Cloud Compute Engine
  • Cloud PubSub
  • Cloud Datastore
  • Cloud Build
  • Cloud Container Registry
  • Slack RTM API

f:id:mazinlabs:20181204163142p:plain
実装2と実装4のメッセージングの設計

Githubのレポジトリを更新するとCloud Buildが動いてContainer Registryにコンテナを登録してくれるように。 RTM APIで受けたメッセージをCloud PubSubにPublishし、wormholeとして接続される側がそれをSubscribeする。 Cloud Datastoreでメタデータを管理し、Postされたメッセージが更新、削除された際にその変更削除を転送先に伝搬させる際に使われる。 だいたい$1/月ぐらいで運用できている。

github.com

これはかなり安定して動かせていたものの、やはり時おりHubotが謎のエラーで落ちる。 あとCofeeScriptで書いていたので書きづらい。 毎度コンテナを復旧させるのが面倒なので、リクエストに応じて処理をするEvent drivenなCloud Functionsのほうが良いのではと思い実装3を検討

実装3

  • Nodejs
  • Cloud PubSub
  • Cloud Datastore
  • Cloud Functions

f:id:mazinlabs:20181204163740p:plain
実装3のメッセージング設計

当時はまだCloud FunctionsにPythonが出てきていなかったのでNodeで書いてみるものの、 Functionごとの環境変数の設定や同じ関数名が動かせない(といううろ覚えの記憶)と、 やりたいことができないのでCoud Functionsを断念。(コードもレポジトリ管理する前に見切った)

結果として自分が得意なRubyにしたほうが一から実装してもあとの機能追加がきっと楽だと思って実装そのものを乗り換えることに。

実装4

現在動かしているのがこのバージョン

  • slack-ruby-client (Ruby)
  • Docker
  • Cloud Compute Engine
  • Cloud PubSub
  • Cloud Datastore
  • Cloud Build
  • Cloud Container Registry
  • Slack RTM API

github.com

メッセージングの設計自体は実装2からさほぼ変更はなし。

slack-ruby-clientのRTM clientが定期的にログも出さずにコネクションが切断されるという問題(下記リンク参照)があったものの、エラーハンドリングなどで対応して現在安定稼働中。

Slack-side disconnects · Issue #208 · slack-ruby/slack-ruby-client · GitHub

ただ、やはりRTM clientの動作が不安定なのとCloud Functionsの機能追加などで実装3のEvent APIベースに戻そうかと悩んでいるところ。

今後の予定

直近はreactionのremoveの挙動が多分不思議な事になってるのでそれの修正をするが、 slack-wormholeのissuesにあげてることぐらいはやりたいなと思ってる。

もし使う人がいてバグ報告や要望があれば暇を見て実装したり直したりしたいところ。

まとめ

紆余曲折を経たもののSlackチーム間をSlack APIを使って無料でつなぐプログラムを開発した。

今後も改良や修正は続けていくつもりなので利用者からのフィードバックお待ちしております!