障害アラートコール×多人数通話接続という仕組みを思いついたのでサーバレスで実現した

この記事はSpeee Advent Calendar 2018かつTwilio Advent Calendar 2018の5日目の記事です。一石二鳥という魂胆です。

エンジニア界隈で定番のAdventCalendarですが、私はAdventCalendarの由来がお菓子であることを知らず、先日アンジャッシュ的なことをかましてしまいました。

妻:アドベントカレンダーあげたら〇〇ちゃん(友人のお子さん)喜ぶかな〜
自分:え、AdventCalendarってなにか知ってるの?てか流石に〇〇ちゃんには難しすぎない?
妻:もう少し子供向けのがいいかな?
自分:いや子供向けっていうか、そもそもどんな記事書くつもりなの?
妻:え?
自分:え?

はじめに

Webサービスはいかなる努力を費やしたとしても死ぬときは死にます。

昨今のWebサービスでは、死活監視ツールを入れてサーバの生存状況やら異常やらを検知できるようにしているケースも多いと思います。

しかし異常を検知できたとしても、すぐ人が気づいて対処できるかどうかは別問題です。例えば深夜の時間帯や外出中など、そもそも気づくことが困難なシチュエーションがあります。

ある程度の規模の開発チームであれば、必ず誰か待機者を立てたりエスカレーションフローを組むなどしてカバーできるかもしれません。しかし、せいぜい2~3人の小規模チームだとそれは難しいです。

個人的にありそうなネックを列挙していきます。

  • 障害に気づきにくい
    • 他所に気を取られてると反応が遅れることも多いかと思います
  • 行き違いによって二次災害が起こる恐怖
    • 他の誰かがすでに対応開始しているかもしれない
    • 操作が衝突することは避けたい
  • 個人での対処の判断が難しいケースもある
    • 他メンバーに相談したいのにレスが帰ってこない
  • コストかけたくない
    • PagerDutyとかあるけどそこまでお金払いたくない
    • OSSでもwakerとかあるけど監視サーバの保守もあまりしたくない
    • 固定費はあんまりかけたくない、でも障害には気づきたいんや。。!(ジレンマ)

ということで、すごくお手軽にこれらの問題を解決できそうな仕組みを考えました。

そして思いついたのが 障害アラートコール×多人数通話×サーバレス という組み合わせです。

アラートコールによる通知

f:id:kohtaro24:20181206013850p:plain

電話発信で障害を伝える仕組みは近年よく採用されてるように思えます。プッシュ通知よりも強力ですからね。

多人数通話接続で障害対応をサポート

f:id:kohtaro24:20181206020443p:plain

せっかく電話で受けるなら、通知で終わるんじゃなく電話の機能をもっと有効活用したいじゃないですか。 電話に出た人全員の通話をそのまま繋げてしまえば、コミュニケーション取りながら対応できるので作業の衝突が防げたり、声を掛け合いながら安心して作業を行えそうです。

サーバレスでコストカット

いつ起きるか分からない障害の通知のために常時サーバを動かしておくのは、可能なら避けたいところです。 これらの仕組みをサーバレスで実現できれば、固定費がまるまる浮くわけですね。


作ってみた

f:id:kohtaro24:20181206024214p:plain

なるべく簡単に色んなアプリケーションと連携できるように、このようなWebhookを作りました。 このWebhookを実行するだけでアラートコールが発信されます。 GETパラメータで発信対象の電話番号リスト応答時に最初に流れる音声ガイドの内容を指定できるようになっており、アラートの種類に合わせて柔軟に内容を変更できます。

実際に動いている様子がこちらです。 アラートコールを受け取ったら最初に音声ガイドが流れ、その後応答者同士の通話が開始しています。

いかがでしょう?ちょっと使ってみたくなりませんか!?

これらの仕組みは全てTwilio上で構築されており、サーバレスの実現にあたりTwilio Functionsを使用し、通話接続にはConferenceという機能を使用しています。着信からの通話接続の仕組みについては去年作ったこれが大体ベースです。

以下で作り方を解説していきます。ものの30分であっという間に実現できてしまうのでぜひ試してみてください。

発信用電話番号を購入する

Twilioコンソールにログインし、電話番号の購入ページから適当に050番号を探して購入します。

050番号は毎月108円の維持費がかかります(これだけは固定費としてかかってしまう)。

f:id:kohtaro24:20181206031223p:plain

Functionsを作成する

Functionsを使ってサーバレスアプリケーションを作っていきます。

  • Functionsについて
    • Twilioコンソール上でjavascriptを書いてボタンをポチるだけでAPIエンドポイントが作れる
    • 静的ファイルのホスティングを提供するTwilio Assetsと組み合わせることができる
    • AWSAPI GatewayとLambdaのラッパーのようなものだと思えばいい

