asken テックブログ

askenエンジニアが日々どんなことに取り組み、どんな「学び」を得ているか、よもやま話も織り交ぜつつ綴っていきます。 皆さまにも一緒に学びを楽しんでいただけたら幸いです!

Twilio も PagerDuty も使わない、アラート電話の仕組みを構築した話

こんにちは。asken でインフラエンジニアをしている沼沢です。
今回は、緊急性の高いアラートを検知した際の電話連絡の仕組みについてお話します。

抱えていた課題

弊社では、元々システム監視はしていたものの、検知時はメールや Slack の通知に留まっており、システムが深刻な状態となった場合に架電する仕組みがありませんでした。
休日や夜間に深刻な状態となった場合にメールや Slack 通知だけでは気付きづらく、早急に対処しなければならない状況の検知が遅れる懸念がありました。

検討したソリューション

Twilio

まず、自身が利用したことのある Twilio を使った架電を検討しました。
しかし思い出したのは、電話番号取得時に日本の法に準拠するために必要な各種手続きの面倒さでした。
https://support.twilio.com/hc/en-us/articles/4406158662171

このような煩雑な手続きをしなくても良いような方法が無いか、探してみることにしました。

PagerDuty

上記のような手続きが不要で架電ができるサービスとして、インシデントマネジメントサービスの PagerDuty を次に検討しました。
PagerDuty では新規に日本の電話番号を取得する必要がないため、Twilio のような煩雑な手続きはありません。

トライアルを実施し、いざ本運用に利用しようと思ったところ、有料プランのトライアルで良かったと感じた機能が無料プランではいくつか利用できないということがわかり、どうしたものかと考えました。
もちろん、オンコール体制を整え、インシデントマネジメントにガッツリ力を入れる場合には非常に強力なサービスだというのは実感できました。

しかし、今の組織上、24/365 のオンコール体制を組むことは現実的ではなく、今回はそこまで高機能なものは求めていなかったため、有料プランを利用するメリットを感じられませんでした。

思い出したこと

ふと本来の課題を思い出した時、緊急時に誰も気付けないという状況を改善したかっただけであることを思い出しました。
言い方を変えれば、モニタリングツールと連携して電話をかけたかっただけ なのだと。
そうなるとまた Twilio が選択肢に浮かんでくるのですが、ここでもう1つ思い出したことがあります。

そう、Amazon Connect です。
https://aws.amazon.com/jp/connect/
Amazon Connect というとコールセンターサービスの印象を持っている人が多いのではないかと思いますが、架電することももちろんできます。
Amazon Connect も PagerDuty と同様に、日本の電話番号を取得しなくても電話をかけることができるため、煩雑な手続きはありません。(2023年7月現在)
Amazon Connect を使って良い感じなものを作れないか検討し、無事に良い感じな仕組みが完成したのでした。

構成図

処理フロー

  1. Step Functions ステートマシンをキック
    • CloudWatch または NewRelic がクリティカルのアラートを検知した際に、EventBridge 経由でキックする
  2. アラート履歴登録用 Lambda 実行
    • アラート履歴テーブル に item を追加
    • アラート通知管理用 Queue に Queue を登録
  3. Connect 呼び出し用 Lambda 実行
    • アラート通知管理用 Queue から Queue を取得し、取得できた場合は以下実行
      • 連絡先テーブル の一覧を取得
      • 一覧の中で最終架電時刻が一番古い人の電話番号を架電ターゲットに指定して Amazon Connect をキック
      • 電話に出たかどうかに関わらず、架電対象者の 連絡先テーブル item の最終架電時刻を更新
    • 次のステップ Queue があったか で参照する変数に、「Queue が取れたかどうかの真偽値」を格納
  4. Amazon Connect のコンタクトフローに則って処理
    1. ログを記録
    2. 架電し、以下の固定文を再生
      • 「国内あすけんで緊急度の高いアラートが発生しました。状況を確認してください。」
    3. 成功した=電話が繋がった時は アラート履歴更新用 Lambda をキックして終了
    4. 失敗した=電話が繋がらなかった時は何もせず終了
  5. アラート履歴更新用 Lambda
    • アラート履歴テーブル の対象アラート item の受電日時、受電者氏名を更新
    • アラート通知管理用 Queue の対象の Queue を削除
  6. Connect 呼び出し用 Lambda で格納された「Queue が取れたかどうかの真偽値」をチェック
    • true
      • 90秒後に Connect 呼び出し用 Lambda を再度キック
    • false
      • Queue がない = 誰かが電話に出たとみなして終了する

コスト

常時かかるコストは DynamoDB に格納されているデータに対してのみです。
ただし、DynamoDB は毎月25GBまで無料枠となるため、他に DynamoDB テーブルがなければ無料で済むでしょう。

他のサービスは、アラートが発生して実行された分だけの課金となります。
緊急度の高いアラートが頻繁に起きるような環境でなければ、数ドル程度の微々たる金額で済むでしょう。

工夫したポイント

運用コストを最小限に

マネージドサービスのみで構成し、サーバレスで実装しています。
電話をかけたいだけなので、運用コストは極力掛けない形を意識しました。
メンテナンスが必要なのは Lambda に乗せているコード(バージョンアップ対応)と 連絡先テーブル に対する架電先の追加変更削除のみです。

電話は異常を知らせることだけに特化

今回、電話では固定の文章しか喋らせていません。
実際の状況はアプリを開いてみたり、Slack 通知から CloudWatch や NewRelic のダッシュボードを表示すればわかるため、電話で詳細を伝える必要はありません。

架電先をローテーション

電話に出たかどうかに関わらず 連絡先テーブル の最終架電時刻を更新することで、架電対象者全員に満遍なく架電されるようにしていて、前回電話を取った人が一番最後の架電対象になるようにしています。

その他 Tips

Connect 呼び出し後の待機時間が90秒の理由

Amazon Connect では、60秒経過しても通話が接続されない場合は通話が失敗となるようになっています。
https://docs.aws.amazon.com/ja_jp/connect/latest/APIReference/API_StartOutboundVoiceContact.html
仮に60秒ギリギリで電話を取ったとして、固定文章の読み上げに11秒ほど要すること、その後の Queue 削除処理が確実に完了していることを考慮した結果、90秒としました。
これによって、1人が電話を取ったのに次の人にも電話が行ってしまうことを防いでいます。
(ことの大きさによっては結局関係者の招集で叩き起こされるかもしれませんが)

アラート履歴テーブル に受電日時と氏名を残している理由

事象発生から検知(人間が気付くまで)の時間の計測に利用するため、受電日時を記録しています。
また、将来的に最初にアラート対応をした回数ランキングなどを作成したり、何かしらの形で表彰するのに使いたいなーという密かな野望があるため、受電した人の氏名も残すようにしています。

あとがき

今回は電話という手段を取りましたが、本来的には「アラートに気付くことができれば良い」ので、スマートフォンのアラーム機能のような、ユーザーがアクションを取るまで音を鳴らし続ける(しかもそれを外部から起動できる)ようなアプリがあれば良かったのですが、そのようなアプリは発見できず。

Twilio 等の素晴らしいサービスがある中で、このような仕組みを自作するのは車輪の再発明のような気がして悩んだのですが、今回は自分たちが抱えていた課題を解決するのに最適かつシンプルなソリューションが見つからなかったため自作しました。
結果的に、架電先のローテーションを工夫したりと、外部サービスを使うだけではできなかったであろう仕組みまで作れたので良かったと思っています。

ちなみに、連絡先テーブルの追加更新削除を Slack Slash Command で実行できたら楽だなーと思ってますので、これはそのうち実装しようかと思ってます。