アプリケーションからSlackへの投稿を円滑にするSlack Anywhereをリリースしました

以前、Slackの検索機能を強化するSSlackをリリースしましたが、今回は引き続き、アプリケーションからSlackへの投稿を円滑にするSlack Anywhereを公開しました。 本記事では、その開発の経緯や使い方を説明します。

開発の背景

WebPayは私を含め、遠隔地で勤務しているメンバーが多いため、ChatOpsを積極的に取り入れています。 Slackは多くの外部ツールと簡単にインテグレートできる点でChatOpsにぴったりです。 しかし、用意されているものでは不満が出てくるのが技術者の性です。 導入当初から、より自分たちのオペレーションにマッチした機能に改良し、業務を効率化するために連携機能の開発をすすめてきました。

Slackに繋ぎこむサービスをつくるのではなく、手製ツールからSlackに通知を飛ばす場合、おもにIncoming WebHooksSlack APIを使います。 しかし、この基本ツールには次のような制限があり、制限を満たした実装をするために結構な量の実装が必要です。 (内容は調査時点のものであり、それ以降変更がある可能性があります。)

  • Incoming WebHooksではメッセージの投稿しかできず、スニペット形式の投稿ができない
  • Incoming WebHooksなどで行う通常の投稿は、10,000字制限がある
  • Incoming WebHookをチャンネルに結びつけて作成すると、そのチャンネルを閉じたときに使えなくなる
  • Incoming WebHooksの作成権限や、作成されたWebHookの管理に手間がかかる
  • Slack APIは利用者個人が1つだけ持つtokenで認証を行うため、Incoming WebHooksのようにたくさん作れない
  • Slack APIのfiles.uploadでは、チャンネル名を直接指定できず、channels.listAPIで取得できるチャンネルIDを指定する必要がある
  • 秒間1回というリクエストレート制限がある

メッセージとスニペットの両方を投稿する機能を組込もうとすると、結構な量の実装が必要なことが想像できるでしょう。 さらに外部のサーバに設置する場合、個人に紐付いたトークンしか利用できないのは、権限やセキュリティの問題がついてまわります。

WebPayではAWS上のホストの診断を実施して結果をSlackに貼りつけたり、デプロイの経過、エラーメッセージを通知したり、審査進行など、スタッフが対応する必要のある更新情報をサーバから流すなど、本当にあらゆる場面で情報集約をおこなっています。 そうしていると、これも通知したい、あれも通知したいというニーズがどんどん出てきて、さらにChatOpsの比重が高まる雰囲気がありました。 各ツールで使っている言語、環境も様々であり、そのたびに上述の実装をするのは大変な手間になることが予想されます。

そこで、SlackのAPIをさらにラップしたAPIサーバを作成し、チームで必要となる機能だけを持たせて、あとはそこに送りつけようというアイディアを得ました。 そうして出来たのがSlack Anywhereです。 権限管理に不自由さを感じていたので、権限の階層を使いやすく設計しなおしました。 メッセージ投稿はSlack APIをラップして投稿内容だけを送れば、あとはSlack Anywhereがうまいことやってくれるようになっています。 こうして、外部のサーバに設置しても安心して使うことができ、curlしかない環境でも1行で安心して使えるツールが完成しました。

機能

Slack Anywhereのリポジトリはgithub:webpay/slack-anywhereになります。 C++で記述しており、cpp-netlibでin、outのHTTP接続を取り扱い、データはMySQLにストアしています。

Slack Anywhereが持つオブジェクトから紹介します。 Slack上での発言者に相当するuser、userがSlack Anywhereを利用するためのtoken、そして投稿内容を表すpostがあります。 管理者がuserを作成し、userが複数のtokenを持ち、tokenを使ってpostを送信するという構造です。

userの名前は好きにつけることができ、実際のチームメンバーを表しても、botでもかまいません。 標準のWebHooksなどにも言えることですが、名前は自由につけられるので、通常のSlackの利用より表現能力が高いです。 例えば、"tomykaira_deployer"といった名前のユーザを作成し、メッセージの名前だけで実行した個人と役割が識別できるようにしています。

tokenはユーザごとに複数つくることができ、さらに有効期限も設定できます。 標準のWebHooksのようにいちいちユーザ名を指定しなくても、tokenを保持するSlack Anywhereのuserが自動的に発言者に設定されます。 これによって投稿時の負担が少なくなっており、基本的にはメッセージと送信先のチャンネルを指定するだけで済みます。

有効期限は、外部のサーバに一時的にtokenを設置するようなケースを想定して実装しました。 外部でプログラムを実行する時に、ユーザの永続トークンを使って一時トークンを作り、これをサーバ上の設定ファイルなどに配置します。 万が一第三者に流出しても、Slack APIのトークンと違ってメッセージ投稿の権限しか持ちませんし、一定時間利用されなかった場合はそれもできなくなるので、セキュリティ上の懸念はゼロと言えます。

最後にpostですが、通常のメッセージを投稿するだけなら、channelcontentを指定すればOKです。 さらに、as_snippettrueにするだけでスニペット形式での投稿になります。 スニペットは長文や、その文面に対して継続的に議論したい場合に効果的で、インシデントに複数人で対処する場合に役立っています。

WebPayではAttachmentsによるメッセージをあまり使っていなかったので、現在Slack Anywhereにそれを取り扱う機能はありません。 使いたいなと思ったら、ぜひ実装してPull Requestを送ってください。

使い方

最初にセットアップをします。

  1. リポジトリをクローンし、makeでコンパイルしてください。いくつかのライブラリに依存していますので、それらは適宜準備してください。
  2. config.sample.jsonを適宜編集し、設定してください。root_tokenは、管理にのみ利用するtokenです。userと、userの最初のtokenを作ります。
  3. 設定ファイルで指定したデータベースにschema.sqlを流し込んで、テーブルを作成します。

あとは、userの作成、そのuserのtokenの作成、そのtokenを用いての投稿を実際にやってみましょう。 sample.shが、このフローを実行するシェルスクリプトです。 READMEのAPIドキュメントと、このサンプルを対照すると、だいたいの流れがわかると思います。

一旦セットアップした後の、運用方法は上に説明したとおりです。 シンプルなツールなので、それぞれのチームの慣習に合わせてお使いください。

終わりに

このツールを作りはじめたとき、WebPayには4つSlackにメッセージをポストする実装がありました。 それぞれは50から100行程度のちいさなクラスですが、何度も書いているとインタフェースに差異がでたり、実装品質が違ったりで開発がやりにくくなります。 とくにAPIのリクエストレート制限を実装していないものでは、通常の使用では越えないことを確認するか利用側でバッファリングする必要があり、心理的負担になっていました。

背景として議論したように、いろいろ使ってみたり調査した結果、毎回実装するのはやはりスマートではない、多少の遅延があっても一段まとめ役を挟んだほうがよかろうとの判断に至りました。 C++の開発の知見を得る必要があったこともあり、習作としてSlack Anywhereを作りました。

現在、複数のツールと組み合わせて、バグフィックスを加えながら1ヶ月程度運用した時点です。 結果としては使い勝手がよく、どの言語でも1行から5行で十分な通知機能が手に入るので、非常に気に入っています。 C++の初歩的な作法や知識も得ることができ、その点でも成功と言えました。

チームの現状に沿って作成したため、JSON形式のリクエストしか受理しないなど、幅広いユーザを想定した場合には不満な点もありますが、 Slackを使っているチームのChatOpsの活性化に貢献したいと思い、公開するに至りました。 Twitterなどでのフィードバックや、GitHubでのPull Requestをお待ちしています。