Apache

nginx + httpd + mod_rpaf

とあるウェブサービスのサーバー負荷状況が思わしく無く、非常に重い状態が続いていました。
そこで話題の高速ウェブサーバーの nginx を利用してみました。

構成は次の通り。(同サーバー上)

フロントエンド(nginx) ... 主に画像処理
バックエンド(httpd) ... アプリケーション

まずは、nginx を 80番 で、httpd を 8080番 で設定して開通。
もちろん、アクセス元IPを記録するので mod_rpaf を httpd 側に設定。

mod_rpaf の設定は次の通り。

1. ファイルのダウンロード
% cd /usr/local/src
% wget http://stderr.net/apache/rpaf/download/mod_rpaf-0.6.tar.gz

2. ファイルを解凍
% tar xvfz mod_rpaf-0.6.tar.gz

3. ディレクトリを移動
% cd mod_rpaf-0.6

4. MakeFileの編集
APXS2にapxsのパスを入れる
% vi Makefile
#APXS2=$(shell which apxs2)
APXS2=/usr/sbin/apxs

5. コンパイル
% make rpaf-2.0
% make install-2.0

6. httpd.confの編集
以下を追記
% vi /etc/httpd/conf/httpd.conf
--
LoadModule rpaf_module modules/mod_rpaf-2.0.so
RPAFenable On
RPAFsethostname off
RPAFproxy_ips xxx.xxx.xxx
※綺麗にまとまっていたので、mod_rpafをインストールし、リバースプロキシのローカルのIPを取得しないようにする を参照。

しかし、ここで問題発生。
一向に外部IPが記録されず、mod_rpaf が機能していない...。

そういえば、フロントは nginx なんだということを思い出し、proxy 関連の設定についてググる。
解決方法がありました。次の設定を nginx.conf に入れておきましょう。

proxy_set_header    X-Real-IP       $remote_addr;
proxy_set_header    X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header    Host            $http_host;
これでアクセス元のIPをバックエンド側でも取得できるようになりました。

さて、これでサーバーの負荷が下がると思いきや、中々状況が改善されません。
詳細調査の為に、バックエンド側で /server-status を確認していると何やら OPTIONS の文字が。
まさか、TRACE 関連の脆弱性を突かれているのかと思ったのですが、そもそもフロントエンドの nginx は何をしているんだと。調べてみたところ、nginx は TRACE method に関しては、問答無用で拒否していることが分かりました。

if (r->method & NGX_HTTP_TRACE) {
    ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
                  "client sent TRACE method");
    ngx_http_finalize_request(r, NGX_HTTP_NOT_ALLOWED);
    return NGX_ERROR;
}
【ソース解析】アクセス受けつけ5 参照

ですが、telnet を利用して会話してみると、OPTIONS が素通りして、バックエンドに行ってしまっているでは無いですか...。

httpd のログで言うとこれですよ、これ。

"OPTIONS * HTTP/1.0" 200 - "-" "Apache/2.2.17 (EL) (internal dummy connection)"


仕方が無いので、nginx 側で request method を制限することにしました。

if ($request_method !~ ^(GET|HEAD|POST)$) { return 444; }
というわけでサーバーの高負荷状態も収まり、一件落着です。

いいね! nginx 。

httpd proxy server と php の file_get_contents()

自分でプロキシサーバーを立て、そのプロキシ経由でウェブアクセスさせる方法です。

まずはプロキシサーバーの立て方ですが、これは httpd.conf に

<IfModule mod_proxy.c>
    ProxyRequests On
</IfModule>

<Proxy *>
    Order deny,allow
    Deny from all
    Allow from ****
</Proxy>
とすることで完成です。
もちろん悪用されないようにアクセス制限はしっかりとしておきましょう。

次にプログラム側ですが、今回は php の file_get_contents を利用します。
ググると沢山出てきますが、プロキシ経由で file_get_contents するには、コンテキストリソースを渡してあげる必要があります。

