Hatena::ブログ(Diary)

うまい棒blog このページをアンテナに追加 RSSフィード Twitter

2009-12-05

mod_proxyのProxyPassReverseの意味がようやく理解できた

気がする!

  1. なぜProxyPassReverseにbalancer://~~ を設定できないのか *1
  2. なぜProxyPassReverseにajp://~~ を設定できないのか
  3. なぜbackendがhttpとajpの場合で、ProxyPassReverseに設定するURLが異なるのか

などなど。今まではmod_proxyする機会がほとんどなく適当にお茶を濁していたので、世間の人から相当遅れているとは思いますが、せっかくなので自分用まとめ。思いついたことをつらつらとメモっているからかなり冗長ですが。

追記 20100907

apache 2.2.12 から、balancer:// のURLにもProxyPassReverseが使えるようにmod_proxyがパワーアップしていました。

*) mod_proxy: Complete ProxyPassReverse to handle balancer URL's. Given;

BalancerMember balancer://alias http://example.com/foo

ProxyPassReverse /bash balancer://alias/bar

backend url http://example.com/foo/bar/that is now translated /bash/that

[William Rowe]

というわけで、記述もスッキリするのでapache 2.2.12はProxyPassReverse balancer://~~ で書いたほうが良いかもしれません。(試してないので何とも言えないけど)

さらに追記

実際に試してみたけど、ProxyPassReverse balancer://~~ がうまく動作するのは、BalancerMemberがhttpのときで、ajp:// だとLocationヘッダが調整できてなかった。3番の項目のとこでも書いたけど、httpとajpプロトコル利用時のレスポンスヘッダの違い関係してるんじゃないかと推測。

## 追記ここまで


自分なりに理解できて納得しているつもりですが、確認に使ったbackendサーバapache + 簡単なcgi, tomcatデフォルトのwebappsだけなので間違っているところもあるかもしれませんご指摘いただけると幸いです。

構成

確認で使ったのはvmware環境に作ったapache(reverse proxy)と、backendはapache or tomcat

                      reverse proxy                   backend  server
--------     http      -----------    http or ajp    -------------------
|client|    <======>   | apache  |     <======>      | apache or tomcat|
--------               -----------                   -------------------

ProxyPassReverse

ProxyPassReverse ディレクティブ

説明: リバースプロキシされたサーバから送られた HTTP 応答ヘッダの URL を調整する

構文: ProxyPassReverse [path] url

apache公式の説明のように、ProxyPassReverseで設定するのはバックエンドサーバから送られてくるLocationヘッダ等のURLで、書き換えしたいURLを設定する。書き換えが行われるのは一致したときなので正しく書かなければいけない。

例えば下2行の設定。backend1のapacheがport80でlistenしていれば、URLとしては実質同じ意味だけど、ProxyPassReverseの設定としては別物(後述).

ProxyPass /app http://backend1.example.com:80/app
ProxyPass /app http://backend1.example.com/app

URLを書き換えしたいときとは

例えばこんなProxyPass設定のとき。

ProxyPass /app http://backend1.example.com:8080/app 

このときclienthttp://www.example.com/app 以下にアクセスすると、reverse proxyapacheはbackendのサーバアクセスする。このとき、backendサーバ側でredirectが発生した場合にProxyPassReverseの出番。良くあるのはディレクトリにスラッシュなしでアクセスした場合。他にもアプリ側で特定URLリダイレクトさせた場合など。

  1. clienthttp://www.example.com/app/dirアクセス
  2. リクエストを受け取ったapache(reverse proxy)が、backendのhttp://backend1.example.com:8080/app/dirアクセス
  3. backendのapacheは301でLocationヘッダにスラッシュを付けたURLをreverse proxyapacheに返す
  4. reverse proxyapacheはbackendから受け取ったHTTPレスポンスclientに返す

