oinume journal

Scratchpad of what I learned

Apacheでリバースプロキシ(mod_proxy)を活用する(3) - 負荷分散編

前回のApacheでリバースプロキシ(mod_proxy)を活用する(2) - 設定編ではフロントエンドのリバースプロキシとバックエンドのCGIサーバの設定を行ないました。今回は、サイトの負荷が大きくなってきた時にバックエンドのサーバを複数台にする設定を説明します。イメージ図としては下記のような感じです。

リバースプロキシでの負荷分散のイメージ

バックエンドの準備

まずは負荷分散が確認できるようにバックエンドのサーバを2台に増やします。前回作成した /etc/apache2/httpd_backend_8080.conf をコピーして /etc/apache2/httpd_backend_8081.conf を作成し、このhttpd_backend_8081.confがListenするポートを以下のように8080から8081にします。


--- httpd_backend_8080.conf 2009-04-07 00:32:55.000000000 +0900
+++ httpd_backend_8081.conf 2009-04-07 00:36:09.000000000 +0900
@@ -1,5 +1,5 @@
#
-# /etc/apache2/httpd_backend_8080.conf
+# /etc/apache2/httpd_backend_8081.conf
#
User www-data
Group www-data
@@ -12,15 +12,15 @@
LoadModule mime_module /usr/lib/apache2/modules/mod_mime.so

ServerRoot "/etc/apache2"
-Listen 8080
-ServerName localhost:8080
-PidFile /tmp/httpd_backend_8080.pid
+Listen 8081
+ServerName localhost:8081
+PidFile /tmp/httpd_backend_8081.pid
ServerAdmin example_at_example.com
DocumentRoot "/var/www/html"
TypesConfig /etc/mime.types
LogFormat "%h %l %u %t "%r" %>s %b "%{Referer}i" "%{User-Agent}i"" combined
-ErrorLog /tmp/error_8080.log
-CustomLog /tmp/access_8080.log combined
+ErrorLog /tmp/error_8081.log
+CustomLog /tmp/access_8081.log combined
UseCanonicalName Off
AddHandler cgi-script .pl

ポートを変更したら、下記のコマンドでこの8081でListenするApacheを立ち上げておきます。

$ sudo /usr/sbin/apache2ctl -f /etc/apache2/httpd_backend_8081.conf -k start

8080のサーバも停止しているようであれば起動しておきましょう。

$ sudo /usr/sbin/apache2ctl -f /etc/apache2/httpd_backend_8080.conf -k start

リバースプロキシで負荷分散の設定

バックエンドのサーバを2台に増設したら、その2台にリクエストを振り分けられるようにフロントエンドのリバースプロキシを設定し直します。やり方は色々ありますが、まずは一番シンプルな「リクエストをランダムに2台に振り分ける」というやり方を試してみたいと思います。

前回の設定では

RewriteEngine on
RewriteRule ^/(.+).pl$ http://localhost:8080/index.pl [L,P,QSA]
RewriteRule ^/(.+).(gif)$ /var/www/html/$1.$2 [L,QSA]

というように、.plファイルへのアクセスを localhost:8080 に振り分ける設定でした。今回はこの部分を変更して、localhost:8080とlocalhost:8081に振り分けるように設定します。具体的には、前回のhttpd.confに対して

RewriteMap server rnd:/usr/local/httpd_proxy_2.2.11/conf/server.txt

という設定をRewriteRuleの前に追加し、RewriteRuleを下記のように変更します。


RewriteRule ^/(.+).pl$ http://localhost:8080/index.pl [P,QSA]
↓
RewriteRule ^/(.+).pl$ http://${server:backend}/index.pl [L,P,QSA]

つまり前回のhttpd.confの設定との差分は下記のようになります。


