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を作ってみる