問題はこのときの3番のHTTPヘッダ(のLocationヘッダ)。reverse proxyであるapacheはbackendのサーバに対してhttp://backend1.example.com:8080/アクセスしているので、backendから受け取るHTTPヘッダは以下のようなもの。

HTTP/1.1 302 Moved Temporarily
Date: Sat, 05 Dec 2009 03:08:13 GMT
Server: Apache
Location: http://backend1.example.com:8080/app/dir/
Transfer-Encoding: chunked
Content-Type: text/plain

このヘッダがclientに返ってしまうと、clientはLocationヘッダの値であるhttp://backend1.example.com:8080/app/dir/ に直接アクセスしようとしてしまう。


clientがbackendのサーバに直接アクセスするとなぜダメか

色々あるけど。

などの事情があるため、reverse proxyはbackendから上記のようなLocationヘッダが返ってきたときにLocationヘッダを調整する必要がある。これを実際に行うのがProxyPassReverse。調整したいURLとはまさにhttp://backend1.example.com:8080/app なので、apacheの設定は

ProxyPass        /app http://backend1.example.com:8080/app 
ProxyPassReverse /app http://backend1.example.com:8080/app 

こうなる。


なぜProxyPassReverseにbalancer://~~ を設定できないのか、の答え

ここで1番の答え。↑の例のようにProxyPassReverseに設定するのはbackendから返ってくるLocationヘッダ等のURLを調整するためのもの。

なので、ProxyPassとセットにしてProxyPassReverseにbalancer://~~ を書いても意味はない。backendのサーバからbalancer://~~ とかいうLocationヘッダが返ってくるはずはないのだから。

ProxyPass        /app balancer://cluster/app 
ProxyPassReverse /app balancer://cluster/app ## 間違い

ProxyPassReverseのURLはBalancerMember or ProxyPassに設定するURLとは必ずしも一致しない

これも微妙にはまった。実環境ではこんなチグハグな設定はないかもしれないけど。

2つのbackendのapacheがそれぞれportを80/8080でlistenしていたので、BalancerManagerに書くときにportを明示的に書いていた。

  • reverse proxyに設定したbalancer
<Proxy balancer://cluster >
  BalancerMember http://backend1.example.com:80  
  BalancerMember http://backend2.example.com:8080
</Proxy>

肝心のProxyする設定。ここを間違えた。てっきりBalancerMemberと同じ値で良いと思いこんで次の設定にしていたらbackend1にいったときは正常に動かない。

ProxyPass         /app balancer://cluster/app
ProxyPassReverse  /app http://backend1.example.com:80/app    # 1
ProxyPassReverse  /app http://backend2.example.com:8080/app

正解はこっち。

ProxyPass         /app balancer://cluster/app
ProxyPassReverse  /app http://backend1.example.com/app       # 2
ProxyPassReverse  /app http://backend2.example.com:8080/app

なぜか。

reverse proxyがbackend1のapacheアクセスするときに、http://backend1.example.com:80/とport指定で

アクセスしたとしても、backendのサーバはport番号を付けずに、

Location: http://backend1.example.com/app

HTTPヘッダを返してくる。ProxyPassReverseが書き換えるのは、このとき返ってくるヘッダのURLなので、#1の設定は間違い。apacheがdefaultの80番でlistenしている場合、URL意味としては#1も#2も同じだけどProxyPassReverseの設定としては別。


backendがajpの場合

ajpプロトコル仕様を知らないので経験を元にした推測ですが、ajpで渡す場合は、

ProxyPass /app ajp://localhost:8009/app

と書いたとしてもtomcatに渡されるhostヘッダはlocalhostではなくて、clientが元々アクセスしていたwww.example.comのままっぽい。なので、tomcat側でredirectが発生しても、tomcatからreverse proxyであるapacheに返ってくるLocationヘッダのhostはwww.exmaple.comのままだった。


同じURLの構成でbackendにproxyする場合

