値に変更があった時だけ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を作った

PhoenixFramework の WebSocket に iOS からつなぐ

PhoenixFramework の WebSocket に iOS から繋ぐのはとても簡単だった。

Awesome iOS with 🌟 で、 Swift で書かれている中から Starscream を選んで使ってみた。README にでかでかと表示されたロボットがカッコよかったので。

PhoenixFramework は ES6 で書かれた WebSocket 通信のためのクラスが最初から用意されてて、その仕組みに乗るとすごく楽なので、Swift 側で同じ仕組みに乗るような通信をすればよい。PhoenixFramework で実装された WebSocket の Chat を使ってみる。

PhenixFramework では、特定の topic に join するというのがあるが、これは JSON に event: "phx_join" というのを付けて送ってる模様。また、まだ良く理解できていないが毎通信ごとに ref というカウンターをインクリメントしてるので倣ってみる。 topic には、Phoenix 側で使ってる topic をそのまま入れれば、あとは好きなように payload にデータを入れてやり取りすればOKなようだった。とても簡単。

下記のようなコードで、Phoenix 側と通信して、 JavaScript で繋がってるブラウザとやり取りが出来た。

PhoenixFramework の WebSocket に iOS からつなぐ

WKWebView で NetworkActivityIndicator を良い感じに表示(Swift3)

以前 iOS9 が出たときに書いたものが古くて使えなくなってたので、書き直した。

iOS9とSwift2の頃のやり方

iOS9 から追加された WKWebView で、networkActivityIndicator (iPhoneの上部のステータスバーで読込中にぐるぐるさせるやつ) を表示する方法。 UIWebView と WebView はもう使うなって API Reference に書いてある。

Swift3 になって書き方も変わっているのと、iOS9 から constrains の設定もしやすくなった。WebView の一種でコンテンツを読み込むとき、isNetworkActivityIndicatorVisible を処理しないことが無かったので、 pod でインストール出来るようにした。

EcpWebView (on GitHub)

続きを読む “WKWebView で NetworkActivityIndicator を良い感じに表示(Swift3)”

WKWebView で NetworkActivityIndicator を良い感じに表示(Swift3)

Mithril (JS Framework) を ES6 で書く

Mithril という軽量な JavaScript Framework を友人に勧められたので使ってみた。フレームワークとしてとてもシンプルなので、公式サイトの説明を読めばすぐ使い始められる。

ES6 で書く場合、 Mithril ES6 Components(gist) をベースにすればOK。と公式に書いてあった。

ES6 で書いてトランスパイルした後のファイルが、 React を使っていた場合だと 120KB くらいになってたが(もしかしたら手元の設定がダメなだけでもっと小さく出来るのかもしれない) Mithril を使ったら、24KB くらいになった。

公式サイトの Performance を見ると、ローディングもレンダリングもかなり速いようだが。 (公式URL: Mithril)

React のように仮想DOMを内部で持っていて、データバインディングして使えるので、React で書く感覚で書ける。試しにReact 使って書いたものを置き換えてみたらすぐ置き換えられた。

実装はシンプルで、JSの機能さえ要求を満たしていれば古いブラウザでも動くらしいので、追加でライブラリをロードすればIE7ですらいけるらしい。

あまり使い込んでいないけど、AJAXでファイルを読み込むのがフレームワークに組み込まれてて、 Promise のインターフェイスで実装されてるので個人的にはかなり使いやすい。尤も、AJAXでファイルロードするのは標準API使って書いても短いので、その他の部分でどうなってるかは分からないけれど。

Awesome Elixir with 🌟 の JS はこんな感じ。 JSX 的なライブラリも出てるようで、そっち使った方が見やすいかも。

Mithril (JS Framework) を ES6 で書く

Phoenix Framework で WebSocket のチャット

WebSocketでチャットという、遠い昔に流行った題材で、 Phoenix Framework を使ってデプロイまでやってみた。

URL

とりあえず Nginx の背後において動かしてみた。 EC2 の t2.nano インスタンスで動かしてみてる。割とサクサクしてる感じがします。
https://chat.ecpplus.net 2つブラウザ開くか、PCとスマホとかで見ると通信の速度が確認できます。

環境構築

elixir

elixir のインストールは Homebrew とかで出来ます。

$ brew install elixir
$ brew install rebar3 # 手元だと、prod の compile で必要だった
$ elixir -v
Erlang/OTP 19 [erts-8.1] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]