--- httpd.conf.entry246 2009-04-12 17:22:07.000000000 +0900
+++ httpd.conf 2009-04-12 17:29:49.000000000 +0900
@@ -421,6 +421,7 @@
RewriteEngine on
-RewriteRule ^/(.+).pl$ http://localhost:8080/index.pl [P,QSA]
+RewriteMap server rnd:/usr/local/httpd_proxy_2.2.11/conf/server.txt
+RewriteRule ^/(.+).pl$ http://${server:backend}/index.pl [L,P,QSA]
RewriteRule ^/(.+).(gif)$ /var/www/html/$1.$2 [L,QSA]

httpd.confを書き換えたら、RewriteMapで指定した /usr/local/httpd_proxy_2.2.11/conf/server.txt というファイルを作成します。

# echo 'backend localhost:8080|localhost:8081' > /usr/local/httpd_proxy_2.2.11/conf/server.txt

これらの設定は何を意味するかというと

RewriteRuleで指定したパターンにマッチするリクエストがあった場合、RewriteMapで定義したサーバのどれか1台にランダムでリクエストを転送する

ということです。RewriteMapというディレクティブでは /usr/local/httpd_proxy_2.2.11/conf/server.txt というファイルを指定して、そのファイルの中身は

backend localhost:8080|localhost:8081

となっています。ここで定義したバックエンドのサーバをその後ろのRewirteRuleで ${server:backend} として参照しているわけです。前回の設定では1台にしかリクエストを転送していませんでしたが、このような設定をすることで「どれか1台にランダムで」という形で負荷分散することが可能になります。

というわけで、設定が完了したら mod_proxyサーバを再起動します。

$ sudo /usr/local/httpd_proxy_2.2.11/bin/apachectl -k restart

動作確認

サーバが起動したらブラウザから http://example/index.pl に何回かアクセスしてみましょう(exampleの部分は自身のホスト名に適宜変更してください)。/tmp/access_8080.logと/tmp/access_8081.log の両方に下記のようなログが残っているはずです。

127.0.0.1 - - [12/Apr/2009:17:46:35 +0900] "GET /index.pl HTTP/1.1" 200 129 "-" "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; ja-JP-mac; rv:1.9.0.8) Gecko/2009032608 Firefox/3.0.8"

これが確認できれば localhost:8080 と localhost:8081 の2台のバックエンドのサーバでの負荷分散環境が構築できたということになります。(なお、例によってそのままの設定だとセキュリティに問題があるので、作業が終了したら起動したApacheのプロセスは止めておくことをお奨めします)

まとめ

mod_proxyを使った負荷分散環境の構築は、バックエンドのサーバさえしっかり準備ができていればリバースプロキシの設定はかなり簡単だったのではないでしょうか。このような複数台のバックエンドのサーバにリクエストを振り分けるような設定は負荷分散としても有効ですが、その他の使い道として「バックエンドのサーバを1台メンテナンスで一時的に止めたい」というような時にサイトを止めることなくメンテナンスが可能になります。(具体的には、server.txtからメンテナンス対象のサーバを削除するだけでリクエストが転送されなくなります)

このように、リバースプロキシの用途は単純な負荷分散以外にもあるので、設定の仕方を知っておくだけでサイトの運用に非常に役立つと思います。

Apache + mod_fcgid + tDiaryでrubyプロセスが残りっぱなし

古いおいぬま日報(不定期)tDiaryなのですが、Ubuntu 8.10にしてからどうも調子が悪くて悩んでいます(*1)。というのは、tDiaryapacheのmod_fcgid で動かしているのですが、CPU使用率100%近くなっているFastCGIrubyプロセスがずっと残りっぱなしになっているようなのです。おかげで無駄にCPUリソースを食って非エコ状態になっています。

*1) 環境はUbuntu 8.10 + Apache 2.2.11 + mod_fcgid 2.2 + tDiary 2.2.0

 

apacheのエラーログには

 

[Mon Feb 16 20:17:26 2009] [notice] mod_fcgid: process /home/kazuhiro/www/diary/htdocs/index.fcgi(30333) exit(communication error), get stop signal 9

 