URLを同じ構成でbackendのajpに渡す場合はLocationヘッダの調整は必要ないのでProxyPassReverseもいらないぽい?

  1. clienthttp://www.example.com/app/dirアクセス
  2. リクエストを受け取ったreverse proxyが、backendのajp://backend1.example.com:8009/app/dirアクセス
  3. backendのtomcatは301でLocationヘッダにスラッシュを付けたURLをreverse proxyに返す
  4. reverse proxyはbackendから受け取ったHTTPレスポンスclientに返す

このときの3のHTTPヘッダは

HTTP/1.1 302 Moved Temporarily
Date: Sat, 05 Dec 2009 04:23:11 GMT
Location: http://www.example.com/app/dir/
Content-Type: text/plain

だったのでヘッダの調整は不要なのかしら。

ProxyPass          /app ajp://backend1.example.com:8009/app
#ProxyPassReverse   /app http://www.example.com/app
## これが不要

URLを変えて渡す場合

このときはProxyPassReverseは必要なはず。例えば /app => backendの/fooに渡す場合

ProxyPass /app  ajp://backend1.example.com:8009/foo
  1. clienthttp://www.example.com/app/dirアクセス
  2. リクエストを受け取ったreverse proxyが、backendのajp://backend1.example.com:8009/foo/dirアクセス
  3. backendのtomcatは301でLocationヘッダにスラッシュを付けたURLをreverse proxyに返す
  4. reverse proxyはbackendから受け取ったHTTPレスポンスclientに返す

このときの3のHTTPヘッダは

HTTP/1.1 302 Moved Temporarily
Date: Sat, 05 Dec 2009 04:26:25 GMT
Location: http://www.example.com/foo/dir/
Content-Type: text/plain

だった。clientアクセスした際の頭の/appが/fooに変わってしまっている。httpと違ってFQDNはbackend1にはなっておらず、www.example.comのままなこともポイント

tomcat側からすると、reverse proxyapacheajp://backend1.example.com:8009/fooにアクセスするから、"/app"なんてものは全く関知しないところなので、たぶんこうなっている? このままだと、clientが間違ったURLリダイレクトするので、

ProxyPass          /app  ajp://backend1.example.com:8009/foo
ProxyPassReverse   /app  http://www.example.com/foo

の設定を書いてあげないといけない。

この辺りまで理解できたきたので、冒頭の2番、3番の答えも出る。


なぜProxyPassReverseにajp://~~ を設定できないのか、の答え

何回も書いてるけどProxyPassReverseに書くのはbackendから返ってきたLocationヘッダ等を調整するための設定。client <=>(http) apache <=>(ajp) tomcatと通信しても、Locationヘッダはclientがそもそもアクセスしたhttp://www.exmaple.com/~~ になるのでProxyPassにajp://~~を設定しても意味がない。


なぜbackendがhttpとajpの場合で、ProxyPassReverseに設定するURLが異なるのか、の答え

backendがhttpの場合はProxyPassReverseに設定するのはhttp://backend1.example.com/。それに対して、ajpの場合は、clientアクセスしたhttp://www.example.com/

で、これは確証がなくて予想なんだけど。ajpプロトコルの場合、Locationヘッダ等に使用されるFQDNapachetomcatajpで接続するときのhost名ではなく、元々clientアクセスしたヘッダ情報が使われているからでないかと。


httpの場合は、reverse proxyがbackendに接続する場合、

という関係になるので、(UseCannonicalNameの設定に依存するけど) reverse proxyapacheはProxyPassに設定しているhost名でbackendに接続しにいくので、backendのapacheはLocationヘッダでhttp://backend1.example.com/~~URLを返すようになる。その違い。


おまけ

あと、ProxyPassReverseに書くURLはマッチした順に適用されるので、

(こんなproxyの仕方しないかもしれないけど)

ProxyPass         /baz balancer://cluster
ProxyPass         /foo balancer://cluster/foo

