あるイントラネット用の Web アプリケーションのアップグレードを試していて、次のようなことをしたい場合があった。
- 特定の IP からのアクセスでは次バージョンのサイトに接続する (試験ユーザ)
- それ以外の IP からのアクセスは現バージョンのサイトに接続する (通常ユーザ)
ローカルホストで試験するという手段もあるが、
A. ホスト名に依存する処理がある (外部の認証機構からリダイレクトする、など)
B. HTTPS 限定のサイトである (このために証明書等をローカルホストに移すのは手間かつセキュアでない)
C. 複数人のアクセスを想定している (ので人数分の /etc/hosts を書き換えるのも容易ではない)
といった条件もあり「リバースプロキシ側 (nginx) でアクセス元の IP レンジに応じて Web アプリケーションを切り替える」という方針で進めることにした。なおかつ、なるべく手軽にやりたかった。
内容はとてもシンプルだけど、すぐ忘れそうなので、備忘録として残しておく。
こう書いた
今回の要旨と関係のないディレクティブは全て省略した。
登場人物は 3 つ。
普通のユーザに見せたい方のサイトを usual_server
, 試験者のサイトを testing_server
と定義した。
upstream usual_server {
server 127.0.0.1:8080;
}
upstream testing_server {
server 127.0.0.1:8081;
}
geo $maintenance {
default 0;
# 許可したい IP レンジ
192.168.100.105/32 1;
}
server {
if ($maintenance = 0) {
set $target_proxy_server "usual_server";
}
if ($maintenance = 1) {
set $target_proxy_server "testing_server";
}
location / {
proxy_pass http://$target_proxy_server;
}
}
あとは
$ sudo service nginx configtest && sudo service nginx reload
なぜこのような書き方にするか
If Is Evil にも書かれているように、 nginx では location context
内での if はうまく処理できない問題がある。
下記は動作しない。
if ($maintenance = 1) {
location / {
proxy_pass http://$testing_server;
}
}
if ($maintenance = 0) {
location / {
proxy_pass http://$usual_server;
}
}
また、 nginx の if
には else
に対応するものがない。
「複雑なことは if
で制御しようとせず、なるべくなら使わない、使っても set
くらいにする」と捉えておけば問題ないはず。