値に変更があった時だけSlackに通知するAPIを作った

ユーザ数などを cron などで定期的に Slack に POST していたが、変化が無い時に通知されてもあまり意味がないので、変化したときだけ Slack に通知する、というのを Serverless で作ってみた。

出来ること

監視する任意の値に変化があった時に Slack に通知してくれる。

もう少し詳細に

監視する値を API に POST し続け、前回の値から変化があったら Slack に通知するというシンプルなもの。例えば、

  • ファイルの md5 を1時間おきに取って API に投げれば、ファイル内容に変更があったら分かる。
  • サービスの登録人数の count を1時間おきに取れば、変化があったときに分かる。
  • クローリングしているデータの最終更新日時の文字列を15分おきに投げれば、変化があった時に分かる。

など、 trivial なデータを気軽に監視できるようにする。というのがこのシステムの主な目的。

ちゃんと作ったシステムはデータ解析のための仕組みがちゃんとあるだろうけど、数時間で作った自分用アプリとかだとそういうのまで作らないので、こういうのがあると便利だなと思って作った。

Getting started

https://monit.ecp.plus/

Web インターフェイスを作ったので、これを使えば監視が始められる。値を入力して作成したら、 curl のサンプルコードが出てくるので、値のとり方だけ自分が取りたいものに合わせて、 crontab とかに登録しておけばOKだと思います。

API Gateway + Lambda + DynamoDB で作ったので、サービスが落ちる心配もないと思います。

GitHub

Value changing monitor

(View on GitHub)

API

監視するリソースはなんでも良いので、 Resource というあまりに抽象的な名前しか思いつかなかったがとりあえずこれで。

  • POST /resources
  • PUT /resources/:uuid

POST で監視対象のリソースを生成し、以後はそこで発行された uuid に対して PUT をしていくようにする。 POST するときに Slack の Webhook の情報 (URL, Channel, Template) を付けておいて、もし変更があったらまた POST して作ればいいやという方針にした。

これは serverless.yml に書くだけで済む。

POST の方を、createResource という function名にして、PUT の方を updateResource という function名にして、 URL から uuid という名前でパラーメータとして受け取るようにした。

Serverless で、 integration を指定しない時のデフォルトが、以前は lambda だったのだが、最近デフォルトが lambda-proxy になってた。 API Gateway のマッピングが変わるので、 Lambda 側で値の取り方が変わる。 lambda-proxy にすると、 path が、 pathParameters という名前になってた。

functions:
  create:
    handler: handler.createResource
    memorySize: 128
    integration: lambda
    events:
      - http:
          path: resources
          method: post

  update:
    handler: handler.updateResource
    memorySize: 128
    integration: lambda
    events:
      - http:
          path: resources/{uuid}
          method: put
          request:
            parameters:
              paths:
                uuid: true

DB

値は DynamoDB に貯めておく。 POST 時に初期値を設定出来て、以後は value のみを PUT していく。変更があった時は Slack に通知して、 DynamoDB の値を書き換える、というシンプルなもの。これなら書き込み&読み込みのキャパシティー両方1で良いので、1秒間に1回!以内のペースであれば月額$0.67で済む(大事)

Key-Value Store としてしか使わないので、AWS Console からテーブルを作った。 uuid を文字列でキーとしてみた。

IAMの設定は serverless.yml に書くだけでOK。今回は、 PutItem, GetItem, UpdateItem のみを許可したいので、下記のような感じにした。

iamRoleStatements:
  - Effect: "Allow"
    Action:
      - "dynamodb:PutItem"
      - "dynamodb:GetItem"
      - "dynamodb:UpdateItem"
    Resource:
      - "arn:aws:dynamodb:ap-northeast-1:1234567890:table/your-awesome-table-name"

Domain

API Gateway のカスタムドメインが未だによく分かっておらず、内部的には CloudFront の裏にいるのに、 CloudFront のようにシームレスに証明書をつけられなくてめんどくさい。Let’s Encypt とかでTLS証明書を自分で作って上げてみてもよいけれど、楽をするために前に CloudFront を立てることにする。なので、おそらく AWS 全体を見ると、 自分で立てたCloudFront -> APIGatewayの内部で使われているCloudFront -> APIGatewayの某かの経路 -> AWSLambda となっており、電気が無駄になっていてエコではなさそう。

API Gateway の前に CloudFront を置く時の注意点としては、動的なデータを扱いたい場合、

  • キャッシュの TTL を 0 にする
  • 必要なヘッダーがあったら転送する( Content-Type を転送しようと思ったが、JSONしかないので application/json でオーバーライドした)
  • 必要に応じて、GET, OPTION 以外のメソッドも許可(詳細に選べないので、今回は全部になった)
  • API Gateway 側に stage名がドメイン直下につくので、 CloudFront の Origin Path をそこに設定する。例えば /production とか。

CloudFront が出来たら、Route53 で Alias として設定すれば完了する。

Serverless

設定をしたら後は特に変わったことはしていない。
GitHub に設定諸々のコードを上げた。 value-changing-monitor

最近ローカルでの開発は、 node-lambda を使ってやっていたが、シンプルなものなら Serverless で作ってデプロイして確認という方法も楽で良いかもしれない。

Web interface

https://monit.ecp.plus/

curl で POST するのは面倒だったので、 mithril を使って Web interface を作ってみた。これも特に変わったことはしておらず特に面白くはない。

値に変更があった時だけSlackに通知するAPIを作った