そろそろISUCONということで、先日チームであつまって練習をしたところnginxでドハマリしたので復習した内容をまとめます。
やりたかったこと
ISUCONはほとんどがフロントにWEBサーバ(nginx)があり、裏にgunicorn等のAPサーバを配置した構成が多いようです。デフォルトはすべてのリクエストがAPサーバに流れているので一部の静的なページをnginxに任せたかったわけです。
その時の条件は以下でした。
- /へのアクセスはnginxに静的ファルを戻させる
- /xxx へのアクセス等、/以外はすべてgunicornにに引き渡す
- /?error=xの場合はxの値に応じた静的ファイルをnginxに戻させる
ネックになったのはgunicornに引き渡すルールが/app
みたいになっておらず同じ/
でも適用するディレクィブをわけなければいけなかった点です。
nginxの内部リダイレクト
nginxではindex
ディレクティブ等を使った場合、内部リダイレクトが発生します。以下を例とします。
# ディレクティブ1 location = / { add_header Content-type text/html; default_type text/html; index index.html; root /tmp/test/; } # ディレクティブ2 location / { proxy_pass http://app; }
気持ち的には、ディレクティブ1が適用され、/tmp/test/index.html
が戻されてほしいですが、実際は以下の流れになります。
- ディレクティブ1が適用される
- indexディレクティブにより/index.htmlへアクセスが書き換えられる
- 内部リダイレクトが発生し、/index.htmlとして再評価される
- ディレクティブ1にはマッチしないのでディレクティブ2にマッチし、APサーバに流れる
なお、/tmp/test/index.html
が実在しない場合は403が戻りますのでさらに混乱します。
root@denzow-ubuntu:/etc/nginx# ls -l /tmp/test/index.html -rwxr-xr-x 1 root root 23 10月 4 22:59 /tmp/test/index.html root@denzow-ubuntu:/etc/nginx# curl http://192.168.56.100/ i am bottle <br/> index.html -> APサーバが応答している
root@denzow-ubuntu:/etc/nginx# rm /tmp/test/index.html root@denzow-ubuntu:/etc/nginx# curl http://192.168.56.100/ <html> <head><title>403 Forbidden</title></head> <body bgcolor="white"> <center><h1>403 Forbidden</h1></center> <hr><center>nginx/1.10.3 (Ubuntu)</center> </body> </html> -> ファイルを削除すると403になる
結局、ファイルがあるかどうかは最初のlocationディレクティブでチェックし、そこで403が戻っているようです。
nginxでのクエリパラメータによる分岐
nginxではhoge?a=1&b=2
のようなクエリパラメータの場合は$arg_a
や$arg_b
として取得することができます。これにifディレクティブを組み合わせると以下のようになります。
if ( $arg_error = "1" ) { #任意のアクション } if ( $arg_error = "2" ) { #任意のアクション }
なお、if内ではalias 等使えないディレクティブもあるようです。
結局どうしたか
rewrite
ディレクティブを使うことにしました。このディレクティブはアクセスされたURLを書き換えるものですが、更にlocationの再評価をさせるかどうかを指定することが可能です。
rewrite ^(.*)$ /error1.html break;
このように末尾にbreak
をつけ場合はlocationの再評価をしないため、ここでアクセス先が確定します。これを利用し以下の様に設定しました。
: upstream app { server localhost:8080; } server { location = / { add_header Content-type text/html; default_type text/html; root /tmp/test/; if ( $arg_error = "1" ) { rewrite ^(.*)$ /error1.html break; } if ( $arg_error = "2" ) { rewrite ^(.*)$ /error2.html break; } rewrite ^(.*)$ /index1.html break; } location / { proxy_pass http://app; } } :
これで、以下のような動きになります。
URL | アクセス先 |
---|---|
/ | /tmp/test/index1.html |
/?error=1 | /tmp/test/error1.html |
/?error=2 | /tmp/test/error2.html |
/hoge | apサーバ宛 |
/foo | apサーバ宛 |
確認してみましょう。
root@denzow-ubuntu:/etc/nginx# ls -l /tmp/test/ 合計 12 -rwxrwxrwx 1 root root 24 10月 4 22:44 error1.html -rwxr-xr-x 1 root root 24 10月 4 22:44 error2.html -rwxrwxrwx 1 root root 24 10月 4 23:11 index1.html root@denzow-ubuntu:/etc/nginx# grep . /tmp/test/* /tmp/test/error1.html:static file error1.html /tmp/test/error2.html:static file error2.html /tmp/test/index1.html:static file index1.html
root@denzow-ubuntu:/etc/nginx# curl http://192.168.56.100/ static file index1.html root@denzow-ubuntu:/etc/nginx# curl http://192.168.56.100/?error=1 static file error1.html root@denzow-ubuntu:/etc/nginx# curl http://192.168.56.100/?error=2 static file error2.html root@denzow-ubuntu:/etc/nginx# curl http://192.168.56.100/hoge i am bottle <br/> hoge root@denzow-ubuntu:/etc/nginx# curl http://192.168.56.100/foo i am bottle <br/> foo
ちゃんと思った通りの動きになっていますね。
なお、nginxのifディレクティブは実運用では推奨されていないようです。あくまでISUCON等の限られた場で生きる知識として理解したいと思います。