障害アラート同時通話という仕組みを思いついたのでサーバレスで実現した

この記事は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 URLをトリガーにするようにしました。 GETパラメータで発信先の番号と着信応答時のメッセージ内容を指定することで、アラートイベントに合わせて柔軟に内容を変更できます。

実際に動いている様子がこちらです。

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

作り方

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

ものの30分であっという間に実現できてしまうのでぜひ試してみてください。

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

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

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

Functionsを作成する

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

  • Functionsについて
    • 実態はAWSAPI GatewayとLambdaのラッパー
    • Twilioコンソール上でjavascriptを書いてボタンをポチるだけで特定のエンドポイントにデプロイできる
    • 静的ファイルのホスティングを提供するTwilio Assetsと組み合わせれば割と高い自由度が得られる

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

  • デプロイが楽
  • TwilioのNode.jsライブラリがデフォルトで使える
  • 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): 任意のクライアントからリクエストを受けて各電話番号に発信する
  • 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に処理を渡す。
            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にWebhookリクエストを飛ばすようにしてしまえばOKです。

ちなみにTwilioでもWebhookアラートは設定できます。 f:id:kohtaro24:20181206052204p:plain

Webhookじゃなくてメールしかないよって場合は、Sendgridとかとうまく連携する感じですかね〜…

まとめ

  • 障害に気づきにくい

    → 電話鳴らして気づかせる

  • 作業が競合して二次災害が起こる恐怖

    → 電話で状況やりとりすればいいから大丈夫!

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

    → 電話で相談すればいいから大丈夫!

  • コストかけたくない

    → サーバレス化で固定費は毎月108円(番号維持費)。障害発生時に通話料が発生

    → ちなみにFunctions自体は毎月1万アクセスまで無料

    → Webhook作っちゃえば導入側はURL指定するだけなので片手間にできる

電話ってすげえ。

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

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