目的
ngx_mrubyを使ってバックエンドのコンテナを切り替えることができるのかやってみました。
dockerを使いたいと思いつつも、ダウンタイムなしでdeployするにはこうしておけばよい、
という簡単な方法がないようでした。この方法は汎用的に使えるのではないかと思います。
サンプルを動かす
一式をGithub(https://github.com/muk-ai/rest-api) に置きました。
network機能を使うので docker >= 1.10, docker-compose >= 1.6 でお試しください。
git clone https://github.com/muk-ai/rest-api.git
cd rest-api/
git checkout 1f978500008e2126fdce2b31b1d4e189520e069b
docker-compose build
docker-compose up -d
ブラウザでport 80にアクセスすると、{"messages": "hello, world"} が表示されます。
ここから、deploy.shでコンテナをすり替えてみます。
sh deploy.sh mukai/rest-api:goodbye
port 80にアクセスすると、 {"messages": "goodbye, world"} が表示されるようになりました。
nginxの渡し先コンテナが切り替わったためです。
ロールバックもコンテナの切り替えで行えます。
sh deploy.sh restapi_falcon
ロールバックはコンテナイメージをpullする時間がないので早いです。
ダウンタイムはあるのか?
wrkでアクセスをかけつつ、バックエンドのコンテナのstop/startをかけた場合
$ ./wrk -c300 -d10s -H"User-Agent: strees-test" --latency --timeout 10 http://172.31.26.80/
Running 10s test @ http://172.31.26.80/
2 threads and 300 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 738.10ms 1.37s 7.88s 87.79%
Req/Sec 796.77 221.40 1.35k 81.82%
Latency Distribution
50% 64.22ms
75% 832.17ms
90% 2.57s
99% 6.44s
14137 requests in 10.01s, 2.70MB read
Non-2xx or 3xx responses: 315
Requests/sec: 1412.04
Transfer/sec: 275.74KB
315回のエラー(Non-2xx or 3xx responses)が出てしまいました。
次に、wrkでアクセスをかけつつ、deploy.shでコンテナを切り替えた場合。
$ ./wrk -c300 -d10s -H"User-Agent: strees-test" --latency --timeout 10 http://172.31.26.80/
Running 10s test @ http://172.31.26.80/
2 threads and 300 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 629.61ms 1.38s 9.43s 90.19%
Req/Sec 792.55 196.76 1.34k 63.32%
Latency Distribution
50% 66.83ms
75% 472.64ms
90% 1.91s
99% 6.90s
15750 requests in 10.01s, 2.97MB read
Requests/sec: 1573.40
Transfer/sec: 303.44KB
エラーが出ませんでした( ̄ー ̄)ニヤリ
要所(nginx.conf)の説明
location / {
resolver 127.0.0.11 valid=2s;
mruby_set_code $backend '
cache = Cache.new :namespace => "nginx"
cache["proxy_dest"]
';
proxy_pass http://$backend:3031;
}
リゾルバの指定が127.0.0.11
になっているのは、dockerネットワークにいる他コンテナの名前解決のためです。
(ビックリしますが、127.0.0.1
以外のループバックアドレス宛にDNSを引くとコンテナの名前解決ができる)
cache["proxy_dest"]
の中にバックエンドのサーバーを入れておきます。
この変数は↓のようにして初期化しています。
mruby_init_code '
# mruby-cache
cache = Cache.new :namespace => "nginx"
cache["proxy_dest"] = "falcon"
';
Cacheクラスを使っているのはnginxプロセス全体で値を共有したいため。
falcon
というのはアプリケーションコンテナの名前です。
この名前を127.0.0.11
に問い合わせると、IPが返ってきます。
location /container/switchover {
allow 172.16.0.0/12;
deny all;
mruby_content_handler_code '
v = Nginx::Var.new
if v.arg_dest.nil?
Nginx.errlogger Nginx::LOG_ERR, "Invalid parameter #{v.arg_dest}"
Nginx.return Nginx::HTTP_BAD_REQUEST
end
cache = Cache.new :namespace => "nginx"
cache["proxy_dest"] = v.arg_dest
Nginx.echo "ack"
';
}
/container/switchover
というpathで、宛先コンテナの制御を行います。
v.arg_destでdestリクエストパラメータを取得し、その値でcache["proxy_dest"]
を更新します。
curl http://localhost/container/switchover?dest=$container_id
のようにすることで、nginxの向き先を切り替えます。
deploy.shでは次のことをやります。
① コンテナイメージのpull
② コンテナの起動とコンテナIDの取得
③ /container/switchover
を叩いてコンテナ切り替え
既知の問題
-
nginxが太る (critical) - 旧コンテナの廃棄処理がない
nginxが太るのはmrubyの書き方に問題があるものと思います。負荷テストにかけると丸丸と太っていきます。
wrkをだいたい30秒くらいかけると1GBあったメモリが尽きます。識者の方、アドバイス頂けると幸いです・・・。
また、旧コンテナをそのままにしているのでdeployの度にコンテナが増えていきそのうち破綻すると思います。
追記
2016/04/11
@matsumotoryさんからアドバイスをもらい、Nginxのメモリリークについては解決しました。
https://github.com/muk-ai/rest-api/pull/10/files
ありがとうございました。m(__)m
ところで、wrkを長時間かけられるようになって、別の問題(高負荷時にしばらくパケットが落ちる)が発見されました。
docker networkの問題であるのか、uwsgiの問題であるのか未だ切り分けできていません。
→ この問題はnginxとuwsgiの間をhttpで繋ぐのをやめたところ見なくなったので、追うのをやめました。