サーバーレスな外形監視ツール pingbot

サーバレスアーキテクチャーって言葉が流行っているのでその近辺技術に触れてみたくてアプリを一本こしらえてみました. リポジトリは github.com/toricls/pingbot です.

何を作ったか

サーバーレスな外形監視ツールです. 1 分ごとに任意の Web サイトに対して http/https リクエストを投げることで外形監視を行い、その結果を保存します. 前回チェック時と比較してステータスが変化した場合、Slack にその旨通知してくれます.
安価かつ安定して任意の Web サイトに対してヘルスチェック(死活監視)を行うことを目的として、AWS Lambda、Amazon CloudWatch Events、Amazon DynamoDB あたりを使っています. サーバーレスアーキテクチャーの構成要素として良く利用されるサービスですね. 管理画面となる Web アプリの方は React とかその辺を使ってみました。

だいたいこんな感じ

Overview

動かしたときのスクリーンショットとかはリポジトリの方に置いてます.

なぜ作ったか

月末の Serverless Conf Tokyo に参加するにあたって、サーバーレス関連技術のキャッチアップをしておきたいなーというところがスタートです.
(普段サーバーサイドばっかりなので、華やかなフロントエンド界の隅っこに触れてみたいという気持ちもちょっとだけ…)

  • サーバーレスアーキテクチャー界隈で語られるサービスや技術のお勉強
  • フロントエンドで利用される要素技術のお勉強

このあたりがモチベーションです.

お題をヘルスチェックアプリにしたのは、さくらのシンプル監視が安くて便利でいいなーと感じて似たようなことをやりたくなったからです.

ゴール

  • ヘルスチェックを行えること
  • メンテナンスが必要なサーバー(EC2とか)が存在しないこと
  • 運用費が安価であること :)

世の中で語られるサーバーレスのメリットとして目立つポイントを狙っていくとこんな感じのゴールかなと思って着手しました.

中身の説明

  • 1 分に 1 回、Lambda で DynamoDB に登録されたチェック対象データを取得
    dispatcher.js
  • チェック対象に対してヘルスチェックを実行して DynamoDB に結果を記録
    health-checker.js
  • 結果ステータスの変化(OK <-> NG)を検知したら結果通知を呼び出し
    result-processor.js, DynamoDB Streams
  • Slack に通知
    slack-notifier.js

health-checker.js についてちょっと掘り下げ

このアプリケーションのコアとなるヘルスチェックスクリプトで、対象のホストに対して http/https リクエストを投げます.

http/https リクエストのタイムアウトは 10 秒になっていて、

  • 対象サーバーから 5xx エラーが返ってきた
  • ネットワークの瞬断とかで通信切れた

などが発生した場合に最大で 5 回までリトライを行います. dispatcher が 1 分に 1 回走ることから、これ以上の回数リトライすると過去の結果が最新の結果を上書きしてしまう事象が発生してしまうので、こんな感じの実装です.

Lambda ファンクションのリトライについては最後の方でまたちょっと触れます.

チェック対象モデルの属性

DynamoDB の pingbot-targets テーブルはこんな感じになります.

  • uuid: チェック対象を一意に特定するキー
  • displayName: 通知メッセージや管理画面に表示される名前
  • protocol: http/https
  • host: ホスト名
  • port: ポート番号
  • path: パス
  • slackChannel: 通知先となる Slack のチャンネル名
  • slackWebhook: 通知用の Slack Incoming Webhook URL

例えば host に BASIC 認証の id/パスワード も一緒に入れとけば BASIC 認証付きの Web サイトも監視できますね.

チェック対象の管理とチェック結果の閲覧

  • S3 の静的 Web サイトホスティング機能でホストされた React アプリケーションで AWS API を叩いている
  • JavaScript から AWS リソースにアクセスする権限付与は Cognito Identity Pools で

とりあえず動かしてみたいと思ってもらえたら

CloudFormation テンプレートなどのスクリプトを用意しています. お使いの AWS アカウントに pingbot stack をセットアップする方法は、こちらのドキュメントをご覧ください.
ちなみに pingbot stack 自体の更新や削除のためのスクリプトも用意してありますので、試した後に削除したくなった場合も同じドキュメントを参考にスクリプトを実行してください.

お金の話