と何やらあやしげなログが出ているのですが、なぜ communication errorになるのかさっぱりわかりません。

 

該当プロセスをkillすればCPUリソース使い過ぎな状態は回避できるので、10分以上経ったrubyプロセスを強制的にkillするスクリプトをでっち上げてcronで動かすように。

 

#!/usr/local/bin/perl

 

# $ sudo aptitude install libproc-processtable-perl

use strict;

use Proc::ProcessTable;

 

my $pt = Proc::ProcessTable->new('cache_ttys' => 1);

for my $p (@{ $pt->table }) {

if ($p->cmndline =~ /^ruby.+index.fcgi/) {

my $elapsed = time() - $p->start;

if ($elapsed > 60 * 10) {

$p->kill(9);

print sprintf "%s (%s) killed!n", $p->pid, $p->fname;

}

}

}

 

 

mod_fcgidじゃなくてmod_fastcgiにすれば改善しそうな気もしますが、もうちょっと切羽詰まってから試してみようと思います。それにしてProc::ProcessTableモジュールは便利でした。

Apacheでリバースプロキシ(mod_proxy)を活用する(2) - 設定編

Apacheでリバースプロキシ(mod_proxy)を活用する(1) - インストール編に続いて、今回は実際にリバースプロキシサーバを構築してみたいと思います。

 

まずはバックエンドのサーバを準備

リバースプロキシを構築する前に、リバースプロキシサーバからリクエストが転送されるバックエンドのアプリケーションサーバを用意しておきます。今回はUbuntuでaptからインストールできるApacheを使用します。apache2パッケージがインストールされていない場合は、まずはこれをインストールしておきましょう。

 

$ sudo apt-get install apache2

 

インストールが完了したら下記のようなアプリケーションサーバ(といってもただCGIスクリプトを動かすだけのサーバ)の設定ファイルを /etc/apache2/httpd_backend_8080.conf として用意します。

 

#

# /etc/apache2/httpd_backend_8080.conf

#

User www-data

Group www-data

# modules

LoadModule authn_default_module /usr/lib/apache2/modules/mod_authn_default.so

LoadModule authz_default_module /usr/lib/apache2/modules/mod_authz_default.so

LoadModule authz_host_module /usr/lib/apache2/modules/mod_authz_host.so

LoadModule cgi_module /usr/lib/apache2/modules/mod_cgi.so

LoadModule dir_module /usr/lib/apache2/modules/mod_dir.so

LoadModule mime_module /usr/lib/apache2/modules/mod_mime.so

 

ServerRoot "/etc/apache2"

Listen 8080

ServerName localhost:8080

PidFile /tmp/httpd_backend_8080.pid

ServerAdmin example_at_example.com

DocumentRoot "/var/www/html"

TypesConfig /etc/mime.types

LogFormat "%h %l %u %t "%r" %>s %b "%{Referer}i" "%{User-Agent}i"" combined

ErrorLog /tmp/error_8080.log

CustomLog /tmp/access_8080.log combined

UseCanonicalName Off

AddHandler cgi-script .pl

 

 

StartServers 1

MinSpareServers 1

MaxSpareServers 5

MaxClients 20

MaxRequestsPerChild 50

 

 

Options ExecCGI FollowSymLinks

AllowOverride None

 

 

設定ファイルを用意したら以下のコマンドでDocumentRootで指定したディレクトリを作成し、その下にCGIスクリプトである index.pl を用意します。

 

$ sudo mkdir -p /var/www/html

$ sudo chmod 755 /var/www/html

$ sudo vi /var/www/html/index.pl

 

 

#!/usr/bin/perl

 

use strict;

use CGI;

 

my $q = CGI->new;

print $q->header(

-type => 'text/html',

);

 

print <<"EOS";

 

 

Hello world

 

 

Hello world!!

 

 

EOS

 

 