Elixir 1.3.3
$ node -v
v6.6.0
$ npm -v
3.10.3

vim

vim-elixir, vim-phoenix を入れてみた。2個目はhaml対応してなかったので、fork して使った。 vim-phoenix は、 vim-projectionist で作られてるので、使い勝手は rails.vim と似たような感じになります。 rails.vim ほどは作り込まれていないけど、十分な感じ。 Rails の開発は、 rails.vim のおかげで3倍くらい速くなってる気がするので、 Phoenix Framework のファイル構成が Rails に似てるのもあって、慣れてきたらはやく書けそう。

というのを、dein.toml に追加しました。

# Elixir
[[plugins]]
repo = 'elixir-lang/vim-elixir'

[[plugins]]
repo = 'ecpplus/vim-phoenix'

開発前の準備

  • Static Assets をビルドするデフォルトのツールが brunch なのだが、慣れてるのと情報が多そうなので webpack に変更
  • Templates を記述するのが EEx (like erb)がデフォルトなのだが、閉じタグ書きたくないので haml に変更

という2点をやった。

brunch -> webpack

package.json

Delete brunch-config.js, package.json and initialize package.json.

$ rm brunch-config.js package.json
$ npm init

Install npm packages. I want to use sass, Bootstrap4, es2015, React but anything is OK.

Then I got a package.json!

webpack.config.js

When I compile for production, run

$ NODE_ENV=production node_modules/webpack/bin/webpack.js -p

Don’t forget to put .babelrc.

config/dev.exs

Change watchers from brunch to webpack. npm start is defined in package.json. And add .haml to live reloading hook.

--- a/config/dev.exs
+++ b/config/dev.exs
@@ -11,9 +11,9 @@ config :chat, Chat.Endpoint,
   debug_errors: true,
   code_reloader: true,
   check_origin: false,
-  watchers: [node: ["node_modules/brunch/bin/brunch", "watch", "--stdin",
-                    cd: Path.expand("../", __DIR__)]]
-
+  watchers: [
+    npm: ["start", cd: Path.expand("../", __DIR__)]
+  ]

 # Watch static and templates for browser reloading.
 config :chat, Chat.Endpoint,
@@ -22,7 +22,7 @@ config :chat, Chat.Endpoint,
       ~r{priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$},
       ~r{priv/gettext/.*(po)$},
       ~r{web/views/.*(ex)$},
-      ~r{web/templates/.*(eex)$}
+      ~r{web/templates/.*(eex|haml)$}
     ]
   ]

web/static/css/app.sass

Delete app.css, phoenix.css(Bootstrap3). Then add app.sass like following.

I copied default _variables.scss for a template.

$ cp node_modules/bootstrap/scss/_variables.scss web/static/css/bootstrap_variables.sass

That’s it!

実装