分り易く冗長的に書くと次のような形です。

$url = 'http://example.com/hoge/fuga/';
$proxy = '***.***.***.***:8080';

$contextParam = array(
        'http' => array(
                'method' => 'GET',
                'proxy'  => "tcp://{$proxy}",
                'request_fulluri' => true,
        )
);
$context = stream_context_create($contextParam);
$contents = file_get_contents($url, false, $context);
これで取得できますが、そもそも 'request_fulluri' => true って何でしょう?
答えは こちら にあります。

request_fulluri boolean
TRUE を指定すると、リクエストを生成する際に完全な URI (GET http://www.example.com/path/to/file.html HTTP/1.0) が用いられます。これは標準のリクエストフォーマットではありませんが、 このようなフォーマットを要求するプロキシサーバも存在します。

デフォルトは FALSE です。

と言っても何のことやらという方もいらっしゃるかと思いますが、このパラメータを付けた場合と付けない場合で、プロキシ側のアクセスログがどう変化するかを見れば一目瞭然です。
※上記の方法で設定した自前プロキシサーバーに対するリクエストです。

■'request_fulluri' => true の場合

"GET http://example.com/hoge/fuga/ HTTP/1.0"

■'request_fulluri' => false の場合

"GET /hoge/fuga/ HTTP/1.0"

以上!

つまり、プロキシサーバー側の挙動によって、完全な URI でなければダメだということですね。
一般に公開されている多くのプロキシサーバーでは、 request_fulluri が false でも問題無く取得出来るので、httpd で仕立てたプロキシサーバーでは何故、完全な URI が必要なのかまでは検証していません。

ご存知の方は是非。

mod_proxy_balancer と stickysession と 携帯セッション

mod_proxy_balancer と stickysession については気を付けないといけないことが以前より指摘されているので問題になるケースは減少していると思います。(以下のエントリー参照)

mod_proxy_balancer の stickysession についてAdd Star


ProxyPass / balancer://cluster/ stickysession=ROUTE_SESSIONID nofailover=On
ProxyPassReverse / balancer://cluster/
<Proxy balancer://cluster/>
  BalancerMember http://127.0.0.1:3000 route=w1 retry=3
  BalancerMember http://127.0.0.1:3001 route=w2 retry=3
</Proxy>

に対して

SetEnvIf Cookie "ROUTE_SESSIONID=[^;]+" HAS_ROUTE_COOKIE
Header add Set-Cookie "ROUTE_SESSIONID=route.w1" env=!HAS_ROUTE_COOKIE

でOKなのは有名な話。

ただ、これで安心していてはいけません。
なぜなら携帯はクッキーを美味しく頂くことができないからです。

つまりロードバランサーで「誰々はこっちに行け」と指示を出そうにもこの「誰々」の部分が分からないわけですから、どのサーバーに飛んでいくのかは未知数なのです。

こんな時に PHP の設定で

session.save_handler

を files としていたらアウトです。
そりゃそうです。file でセッション管理をするのであれば、最初のアクセスでサーバーAにセッションファイルが生成されても、次のリクエストでサーバーBに振られてしまったらセッションファイルが存在しないので、当然セッションが切断されてしまいます。

ここでの対応方法は幾つかあると思いますが、サーバー屋的に解決するのであればセッションの維持を memcached にさせるか、セッションファイルが置かれるディレクトリを nfs 等で同期する方法でしょうか。

プログラマ的な解決方法は session.save_handler を user として、DBを利用して自前でセッションを管理する方法です。

これについては

PHP:セッション情報をDBに持つ



で詳しく書かれていますが、最近配布されているパッケージではこれをしっかりやってくれているものもありますね。例えば EC-CUBE がそうでした。

皆さんもロードバランサーを挟んだ場合は PC 側のセッション管理ばかりに気を取られないよう十分に気を付けましょう。



記事検索
Twitter
livedoor プロフィール
QRコード
QRコード
  • ライブドアブログ