index.pl を作成したら実行権限を付与します。そしてサンプルのための画像を Apache のサイトからダウンロードして /var/www/html/httpd_logo_wide.gif として保存します。

 

$ sudo chmod +x /var/www/html/index.pl

$ cd /var/www/html

$ sudo wget http://httpd.apache.org/images/httpd_logo_wide.gif

 

 

これで準備が整いました。アプリケーションサーバApache を起動しましょう。

 

$ sudo /usr/sbin/apache2ctl -f /etc/apache2/httpd_backend_8080.conf -k start

 

起動したら http://example:8080/index.pl にアクセスして Hello world!! なページが表示されることを確認します。example のホスト名は例なので、お手持ちのマシン名に置き換えてください。これでバックエンドのサーバの設定は完了です。もし起動しない場合はエラーログが /tmp/error.log にあるので、これを確認してみてください。

 

リバースプロキシの準備

次は肝心のリバースプロキシの設定です。前回 /usr/local/httpd_proxy_2.2.11 にインストールしたので、/usr/local/httpd_proxy_2.2.11/conf/httpd.conf を以下のように修正します。

リバースプロキシの設定については ProxyPassReverse を使うやり方と mod_rewrite の [P] を使う2種類のやり方があると思いますが、今回は mod_rewrite でやりたいと思います。

 

--- httpd.conf.orig 2009-02-01 13:06:48.000000000 +0900

+++ httpd.conf 2009-02-04 22:40:14.000000000 +0900

@@ -106,7 +106,7 @@

# documents. By default, all requests are taken from this directory, but

# symbolic links and aliases may be used to point to other locations.

#

-DocumentRoot "/usr/local/httpd_proxy_2.2.11/htdocs"

+DocumentRoot "/var/www/html"

 

#

# Each directory to which Apache has access can be configured with respect

@@ -412,3 +412,15 @@

SSLRandomSeed startup builtin

SSLRandomSeed connect builtin

 

+

+

+ Options Indexes FollowSymLinks MultiViews

+ AllowOverride Indexes Limit

+ Order allow,deny

+ Allow from all

+

+

+RewriteEngine on

+RewriteRule ^/(.+).pl$ http://localhost:8080/index.pl [L,P,QSA]

+RewriteRule ^/(.+).(gif)$ /var/www/html/$1.$2 [L,QSA]

 

修正内容を順番に大雑把に解説すると

 

 

  • DocumentRootの変更

 

 

  • /var/www/htmlディレクトリの設定

 

 

  • mod_rewriteによるリバースプロキシの設定

 

 

となります。詳しく見ておきたいのは3.の部分で

 

RewriteEngine on

 

というディレクティブで mod_rewrite によるURLの書き換えを有効にし

 

RewriteRule ^/(.+).pl$ http://localhost:8080/index.pl [L,P,QSA]

 

拡張子が .pl のファイルを先ほど設定したバックエンドのサーバに転送し

 

RewriteRule ^/(.+).(gif)$ /var/www/html/$1.$2 [L,QSA]

 

拡張子が .pl 以外の .gif ファイルはこのリバースプロキシサーバ自体が処理する、という設定になっています。つまり、index.pl 内で使用しているスタティックな画像ファイル httpd_logo_wide.gif はプロキシサーバによって処理されますが、index.pl のファイルは動的にコンテンツを生成するCGIスクリプトなので、バックエンドのアプリケーションサーバにリクエストが転送されます。

 

ちなみに RewriteRule で使っている L, P, QSA のようなオプションの意味ですが

 

 

  • L - このルールが適用されたらそれ以降のルールは実行されない

 

 

  • P - プロキシとしてURLの書き換えを行う

 

 

  • QSA - URLの?以降のQUERY_STRINGを転送先のURLに常につける

 

 

という意味になります。詳しくはmod_rewrite - Apache HTTP Serverに書いてあるので参照してみてください。

 

動作確認

これで全ての準備が整ったので mod_proxy サーバを起動します。

 