$0.1/month くらいで運用できます. S3 とかにも無料枠がある新規 AWS アカウントであればおそらく完全に無料です.
チェック対象が増えてくると DynamoDB のキャパシティーユニットを拡張する必要が出てきますが、新規アカウントかどうかに関係なく常に月間 25 の Read/Write キャパシティーが無料らしいので、特にお金がかかることもないかなーと思います.
(真面目に計算しようとすると Web アプリからの DynamoDB へのリクエストとかもカウントしなきゃいけないので、今のところちゃんと計算してませんごめんなさい)

ハマったこととか感じたこととか

Cognito が CloudFormation でデプロイできないじゃないか

Lambda-backed Custom Resource を使いましょう、というのが定石らしいです. 何で対応してないのかはわかりませんが、機会があったら AWS の人に聞いてみたい.
binoculars/aws-cloudformation-cognito-identity-pool というリポジトリを公開してくれている方がいて、そのままでも使えるくらいイイ感じです. とっても助かりました.

Lambda 失敗時の自動リトライをそのまま使って良いかどうかはアプリケーションの内容次第

ネットワーク不調などで通信がこけた場合を含め、Lambda ファンクションとして失敗したというステータスになると、Lambda の実行基盤は自動的に 3 回まで(だったかな?)リトライをしてくれます.
これはこれで素晴らしいのですが、このリトライ機構はおそらく Exponential Backoff 的な何かで次の実行時間が再計算されているため、 Lambda 実行基盤がリトライをしてくれる時には次のスケジュール実行が始まっている(= リトライによる過去の結果が最新の結果を上書きしてしまう)可能性がありました.
上述したとおり、今回のヘルスチェックは 1 分に 1 回実行するという仕組みなので、この Lambda 自体のリトライ機構は利用せず、エラーを検知したら health-check.js の中から自分自身を再度 Invoke する、という手段を採ってみました. (再 Invoke じゃなくて同一セッション内で再起呼び出しする方法でもよくない?っていう疑問がこの記事を書いている最中に生じたんですが、とりあえず現状はこんな感じです)
今回のアプリケーションに限らず、Lambda スクリプトの内容によっては自動リトライされたら論理的にバグになってしまうものはあると思うので、その辺りを想定してコードを書かないと困った事象を生むのかなーと思います.

サーバーレスなアプリのデプロイ/チーム開発の難しさ

これまで世の中で流行ってきた Web フレームワークは結構デプロイ手法などが確立されていて、環境別に設定を切り替えたり複数のアプリケーションインスタンスを別々の環境で簡単に動かしたりと何かと便利でした.
今回は一人でアプリを書いたのと、開発環境/本番環境みたいなことの実現を黙殺することでサクサク作業を進められましたが、チームでやろうとするとけっこう大変になるな、という印象です.
Serverless, apex, Chalice あたりが注目されるのはさもありなんという感じですね.

CloudFormation すごい

最初に書いたことと全く逆じゃないかと思うかもしれませんが、デプロイ -> 全部削除がコマンド一発でできる CloudFormation はやっぱりすごいなーと思いました. (ときどき削除に失敗するのはご愛嬌…)

フロントエンドちゃんとやるとすごく楽しそう

今回実装を始めるタイミングでネット上のいろんな記事を見ながら React だったり Webpack だったりを使うための情報を収集したんですが、なんというか本気で取り組もうとすると深遠な闇にハマりそうだったので今回の Web アプリはシンプルなものにとどまっております.
ただ、画面が作れるとやっぱり人に見せたときに感想をもらいやすいので、今後も少しずつキャッチアップを継続していきたいな〜と思いました.

テスト書く

Lambda のテスト, React のコンポーネントテストを書こうかなと思ったんですがテストフレームワークについてのキャッチアップエネルギーが切れたのでまた次回にすることにしました. サーバーレスなアプリのテストってどんな感じでやるのがベストプラクティスなのかしら.

今後の予定

  • DynamoDB のキャパシティーとか、Lambda のエラーとかを CloudWatch で監視 -> 通知まで CloudFormation で作成したい
  • テストボタンがターゲットの登録画面に欲しい
  • 管理画面は IP 制限じゃなくて、登録できるユーザーを制限した形で Cognito User Pools を使いたい
  • Monthly/Weekly Uptime Reporting
  • Slack 以外の通知手段 (通知が失敗した場合にフォールバックとしてメール通知ってのを上述のさくらのシンプル監視がやっていていいなーと思いました)
  • 特に React のソースがガヤガヤしてるので整理したい

などなど…

不具合のご連絡、機能要望、プルリクなどなどお待ちしております.
(サーバーレスとか js とかに詳しい方からのツッコミ/改善提案もらえるとすごく嬉しいです.)

参考