コード全然整理されていないけれども、GitHub にコードを上げてあります。
(ecpplus/phoenix-framework-websocket-chat-example)[https://github.com/ecpplus/phoenix-framework-websocket-chat-example]

デプロイ

phoenix.server

Phoenix Framework の公式サイトにあったコマンドをほぼ持ってきただけだが、 Ubuntu で webpack のコンパイルが通らなかったので、 assets を手元で作ってからデプロイした。出来なかった理由は追ってない。

$ MIX_ENV=prod mix phoenix.digest

というので、 assets に hash 値をつけてくれる模様。 priv/static/ 以下に出力されているので、ここを webroot として扱えばOKなようだ。

アプリケーションサーバは、 MIX_ENV=prod mix phoenix.server で十分高速ということだったので、そのまま使ってる。

$ MIX_ENV=prod mix phoenix.server

とするとフォアグラウンドで立ち上がります。

$ MIX_ENV=prod PORT=4001 elixir --detached -S mix do compile, phoenix.server

とするとバックグラウンドで立ち上がります。

多分 elixir より堅牢な supervisor となる(ややこしい) ソフトウェアもない気もするので、これでとりあえずこのままにしてみようと思います。

nginx

TLS は Let’s Encrypt とかでサクッと用意して、それ以外の設定です。 WebSocket は /socket 以下につなぐようにしています。 proxy_http_version は 1.1 にしないと Nginx のエラーになりました。Nginxで動かす場合で調べると、書いてる人みんな同じ設定なので多分それで良いのだと思われるが、あまり調べてないです。 Static assets は Nginx で配信して、それ以外を phoenix.server という感じで動かしました。

負荷テスト

t2.nano インスタンスで、DBアクセスがないページに keepalive 付きで、100並列で20000回アクセスしてみた結果です。
アプリケーションの処理自体はほとんどないページなので、フレームワークが必ず行う処理の速度がこれくらいということだと思います。
この非力なサーバで1514.92リクエスト/秒も処理できてるので、しょぼいVPSでも十分実用的な速度が出そう。
この処理の間で、 beam のプロセスが CPU60% くらい使ってました、メモリはあまり変わらず3%くらいでした。

$ ab -k -c 100 -n 20000 "https://chat.ecpplus.net/"
This is ApacheBench, Version 2.3 <$Revision: 1706008 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking chat.ecpplus.net (be patient)
Completed 2000 requests
Completed 4000 requests
Completed 6000 requests
Completed 8000 requests
Completed 10000 requests
Completed 12000 requests
Completed 14000 requests
Completed 16000 requests
Completed 18000 requests
Completed 20000 requests
Finished 20000 requests


Server Software:        nginx
Server Hostname:        chat.ecpplus.net
Server Port:            443
SSL/TLS Protocol:       TLSv1.2,ECDHE-RSA-AES128-GCM-SHA256,2048,128

Document Path:          /
Document Length:        905 bytes

Concurrency Level:      100
Time taken for tests:   13.202 seconds
Complete requests:      20000
Failed requests:        0
Keep-Alive requests:    19829
Total transferred:      30119145 bytes
HTML transferred:       18100000 bytes
Requests per second:    1514.92 [#/sec] (mean)
Time per request:       66.010 [ms] (mean)
Time per request:       0.660 [ms] (mean, across all concurrent requests)
Transfer rate:          2227.94 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    5  53.5      0     915
Processing:    42   60  21.9     55     808
Waiting:       42   59  21.7     55     808
Total:         42   65  63.4     55     976

Percentage of the requests served within a certain time (ms)
  50%     55
  66%     57
  75%     59
  80%     60
  90%     66
  95%     89
  98%    162
  99%    303
 100%    976 (longest request)

keepalive なしだと、回線かクライアント側の問題で、サーバに全然負荷をかけられませんでした。
Nginx の CPU 使用が20%くらいで、beam はほとんど CPU 使ってない状態でした…。

~ ᐅ ab -c 100 -n 20000 "https://chat.ecpplus.net/"
This is ApacheBench, Version 2.3 <$Revision: 1706008 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking chat.ecpplus.net (be patient)
Completed 2000 requests
Completed 4000 requests
Completed 6000 requests
Completed 8000 requests
Completed 10000 requests
Completed 12000 requests
Completed 14000 requests
Completed 16000 requests
Completed 18000 requests
Completed 20000 requests
Finished 20000 requests


Server Software:        nginx
Server Hostname:        chat.ecpplus.net
Server Port:            443
SSL/TLS Protocol:       TLSv1.2,ECDHE-RSA-AES128-GCM-SHA256,2048,128

Document Path:          /
Document Length:        905 bytes

Concurrency Level:      100
Time taken for tests:   263.151 seconds
Complete requests:      20000
Failed requests:        0
Total transferred:      30020000 bytes
HTML transferred:       18100000 bytes
Requests per second:    76.00 [#/sec] (mean)
Time per request:       1315.753 [ms] (mean)
Time per request:       13.158 [ms] (mean, across all concurrent requests)
Transfer rate:          111.41 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:      148 1017 820.6    715   12429
Processing:    42  295 391.6    212   14791
Waiting:       42  235 373.8    160   14791
Total:        233 1313 918.9    991   16536

Percentage of the requests served within a certain time (ms)
  50%    991
  66%   1230
  75%   1468
  80%   1656
  90%   2233
  95%   3289
  98%   4166
  99%   4885
 100%  16536 (longest request)

実は ecpplus.net は t2.nano に置いてるのですが、動的なコンテンツを吐くの恐ろしすぎて WordPress や Sinatra で吐いてる HTML も全ページ Nginx のキャッシュにしてて、静的な以外配信してません。が、 Elixir なら割と行けそうな感じでした。メモリ的にも、フレームワークを起動しただけなら 10MBくらいしか消費しません。 Unicorn で動かしてるほぼ最小構成な Sinatra は6MBくらいなので、Phoenix が特に少ないわけではないかもしれないですが。

次にやりたいこと

  • AWS のサービスとの連携をやってみる
  • NoSQL との連携をやってみる
  • テストを書く
  • 良いデプロイ方法を調べる
  • iOSから叩く何かのAPIを作ってみる
Phoenix Framework で WebSocket のチャット

xcodeproj を CONFLICT しづらくする mergepbx

Xcode を使って開発していると、.xcodeproj が、論理的には CONFLICT していないのに Xcodeのバージョンアップ等で激しく CONFLICT することがある。 mergepbx を使うと、理不尽に CONFLICT されるケースをある程度解消してくれる。複数人で開発している時は入れておいて助かることが多い。

1年くらい使ってるけど、特に問題が起きたことはないです 🙂

Install

brew install mergepbx

Usage

以下の2ファイルに設定を追記(プロジェクト単位でもglobalでもOK)すれば、あとは git merge や git rebase とか通常の操作で mergepbx を使ってくれるようになります。

.gitconfig

下記を追加。

[merge "mergepbx"]
  name = Xcode project files merger
  driver = mergepbx %O %A %B

.gitattributes

下記を追加。

*.pbxproj merge=mergepbx
xcodeproj を CONFLICT しづらくする mergepbx

Awesome 某のスター付き一覧

Awesome Hoge のページをよく見ることがあり、特に iOS をよく見ていたのだが、スターの数をまとめて見たいと思い、主に自分用にスター付きの一覧を生成してみました。Awesome repositories の contributors と全てのレポジトリの developers の方々に感謝です。

URLはこれです。
Awesome repos with 🌟

GitHub API で取得した stargazers_count を使っています。 GitHub API、レポジトリの取得なら1時間に5,000回のLimitなので結構叩ける印象。

サーバは要らなそうだったので、AWSのサービスを組み合わせてサーバレスでページを生成しています。

  • Lambda でレポジトリ一覧を取得
  • まとめて GitHub API を叩くと Lambda がタイムアウトするので、一旦 SQS に JSON にしていれる。
  • CloudWatch Events で Lambda を定期的に起動して、 SQS のキューを受信しながら、 GitHub API を叩いて SimpleDB に結果を入れる
  • 定期的に、 SimpleDB のデータを Lambda で JSON にしてs3 に PUT する。

という風にしています。APIにも優しいように、1日かけて1ループするくらいの周期で処理をしています。

DynamoDB ではなく SimpleDB なのは、最後の処理で一括でデータを取得する必要があるのですが、DynamoDB はスループットの設定的に一括取得には向いていないので、課金的な意味で SimpleDB にしてます。無料枠を考えないと、ミニマムコストが SimpleDB はが$0で DynamoDB は$0.67なので。ただ、DynamoDBで読み書き1ずつにして、s3にPUTするときだけ scan しても、もしかしたら DynamoDB の方が安い、ということはあるかもしれません。この部分の比較は見たことがないというか、SimpleDBに関する情報をあまり見かけません。 Lambda からもデフォルトで使えるし、ちょっとした時に便利だと思うのですが、管理画面にも無いし、そのうちサービスが終わってしまうのではないかというのが心配です。

Lambda 開発環境は、最近知った node-lambda がシンプルで使いやすいです。手元での実行とデプロイが出来ます。

HTML側は、S3 + CloudFront です。最近 CloudFront が HTTP/2 に対応したので、個人的には静的ページの配信に HTTP/2 対応のサーバを立てる意味がほぼなくなりました。あと、React で描画してるので、対応してないブラウザでは見えないです。

JSON の URL はパブリックなので、直接叩いても良いです。見ればフォーマット何となくわかると思いますので、 jq とか使って絞り込めば、例えば 「Awesome iOSで、Buttonのカテゴリで、500スター以上付いているレポジトリを開く」とかもすぐ出来ます。

curl https://s3-ap-northeast-1.amazonaws.com/awesome-repositories/ios.json | gunzip | jq -r 'map(select(.category1 == "Button")) | map(select(.stargazers_count >= 500)) | "http://github.com/"+.[].path' | xargs open

※ 一気にブラウザ上でページが立ち上がるので、内容に注意して下さい!

最初 iOS だけ作ったら、他のものもほぼ同じコードで生成できたので、自分がよく使う言語だけですが、6個作りました。 Elixir 力を上げたい。

Awesome 某のスター付き一覧