$ sudo /usr/local/httpd_proxy_2.2.11/bin/apachectl -k start

 

そしてブラウザから http://example/index.pl にアクセスしてみましょう。Hello world!! のあとにApache のロゴが表示されているでしょうか。表示されていればリバースプロキシサーバの構築は完了ですが、念のため本当にリバースプロキシで動いているのか、アクセスログを見て確認してみましょう。リバースプロキシのログは /usr/local/httpd_proxy_2.2.11/logs/access_log にあります。

 

192.168.1.54 - - [05/Feb/2009:02:12:43 +0900] "GET /index.pl HTTP/1.1" 200 129

192.168.1.54 - - [05/Feb/2009:02:12:44 +0900] "GET /httpd_logo_wide.gif HTTP/1.1" 200 -

 

このログには上のような2つのログが出ているはずです。次にバックエンドのサーバのログを見てみます。これは /tmp/access.log にあります。

 

127.0.0.1 - - [05/Feb/2009:02:12:43 +0900] "GET /index.pl HTTP/1.1" 200 129 "-" "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; ja-JP-mac; rv:1.9.0.5) Gecko/2008120121 Firefox/3.0.5"

 

こちらのログには index.plのアクセスのみで httpd_logo_wide.gif へのアクセスはないはずです。これが確認できれば mod_proxy によるリバースプロキシサーバ構築に成功ということになります。ここまで設定できれば、あとは応用で「バックエンドのサーバを2台にして運用する」ということも可能になります。次回はそういう負荷分散も視野にいれたエントリを書きたいと思います。

 

なお、今回記載した Apache の設定は、簡潔なサンプルであるためセキュリティ的にはよろしくありません。なので、インターネットにつながっているサーバではこの記事の内容を試す場合、動作確認後すみやかに Apache のプロセスを落としておいた方が良いかと思います。

 

2009/04/06: 後世のためにバックエンドの設定ファイルの名前を /etc/apache2/httpd_backend.conf から /etc/apache2/httpd_backend_8080.conf に変更しました。

 

[tmkm-amazon]4873113814[/tmkm-amazon]

Apacheでリバースプロキシ(mod_proxy)を活用する(1) - インストール編

Apacheはとてもとても有名なWeb Serverですが、今日はそのチューニング方法の一つであるリバースプロキシについて書きたいと思います。

Webサーバの種類

一口にWebサーバと言っても、ダイナミックなWebコンテンツを生成するサイトにおいてWebサーバがserveするものは2種類あります。一つはプログラムで動的に生成されるHTMLファイル。もう一つがGIFJPEG, PNGなどの画像、および Flash, JavaScript, CSSなどの静的なファイルです。

 

プログラムで動的にHTMLを生成するためには、そのプログラムを動かす実行環境がWebサーバ(Apache)と連携する必要があります。いわゆるCGIとか最近だとFastCGI, mod_php, mod_perl, mod_rails, mod_pythonなどのスクリプト環境がよく使われています。これらの環境は昨今では「事前に実行環境(インタープリター)の起動をしてプログラムをコンパイルしておきそれを再利用する」というような使われ方をするため、Apacheがクライアントからの要求を処理するために生成する子プロセスのメモリ使用量が肥大化し、プロセスを起動する処理が重くなる傾向にあります。

 

一方で、単純にファイルの中身をそのままクライアントに渡せばいいようなスタティックなファイルが、このような肥大化したプロセスで処理されることには非常に無駄があります。そこでリバースプロキシの出番です。リバースプロキシの動作は凄くシンプルで、リクエストを受けたら、ダイナミックなページのリクエストのみバックエンドのアプリケーションサーバに渡し、それ以外のスタティックなファイルは自分自身で直接処理します(*1)。図にするとこんな感じでしょうか。

リバースプロキシのイメージ

 

