nginx
nginxDay 1

nginxにおけるmapとその応用

More than 1 year has passed since last update.

nginxの設定ファイルであるnginx.confでは変数が利用できます。

set $x_hoge_header hoge;

proxy_set_header X-Hoge-Header $x_hoge_header;

また、ifディレクティブによる条件分岐処理も利用できます。

if ($request_method = POST) {

# ...
}

これらを利用すると例えばUser-Agent等のヘッダ情報を元に、変数の値を代入して利用することができます。

set $device "pc";

if ( $http_user_agent ~ iPhone) {
set $device "iphone";
}
if ( $http_user_agent ~ Android) {
set $device "android";
}
proxy_set_header X-Device $device;

このように特定の条件毎に変数の値を代入して各種ディレクティブの動作を変更する、といったニーズは現実のシステムでは結構あります。ただ、ifディレクティブで分岐させるような設定はせいぜい数個くらいが限界でしょう。

また、nginxのifディレクティブはネストや条件の複数指定ができないのでちょっと凝ったことをしようとすると設定がかなり複雑になりますし、If is Evilという言葉があるくらいnginxではifを多用するスタイルはあまり推奨されていません。

こういった場合に便利なのがmapディレクティブです。


mapディレクティブ

mapディレクティブは特定の変数の値を別の変数の値に応じて設定できる機能です。例えば、さきほどUser-Agentの値に応じて変数$deviceの値を動的にセットしていましたが、

set $device "pc";

if ( $http_user_agent ~ iPhone) {
set $device "iphone";
}
if ( $http_user_agent ~ Android) {
set $device "android";
}

これはmapディレクティブを使うと次のように書けます。

map $http_user_agent $device {

default pc;
~iPhone iphone
~Android android;
}

大分シンプルになりました。ほかにもVPNのIPアドレスかどうかのフラグとして利用したり、

map $remote_addr $vpn {

default 0;
203.0.113.125 1;
}

Hostヘッダに応じてアップストリームを切り替える、といった設定も容易に記述可能です。

map $http_host $backend {

default 192.0.2.10;
~storage1\d.example.com 192.0.2.11;
~storage2\d.example.com 192.0.2.12;
~storage3\d.example.com 192.0.2.13;
}

server {
# ...
server_name *.example.com;
location / {
proxy_pass http://$backend;
}
}

このようにnginxではmapをうまく使うことでシンプルで柔軟な設定ファイルを作成できます。

なお、mapディレクティブはhttp、streamの両コンテキストで利用可能ですが、stream向けのmapディレクティブは比較的最新のnginx-1.11.2以降でないと使えない点に注意しましょう。


mapを応用したモジュール

nginxには以下のようにmapを応用したモジュールがあります。


ngx_http_geo_module

ngx_http_geo_moduleはIPアドレス(とサブネットマスク)を元に変数を生成するモジュールです。mapでは文字列と正規表現によるマッチングで変数を生成していましたが、geoだとIPアドレスとサブネットマスクを元に変数の値を割り当てることができます。こんな風に。

geo $remote_addr $is_allowed_ip_range {

default 0;
127.0.0.1 1;
192.0.2.0/24 1;
}


ngx_http_split_clients_module

ngx_http_split_clients_moduleは特定のパラメータの割り振りをパーセンテージ単位で調節出来るモジュールです。A/Bテストなんかで使うのを想定しているみたいです。例えばURLのクエリストリングを元に振り分けるのであればこんな感じで書けます。

split_clients "${arg_user_id}" $variant {

10% special;
* normal;
}


さいごに

nginxの変数とifディレクティブはちょっとしたことをやる分には十分ですが、分岐や変数の代入が増えてくると設定ファイルがかなり汚くなります。mapやgeoを利用するとそれなりに大がかりな仕組みでも比較的シンプルに実現できたりするのでオススメです。