気がする!
- なぜProxyPassReverseにbalancer://~~ を設定できないのか *1
- なぜProxyPassReverseにajp://~~ を設定できないのか
- なぜ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プロトコル利用時のレスポンスヘッダの違い関係してるんじゃないかと推測。
## 追記ここまで
- http://httpd.apache.org/docs/2.2/ja/mod/mod_proxy.html
- http://httpd.apache.org/docs/2.2/ja/mod/mod_proxy.html#proxypass
- http://httpd.apache.org/docs/2.2/ja/mod/mod_proxy.html#proxypassreverse
自分なりに理解できて納得しているつもりですが、確認に使った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| -------- ----------- -------------------
- clientがアクセスするhostはwww.example.com
- reverse proxyであるapacheがアクセスするbackendのhostはbackend*.example.com
- backendのapacheはのUseCannonicalName, UseCannonicalPhysicalPortがOffになっているとする
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
このときclientがhttp://www.example.com/app 以下にアクセスすると、reverse proxyのapacheはbackendのサーバにアクセスする。このとき、backendサーバ側でredirectが発生した場合にProxyPassReverseの出番。良くあるのはディレクトリにスラッシュなしでアクセスした場合。他にもアプリ側で特定URLにリダイレクトさせた場合など。
- clientがhttp://www.example.com/app/dir にアクセス
- リクエストを受け取ったapache(reverse proxy)が、backendのhttp://backend1.example.com:8080/app/dirにアクセス
- backendのapacheは301でLocationヘッダにスラッシュを付けたURL(http://backend1.example.com:8080/app/dir/)をreverse proxyのapacheに返す
- reverse proxyのapacheは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のアプリケーションサーバにアクセスされたら負荷が高くなる
- そもそもbackend1.example.comがDNSで名前解決できない or backendがプライベートIPのLANにいる or Firewallで閉じられている等の場合はclientがbackendにアクセスできない
- ex) /etc/hostsに 10.0.0.10 backend1 を設定
- ProxyPass /app http://backend1:8080/app と設定している場合など
などの事情があるため、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もいらないぽい?
- clientがhttp://www.example.com/app/dir にアクセス
- リクエストを受け取ったreverse proxyが、backendのajp://backend1.example.com:8009/app/dirにアクセス
- backendのtomcatは301でLocationヘッダにスラッシュを付けたURLをreverse proxyに返す
- 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
- clientがhttp://www.example.com/app/dir にアクセス
- リクエストを受け取ったreverse proxyが、backendのajp://backend1.example.com:8009/foo/dirにアクセス
- backendのtomcatは301でLocationヘッダにスラッシュを付けたURLをreverse proxyに返す
- 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 proxyのapacheがajp://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ヘッダ等に使用されるFQDNはapacheがtomcatにajpで接続するときのhost名ではなく、元々clientがアクセスしたヘッダ情報が使われているからでないかと。
httpの場合は、reverse proxyがbackendに接続する場合、
- client: reverse proxy
- server: backend apache server
という関係になるので、(UseCannonicalNameの設定に依存するけど) reverse proxyのapacheは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
- http://httpd.apache.org/docs/2.2/ja/mod/mod_proxy.html#proxypassreversecookiedomain
- http://httpd.apache.org/docs/2.2/ja/mod/mod_proxy.html#proxypassreversecookiepath
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
clientが http://www.example.com/foo/hoge.cgiにアクセスするとreverse proxyはhttp://backend.example.com/bar/hoge.cgiにいく。このcgiが
Set-Cookie: id=100; path=/bar
のようなcookieヘッダを返す場合、このままではpathが変わってcookieが動かないのでProxyPassReverseCookiePathを使う。
### 20140824 修正 コメントで指摘いただいたので修正しました!
ProxyPassReverseCookiePath /bar /foo
↓間違ってこう書いてしまっていました。
ProxyPassReverseCookiePath /foo /bar
また、同様にSet-Cookie内のdomainも置換する場合は、ProxyPassReverseCookieDomainで。この設定は他と違って、reverse proxy先のdomainを先に書くので注意。
ProxyPassReverseCookieDomain backend.example.com www.example.com