このような単純な動作なので、リバースプロキシ自体にはmod_perlやmod_phpなどを組み込む必要がなくなり、プロセスのサイズは非常にスリムになります。実際このブログを動かしているマシンでも

 

 

  • リバースプロキシのプロセスサイズは3.5MB程度

 

 

  • mod_phpのプロセスサイズは25MB程度

 

 

となっており、一プロセスあたりで20MBも違います。

 

さらに、リバースプロキシは動作がシンプルなので、ApacheのMPM(Multi-Proccessing Module)にworkerというものを選択することができます。worker mpmはクライアントからのリクエストを処理するのにプロセスより軽量なスレッドを使用するため(*2)、prefork mpmと比べて動作が軽いという特徴があります。

 

*1)これは設定自体でどうにでも変更できます

*2)厳密に言うとプロセス+スレッドのハイブリッド方式です

 

また、フロントエンドのリバースプロキシ+バックエンドのアプリケーションサーバという構成にしておくことで、リバースプロキシは止めずにバックエンドのサーバを増やしたり交換したりすることが可能です。このため負荷分散のためのメンテナンスが非常にやりやすくなります。このあたりはmod_proxy_balancerというモジュールの話になるので今回は割愛しますが、また機会があればということで。

 

Apache 2.2.11のインストール

前置きが長くなりましたが、リバースプロキシを導入するメリットがわかったところで実際にApacheをインストールしてみたいと思います。環境は Ubuntu 8.10 Interpid です。

まずは最新版である2.2.11のソースを Apache本家 からダウンロードしておきます。

 

$ tar xvzf httpd-2.2.11.tar.gz

# ちょっとでも最適化する

$ export CFLAGS="-O3"

$ cd httpd-2.2.11

$ ./configure --prefix=/usr/local/httpd_proxy_2.2.11

--with-mpm=worker

--enable-shared

--enable-so

--disable-asis

--disable-cgid

--disable-proxy-connect

--disable-proxy-ftp

--disable-proxy-ajp

--disable-userdir

--disable-actions

--enable-modules='rewrite proxy proxy_balancer proxy_http cgi deflate headers expires'

--enable-mods-shared='ssl cache disk_cache mem_cache'

&& make && sudo make install

 

でインストールが完了です。configureにいろいろとオプションを渡していますが、これらは

 

 

  • 確実に使用するモジュールを静的にリンクさせる

 

 

  • 不要なモジュールは無効にする

 

 

  • 必要になるかもしれないモジュールはDSOとして組み込めるようにしておく

 

 

という指定です。/usr/local/httpd_proxy_2.2.11 にApacheのファイル一式がインストールされているので、起動確認を行いましょう。

$ sudo /usr/local/httpd_proxy_2.2.11/bin/apachectl start

でサーバが起動するので、http://localhost/ にアクセスしてみてください。"It works!"と表示されればインストール成功です。次回は具体的なリバースプロキシの設定方法を説明します。

 

Apacheが出力するServerヘッダを隠す

Apacheでは何も設定しないとデフォルトでレスポンスヘッダーに

Server: Apache

のような情報を残してしまいます。ServerTokens ProductOnly としてもApacheであることは隠せないので、mod_headersを使って

Header unset Server

とするのが正解なのか一応ぐぐってみたところ、ここのような「ソースを修正する」という残念なアドバイスが載っていました。それmod_headersでできるよ!とつっこんであげたかったのですが、解答が締め切られていたのが残念です。

ちなみにPHPは何も設定しないと

X-Powered-By: PHP/5.2.6-2ubuntu4

 

というようなヘッダーを出してしまうので、これもあわせて

Header unset X-Powered-By

しておきました。php.iniを修正すればいいらしいのですが、Ubuntuだとどこにあるのか調べるのが面倒だったのでApache側で対処。しかしデフォルトの設定っていうのは本当に怖いなぁと思いました。

 

2009/6/28 追記:Header unset Serverはリバースプロキシの環境でしかunsetできなさそうです。Header set Server hogeのようにして隠すことはできるはずです(未検証ですが)