この場合は、/bazについてのProxyPassReverseを後に書く必要がある。

  • 間違い
ProxyPassReverse  /baz http://backend1.example.com:8080    
ProxyPassReverse  /foo http://backend1.example.com:8080/foo
  • 正解
ProxyPassReverse  /foo http://backend1.example.com:8080/foo
ProxyPassReverse  /baz http://backend1.example.com:8080             # こっちを後に

/bazの設定を先に書いてしまうと、/fooへのリクエストに対してのLocationヘッダも調整されてしまって、http://backend1.example.comの部分がhttp://www.example.com/bazに書き換えられる。結果としてhttp://www.example.com/baz/foo になってしまう。


設定まとめ

上記のことをまとめると、たぶんこんな感じになる。

backendがhttp

基本的にProxyPassと同じものを書けば良い。

  • 1台の場合
ProxyPass         /app http://backend1.example.com:8080/app 
ProxyPassReverse  /app http://backend1.example.com:8080/app
  • 複数台でbalancerしてるとき
<Proxy balancer://cluster/ >
  BalancerMember http://backend1.example.com:8080
  BalancerMember http://backend2.example.com:8080
</Proxy>

ProxyPass         /app blancer://cluster/app
ProxyPassReverse  /app http://backend1.example.com:8080/app
ProxyPassReverse  /app http://backend2.example.com:8080/app
backendがajp

URLを変換してproxyしている場合は設定が必要。URLをそっくりそのまま渡している場合はProxyPassReverseはいらない。とはいえ書いても問題はないと思うので、書いてしまってても良いかも。

  • 1台の場合: URLをそのまま渡している
ProxyPass         /app ajp://backend1.example.com:8009/app
ProxyPassReverse  /app http://www.example.com/app
## ↑いらない
  • 1台の場合: URLを変更している
ProxyPass         /app ajp://backend1.example.com:8009/foo/app
ProxyPassReverse  /app http://www.example.com/foo/app
  • 複数台でbalancer: URLをそのまま渡している
<Proxy balancer://cluster >
  BalancerMember ajp://backend1.example.com:8009
  BalancerMember ajp://backend2.example.com:8009
</Proxy>
ProxyPass         /app blancer://cluster/app
ProxyPassReverse  /app http://www.example.com/app
## ↑いらない
  • 複数台でbalancer: URLを変更している
<Proxy balancer://cluster >
  BalancerMember ajp://backend1.example.com:8009
  BalancerMember ajp://backend2.example.com:8009
</Proxy>
ProxyPass         /app blancer://cluster/foo/app
ProxyPassReverse  /app http://www.example.com/foo/app

追記

ProxyPassReverseCookiePathとCookieDomain

ProxyPassReverseはLocationヘッダを調整する。それと同様に、アプリによってはCookie(Set-Cookieヘッダ)のpath、domainも調整しないといけない。仕組みはProxyPassReverseと同じ。

clientアクセスしているURLのpathとbackendのアプリが返すSet-Cookieのpathは必ずしも一致しない。置換する必要がある場合に、reverse proxyしているapacheに設定する。

ProxyPass        /foo http://backend.example.com/bar
ProxyPassReverse /foo http://backend.example.com/bar

clienthttp://www.example.com/foo/hoge.cgiアクセスするとreverse proxyhttp://backend.example.com/bar/hoge.cgiにいく。このcgi

Set-Cookie: id=100; path=/bar

のようなcookieヘッダを返す場合、このままではpathが変わってcookieが動かないのでProxyPassReverseCookiePathを使う。

ProxyPassReverseCookiePath   /foo /bar

また、同様にSet-Cookie内のdomainも置換する場合は、ProxyPassReverseCookieDomainで。この設定は他と違って、reverse proxy先のdomainを先に書くので注意。

ProxyPassReverseCookieDomain backend.example.com www.example.com

*1apache 2.2.12以前

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証