複数のテストサーバをリバースプロキシで集約 (1)
各所でテストサーバが立ち上がっていて,これらは一台のリバースプロキシの背後にいます。普通に mod_proxy でやっているのですが,テストサーバを増やすたびに設定ファイルを書き換えて httpd を再起動,としなくちゃいけないのが煩わしい。
で,リバースプロキシのマッピング先を動的に構成したいなと思ったわけです。
以降では下記の例を使います。
アクセス URL | リダイレクト先 |
---|---|
http://outer1.example.com/path1/ | http://inner-a.example.com/path1/ |
http://outer1.example.com/path2/ | http://inner-b.example.com/path2/ |
http://outer2.example.com/path3/ | http://inner-b.example.com/path3/ |
おことわり
今後のお話ででてくる設定を適用すると,mod_proxy を素で使った場合よりパフォーマンスがだいぶんと落ちます。なのでプロダクション環境にはおすすめできません。あくまでテストサーバ環境ということで。
素直に mod_proxy だけで書いてみる
まずは mod_proxy を使った設定を書いてみます。ProxyPass
に retry
*1 を設定していないのはサンプルということでご容赦を。
LoadModule proxy_module modules/mod_proxy.so LoadModule proxy_http_module modules/mod_proxy_http.so NameVirtualHost * ProxyRequests Off ProxyPreserveHost On <VirtualHost *:*> ServerName outer1.example.com DocumentRoot /var/www/empty # http://outer1.example.com/path1/ => http://inner-a.example.com/path1/ ProxyPass /path1 http://inner-a.example.com/path1 # http://outer1.example.com/path2/ => http://inner-b.example.com/path2/ ProxyPass /path2 http://inner-b.example.com/path2 </VirtualHost> <VirtualHost *:*> ServerName outer2.example.com DocumentRoot /var/www/empty # http://outer1.example.com/path3/ => http://inner-b.example.com/path3/ ProxyPass /path3 http://inner-b.example.com/path3 </VirtualHost>
普通に書くとだいたいこんな感じになるのではないでしょうか。んで,ドメインが増えるたびに <VirtualHost>
を追加し,マッピング先が増えるたびに ProxyPass
を追加していく,ということになります。見通しは悪くないのですが,記述する分量はずいぶんと多いですね。
ヘッダの調整について
[http://httpd.apache.org/docs/2.2/mod/mod_proxy.html#proxypassreverse:title=ProxyPassReverse]
, [http://httpd.apache.org/docs/2.2/mod/mod_proxy.html#proxypassreversecookiedomain:title=ProxyPassReverseCookieDomain]
, [http://httpd.apache.org/docs/2.2/mod/mod_proxy.html#proxypassreversecookiepath:title=ProxyPassReverseCookiePath]
あたりの話です。
なんでこんなのが用意されてるか。
さきほどの例で,[]inner-b.example.com[]
にアクセスがあった状況について考えてみます。
GET /path2/hoge Host: outer1.example.com
のようなリクエストにたいして /path2/foo
にリダイレクトする場合。
Host
ヘッダを考慮して
HTTP/1.1 303 See Other Location: http://outer1.example.com/path2/foo
のようにレスポンスを返してくれればよいですが,
HTTP/1.1 303 See Other Location: http://inner-b.example.com/path2/foo
のように自分のホスト名をドメイン名としてしまう「頑固」なウェブアプリケーションも存在します*2。
こんなとき,mod_proxy 側の設定で
<VirtualHost *:*> ServerName outer1.example.com ProxyPassReverse /path2 http://inner-b.example.com/path2 </VirtualHost>
のようになっていると
HTTP/1.1 303 See Other Location: http://outer1.example.com/path2/foo
mod_proxy がレスポンスヘッダを適宜書き換えてくれるというわけです。
とはいうものの,レスポンス HTML(のたとえば <a>
タグとか)に含まれる (Fully Qualified) URL まで書き換えてくれるわけではありません。逆にいうと,出力される HTML の内容が Host
ヘッダによってきちんと変わるウェブアプリケーションであれば,ヘッダも同様にきちんと書き換えてくれるはずでしょう。なので実はいちいち設定しなくてもうまく動くことがほとんどです。
ちなみに HTML の中身まで書き換えてくれる mod_proxy_html というモジュールもあります。普通はウェブアプリ(サーバ)側を書き換えるべきだと思いますが,既存のウェブアプリ環境を書き換えることなく外部に露出したいなどどうしてもということであれば,こういうのを使ってもいいかもしれません。
mod_rewrite とからめて使ってみる
[http://httpd.apache.org/docs/2.2/mod/mod_rewrite.html:title=mod_rewrite] を使うと,リクエスト(ヘッダ)の内容に応じて URI を書き換えたり Proxy 先を選定することが可能になります。これを使った設定は下記のようになります。
LoadModule proxy_module modules/mod_proxy.so LoadModule proxy_http_module modules/mod_proxy_http.so LoadModule rewrite_module modules/mod_rewrite.so UseCanonicalName Off ProxyRequests Off ProxyPreserveHost On RewriteEngine On RewriteMap lowercase int:tolower # http://outer1.example.com/path1/ => http://inner-a.example.com/path1/ RewriteCond ${lowercase:%{SERVER_NAME}} =outer1.example.com RewriteCond %{REQUEST_URI} /path1 RewriteRule ^/(.*)$ http://inner-a.example.com/$1 [proxy] # http://outer1.example.com/path2/ => http://inner-b.example.com/path2/ RewriteCond ${lowercase:%{SERVER_NAME}} =outer1.example.com RewriteCond %{REQUEST_URI} /path2 RewriteRule ^/(.*)$ http://inner-b.example.com/$1 [proxy] # http://outer2.example.com/path3/ => http://inner-b.example.com/path3/ RewriteCond ${lowercase:%{SERVER_NAME}} =outer2.example.com RewriteRule ^/(.*)$ http://inner-b.example.com/$1 [proxy]
SERVER_NAME
とのマッチングで「=
〜」というのが見慣れないかもしれません。普通にセオリー通りに正規表現で書くなら,
RewriteCond ${lowercase:%{SERVER_NAME}} ^outer1\.example\.com$
となりますし,PCRE 表記を用いると
RewriteCond ${lowercase:%{SERVER_NAME}} ^\Qouter1.example.com\E$
のようになるでしょう。このような状況で使える全体 as-is マッチング記法「=
」というのがあるということです。というかわたしも今回初めて知ったんですけどね。
また
RewriteMap lowercase int:tolower
というのも見慣れないかも。RewriteMap
というのは RewriteCond
や RewriteRule
で用いられる値にフィルタリングするためのものです。int:tolower
というのは,mod_rewrite に内蔵された tolower
というマッパで,このマッパを lowercase
という名前で用いる,ということになります。
したがって,
RewriteCond ${lowercase:%{SERVER_NAME}} =outer1.example.com
の部分は,SERVER_NAME
に int:lower
というフィルタをかけて,outer1.example.com
に全部一致したとき,という意味になります。
もちろんこのようなフィルタをかけずに
RewriteCond %{SERVER_NAME} =outer1.example.com
だけでもいいことはいいのですが,URL のドメイン名部分は大文字でやってくるかもしれないので,念のために tolower
しています。このへんは実は Dynamically configured mass virtual hosting - Apache HTTP Server Version 2.2 を参考にしました。
ともあれ,これでマッピングルールが増えるたびに,RewriteCond
を2〜3行,RewriteRule
を1増やせばよい勘定になります。もとの mod_proxy を使う設定に比べると黒魔術的表記になっている気もしますが。
余談: ログについて
このように <VirtualHost>
を使わずに複数ドメインを扱うと,異なるドメインへのリクエストログが同一のファイルに書かれてしまいます。同一のファイルにまとまってしまうことはまあいいんですが,ホスト名部分がロストするのでどのリクエストかわからなくなる弊害があります。
LogFormat "%h %l %u %t \"%m http://%{Host}i%U%q\" %s %b" vcommon CustomLog logs/virtual.log vcommon
のような設定をしてホスト名をログに記載するようにするのがいいかもしれません*3。このような形式のログを既存のログ解析ソフトが受け付けてくれるかわかりませんが。
ここまでの設定では,テストサーバが増減するたびに設定ファイルを書き換えて httpd を再起動しなくてはいけないという状況はかわっていません。続きます。→複数のテストサーバをリバースプロキシで集約 (2) - daily dayflower