AWSAPI Gateway×Lambdaを使って自前で構築する場合と比較すると、このあたりが嬉しいところですかね。

  • GUI上でさくっと作ってデプロイできる(一応CLIから操作するためのツールキットもあります)
  • Twilio関連ライブラリがそのまま呼び出せる
  • Twilioに関するクレデンシャル情報の設定を省略できる
  • Twilioのプライベートネックワークからのリクエストのみを受け付けるように制限かけたりできる

ではまずTwilio Functionsの設定画面を開きます。

Enable ACCOUNT_SID and AUTH_TOKEN がONになってることを確認し、なってなければONにしてSaveします。

f:id:kohtaro24:20181206043020p:plain

次にTwilio Functionsの作成画面を開きます。

f:id:kohtaro24:20181206035113p:plain

今回は二つのFunctionを用意します。

  • Function(1): Webhookリクエストを受けて指定の電話番号に発信する
  • Function(2): 発信に応答があった場合にコールバックされ、応答者を電話会議につなぐ

テンプレートを聞かれるのでBlankを選択して作成。

f:id:kohtaro24:20181206035215p:plain

作成されたら、このように書き換えます。

f:id:kohtaro24:20181206040730p:plain

以下がFunction(1)の処理内容です。

GETパラメータとして二つのパラメータを受け取ります。

  • telsパラメータ:+81から始まる発信対象の番号をカンマ区切りで入れる
  • messageパラメータ:応答時に読み上げる音声ガイドのテキストを入れる
exports.handler = function(context, event, callback) {
    const twilioClient = context.getTwilioClient();
    // telsパラメータ内の電話番号に対して順番に発信
    event.tels.split(',').forEach(function(tel){
        twilioClient.calls.create({
            // 応答があったらFunction(2)をコールバックする。
            url: 'https://' + context.DOMAIN_NAME +'/join_conference?message=' + encodeURIComponent(event.message),
            from: '+815012345678', // 購入済みのTwilio番号(発信番号として通知される)。自分が購入した電話番号に書き換えること
            to: tel,
        }, function(err, result) {
            console.log('Created message using callback');
            console.log(result.sid);
            callback();
        });
    });
};

Saveを押したら即デプロイされます。

f:id:kohtaro24:20181206042551p:plain

これでアラートコールを実行するWebhookが出来ました。

f:id:kohtaro24:20181206050046p:plain

次に応答者を通話接続するためのFunction(2)を作成します。 (1)と同様にBlankを選択して作成したら、このように書き換えます。

f:id:kohtaro24:20181206044901p:plain

以下がFunction(2)の処理内容です。

GETパラメータとして一つのパラメータを受け取ります。

  • messageパラメータ:応答後に読み上げる音声ガイドのテキストを入れる(Function(1)から渡される)
exports.handler = function(context, event, callback) {
    let twiml = new Twilio.twiml.VoiceResponse();
    let conferenceName = 'AlertRoom'  
    twiml.say({ language: 'ja-JP' }, "エマージェンシー、エマージェンシー、" + event.message) // メッセージを読み上げる
    twiml.dial().conference(conferenceName); // AlertRoomという電話会議室に招待。最初の一人は保留音が鳴った状態で待機になる。
    // twiml.dial().conference({ waitUrl: 'https://hogehoge/assets/保留BGM.mp3' }, conferenceName); // 保留音として任意のBGMを流すこともできる
    callback(null, twiml);
};

Saveをお忘れなく。

これで発信の応答者を電話会議につなぐ仕組みができました。

f:id:kohtaro24:20181206050510p:plain

なんとこれで完成です! Function(1)のエンドポイントにGETパラメータをつけてアクセスしてみましょう。ちゃんと設定できていれば電話がかかってくることでしょう。

https://#{割り当てられたサブドメ}.twil.io/alert?tels=#{発信したい電話番号(+81が必要)をカンマ区切りで}&message=hoge

あとは任意の監視ツールにて特定のアラートを拾った際に、このURLにリクエストをHookすればOKです。

利用してるSaaSがメールでの通知にしか対応してない場合などは、Sendgridとかとうまく連携すると良さそうです。

まとめ

  • 障害に気づきにくい

    → 電話鳴らして気づかせよう

  • 行き違いによって二次災害が起こる恐怖

    → メンバー全員の通話を繋いでしまえ!

  • 個人での対処の判断が難しいケースもある

    → メンバー全員の通話を以下略

  • コストかけたくない

    → サーバレス化で固定費は毎月108円(番号維持費)。障害が起きない限り通話料は発生しない!

    → Functionsは毎月1万アクセスまで無料なので、障害が1万回起きなければ無料

    → エンドポイント一個作っとけばアプリケーション側はWebhook指定するだけ!

電話ってすごいな〜

いかがでしたでしょうか。お金と人がいればこんな問題はあまり気にならないかもしれませんが、小さいチームには需要あるんじゃないかということで記事にしてみました。

もしよかったら試してみてください。ご覧頂きありがとうございました。