|
|
||
Nginxは非常に強力なhttpdですが、独自のモジュールを実装しようとするとこれまた非常に敷居が高い印象です。
まず開発用のドキュメントはほとんどありません。必然 既存のモジュールをお手本としますが、コメントも少ないのでソースだけが頼りです。
{ファイル,ネットワーク} I/O を伴う処理では、Nginxのノンブロッキング/イベントドリブンのアーキテクチャにのっとってコールバックを駆使したCで実装する必要があり、LLで育ったゆとり脳では太刀打ちできませんでした
なんらかのNginxモジュールを開発しなければならない場合、lua-nginx-module が代わりにならないか検討してみましょう。
lua-nginx-module を使うと Luaのコードを通してNginxを制御できます。設定ファイルで扱える変数の定義/読み書きや、HTTPリクエスト(ヘッダ、ボディ)の操作、mysqld,memcached,redis といったストレージを組み合わせて使う方法もあります。
ここからは lua-nginx-module の中でも私自身が特に興味をひかれた機能について記します。日本語ブログで lua-nginx-moduleへの言及が少ないので、何らかの足しになればと思います。
また後半では Nginx + Lua + Redis を使用して動的にupstream(=バックエンド) を決めるリバースプロキシの実装例を取り上げます。
ngx.var.<変数名> で設定ファイルから参照できる変数を操作できます
error_log /dev/stderr debug;
events {
worker_connections 256;
}
http {
server {
listen 8888;
server_name localhost;
location / {
# 先に空文字で初期化しておかないと 起動時にシンタックスエラーを起こす
set $upstream "";
rewrite_by_lua '
ngx.var.upstream = "192.168.0.1"
';
proxy_pass http://$upstream;
}
}
}
proxy_pass で参照できる変数を定義してみました。なんらかのロジックを組み込めば動的に proxy_pass 先を決めことができますね。
後半でもう少し有意な使い方になる例を挙げてみます
ngx.location.capture() という、擬似的なHTTPリクエストを扱える関数が用意されています。lua-nginxモジュールの中もかなり熱い関数です。
worker_processes 1;
error_log /dev/stderr debug;
events {
worker_connections 256;
}
http {
server {
listen 8080;
server_name localhost;
location /1 {
internal;
proxy_pass http://127.0.0.1:10080/1;
}
location /2 {
internal;
proxy_pass http://127.0.0.1:10080/2;
}
location /3 {
internal;
proxy_pass http://127.0.0.1:10080/3;
}
location / {
content_by_lua '
local res1 = ngx.location.capture("/1")
ngx.say(res1.body)
local res2 = ngx.location.capture("/2")
ngx.say(res2.body)
local res3 = ngx.location.capture("/3")
ngx.say(res3.body)
';
}
}
}
上記の設定でNginxを起動すると 1,2,3 の順番でコンテンツを返します。
一見 ngx.location.capture() の箇所でブロックしてしまう記述に見えますが、コルーチンを用いて同期的なインタフェースをしつつノンブロッキング で処理してくれるような実装になっています。
また ngx.location.capture() は HTTP GET を飛ばすインタフェースを取っていますが、実際はHTTPリクエストを生成している訳でなく 全てNginxの内部で完結する処理になっており、トラフィック/IPC(プロセス間通信) の類いは発生しません。そのため ngx.location.capture("http://example.com") のようにして他のホストにリクエストする使い方はできないのですが....
ngx.location.capture_multi という関数も用意されており、こちらは複数のリクエストを同時に発行できます。
(ここらへんの仕様は githubのドキュメントに書かれていますので、是非一度読んで見てください)
ngx.location.capture()は、mysql,redis,memcachedなどのストレージを組み合わせる事でより強力な使い方ができます
NginxがリクエストのHostヘッダを見て、動的にリバースプロキシする先を決める実装案です (ルーター/リクエストルーターなどの呼び名がありますが、どれがデファクトか分からないのでダイナミックリバースプロキシ と呼んでいます )
図式すると下記のような構成になります
ホスト名 -> IPの名前解決だけであればローカルネットワーク専用のDNSを立てるなどして解決できそうですが、IPに加えてポート番号の解決も必要なため Luaを通してRedisに問い合わせます。
ホスティングサーバーのような大量のドメイン(バーチャルホスト) が任意のタイミングで追加/削除される環境や、1ユーザーごとにhttpdを起動して複数のポート番号を管理するサービスの場合、 このようなリバースプロキシの利用価値が高くなります。
( 私が勤める paperboy&co. のサービス ロリポップでは、Apache で mod_rewrite + MySQL + memcached を組み合わせたモジュールを作成し、ホスティングサーバ用リバースプロキシとして運用されています )
さて、リバースプロキシの動作についてです。
上記を 先に挙げた ngx.var.<変数名> と ngx.location.capture() を組み合わせてLuaのコードに落とし込みます。
Redisを使ってるのに特別な意味はなく、やってみたかっただけ、です。
使用するモジュールは ↓ の通り。
以下が設定(実装)例となります
worker_processes 1;
error_log /dev/stderr debug;
events {
worker_connections 256;
}
http {
server {
listen 8888;
server_name localhost;
location / {
set $upstream "";
rewrite_by_lua '
local res = ngx.location.capture("/redis")
if res.status == ngx.HTTP_OK then
ngx.var.upstream = res.body
else
ngx.exit(ngx.HTTP_FORBIDDEN)
end
';
proxy_pass http://$upstream;
}
# HostヘッダをキーにしてRedisに問い合わせ
location /redis {
internal;
set $redis_key $host;
redis_pass 127.0.0.1:6379;
default_type text/html;
}
}
}
Redisの操作も含めた設定は https://gist.github.com/1670088 にメモってあります
実際に運用する場合はキャッシュやエラーハンドリングを厳密に詰める必要があるでしょう。上記は説明を簡単にするためのプロトタイプ実装として見てください。Redisだけでなく、MySQL(libdrizzle) や memcached などを複数組み合わせる方法も有効でしょう。
「memcached とか MySQL とかにプロキシするNginxモジュールって何の役に立つんだ!? 」とか思ってたのですが、Luaとの組み合わせを見て世界がひっくりかえったような衝撃を受けました。
lua-nginx-module には認証を操作するAPIもあります。ペパボの 30days album では Perlbal + memcached で画像リクエストの認証を制御していますが、lua-nginx-module + {memcached,redis,mysqld} でも同様の機能を実装できそうだなと思いました。
ところで Apacheでも mod_lua というモジュールが提供されていますが 、Apacheの内部APIへのアクセスが限定的で 後一歩のところで使い勝手がよくない印象でした。その点 lua-nginx-module はよくできた子だなーと。
あとあと、設定ファイルにロジックが紛れ込むことに抵抗がある方もいるとは思います。ただしNginxモジュールを作成して管理するコストがパないので、天秤にかけた場合 多少の見通しの悪さは黙認できるのではないでしょうか。(...Luaの部分だけ外部ファイル化もできます)
sudo brew install redis sudo brew install lua wget http://nginx.org/download/nginx-1.0.11.tar.gz tar xvfz nginx-1.0.11.tar.gz cd nginx-1.0.11 git clone https://github.com/chaoslawful/lua-nginx-module.git wget http://people.FreeBSD.org/~osa/ngx_http_redis-0.3.5.tar.gz tar xvfz ngx_http_redis-0.3.5.tar.gz ./configure --add-module=lua-nginx-module --add-module=ngx_http_redis-0.3.5 --prefix=/usr/local/nginx-1.0.11/ make sudo make install
私物のMacBook Pro (13inch) , MacBook air (11inch) と、会社で私用している MacBook Pro (15inch) のバッテリー容量を 半年程 はてなグラフに数値を投稿してモニタリングしていたので 公開します。
Macのバッテリーに関する情報は /usr/sbin/ioreg を実行して取得できます。
バッテリーの最大容量となる数値は ioreg -l | grep MaxCapacity で得られる値を利用したらよいようです 。
(この数値は 「このMacについて」>「詳しい情報」 > 「電源」でも確認できます )
ということで 毎日のcronで ioregのMaxCapacityの値を取得はてなグラフに投稿、という形式で半年ほどモニタリングしました。
#!/usr/bin/perl use strict; use warnings; use LWP::UserAgent; use DateTime; use Mac::IORegistry::Battery; my $mac = shift || die; my $battery = Mac::IORegistry::Battery->get; my $ua = LWP::UserAgent->new; $ua->credentials('graph.hatena.ne.jp:80', '', 'hiboma', 'ひみつのぱすわーど'); my $res = $ua->post( 'http://graph.hatena.ne.jp/api/post', { graphtype => 'bars', graphname => "${mac}::Battery", date => DateTime->now->ymd, value => $battery->{MaxCapacity}, }); warn $res->content unless $res->code == 201;
http://developer.hatena.ne.jp/ja/documents/graph/apis/rest に登録されているコードのまんまです
Mac::IORegistry::Battery は拙作のモジュールで /usr/sbin/ioreg -r -n AppleSmartBattery の出力をハッシュに変換するだけの単純コードです。コードは github に置いてあります。
使用しているOSバージョンは全て Snow Leopard です。(未だにLion導入してないので...計測していません)
半年程計測して下記のようなグラフが得られました。(縦軸の単位は mAh )
グラフの赤線は ioregで得られる DesignCapacity という値です。 設計上の最大容量をさすようです。
いずれのグラフでも時間が経つにつれてバッテリーの容量が減っていますね
常時電源ケーブルをつけている (A)のMacは値の変動がほぼありません。容量が急激に減っている時期がありますが、一時期サーバーとしての使用を止めて起動していない時期でした。
(B)と(C)のMacは 値の変動激しいですね。持ち運びして充電ケーブルをつけていない環境での使用時間も多いです。一時的に値が回復している時期もありますが、長期的に見ると下降しています。
Appleが書いている http://www.apple.com/jp/batteries/notebooks.html によると
Appleは、ノートブックを電源コンセントに常時接続しておくことを推奨していません。
理想的なのは、通勤の電車の中でノートブックを使い、オフィスで充電のために電源につなぐといった利用方法です。
このような使い方をすると、バッテリー液が流動している状態に保てます。
一方、オフィスでデスクトップコンピュータを使っていて、ノートブックは旅行や出張で時々使うだけ、という場合は、毎月最低1回はバッテリーを充放電することをお勧めします。
という使い方が良いようです。これらの手順に従って管理していれば 容量がヘタるのを抑えられたかもしれません。(減ってもあんまり気にしてないんですが)
また、エントリをまとめるにあたって調べ直して分かったのですが CycleCount などの値で充電/放電の回数も取れるようですね。こちらも合わせて取っておけばよかったなと。グラフの値が上下しているタイミングと照らし合わせることで 面白い結果になったかも。