Qiitaなどの記事を書くのは初めてでまだまだ不慣れな点が多々あると思いますが宜しくお願いします!
また、初学者のため間違っている点など多数あると思います。そのときは是非コメントなどいただけると助かります。
今回はNginxをリバースプロキシサーバ、その配下にApacheを用いたWebサーバを動かしている環境で、複数のVirtualHostを設定し、聞かれたドメインに対してDocumentRootを振り分けるということをしようと思います。
#前提
nginxでリバースプロキシ運用しているサーバ(hoge.com, IPは111.111.11.11)がいて、この子は2つのサブドメイン(a.hoge.com, b.hoge.com)を持っています。また、プロキシ配下にApacheで動いているWebサーバ(huga.com, IPは222.222.22.22)がいます。このサーバには既にこれまで作られたたくさんのWebコンテンツが入っています。
#やりたいこと
nginxで1台のWebサーバにリバースプロキシさせるときに、ドメインによってApacheの読むconfig file (主にDocumentRootなど)を変えたい。
- a.hoge.comで受け取ったRequestが来たら/etc/httpd/conf.d/a.conf,
- b.hoge.comで受け取ったRequestが来たら/etc/httpd/conf.d/b.conf
を読んでほしいという感じです。
方針としては、WebサーバーにてVirtualHostを2つ割り振り、リバースプロキシで受けたHost名に対してApacheの動作タグを分岐させれば良いということになると思います。
#やったことなど(失敗例)
Apacheの方でVirtualHost名をそれぞれ
- a-intra
- b-intra
といった具合に設定し、huga.comのCNAMEとしてDNSに登録しました。
<VirtualHost *:80>
ServerName a-intra.com
DocumentRoot /foo/bar/a/html
</VirtualHost>
<VirtualHost *:80>
ServerName b-intra.com
DocumentRoot /foo/bar/b/html
</VirtualHost>
(これが長い戦いとなる原因でした…)
nginxには
- proxy_pass
- proxy_set_header Host
の関数があります。この2つをうまくやって実装できると考えました。
proxy_passには、リバースプロキシを向ける先を指定します。proxy_set_headerにてHostの情報をheaderに書き込む設定をします。
nginxのconfig fileのリバースプロキシ設定をこのように書きます。
server {
listen 111.111.11.11:80; #hoge.comのIP
server_name a.hoge.com;
...
proxy_pass http://222.222.22.22:80; #huga.comのIP
proxy_set_header Host a-intra.com; #振りたいVirtualHost名
server {
listen 111.111.11.11:80; #hoge.comのIP
server_name b.hoge.com;
...
proxy_pass http://222.222.22.22:80; #huga.comのIP
proxy_set_header Host b-intra.com; #振りたいVirtualHost名
最初、proxy_passにVirtualHost名を入れてproxy_set_header Host $hostのようにしてもみたのですが、これは意味がありませんでした。
proxy_passにホスト名を入れても、DNSで名前を引っ張ってきたIPアドレスに変換されるので
- proxy_pass http://222.222.22.22:80;
- proxy_pass http://a-intra.com:80;
- proxy_pass http://b-intra.com:80;
はいずれも等価であると思われます。
そうすると、Host名をVirtualHost名にすればいいか、と至ったわけです。
nginxを再起動し、Let's try!
a.hoge.comにアクセスします。
……
出来た!a-intra.comの中身が見れました!
……
と思ったら、Link(/foo)を踏んだ先でURLが
http://a-intra.com/foo/
と書き換わっている!!!
222.222.22.22はイントラネット上のサーバで外部からのアクセスはできませんので、このページは外からは見れないことになります。
##原因
ここでどのようなHeaderで通信しているか、host名・server名はどのような通信をしているのかをPHPを用いて調べてみました。
<?php
print $_SERVER['HTTP_HOST'];
print . “ <br/>”;
print $_SERVER['SERVER_NAME'];
?>
proxy_set_header Host a-intra.com;
とするとこのように出力されます。
a-intra.com
a-intra.com
ふむふむ。server nameもこうなっているのか…
というか、headerにHostをa-intra.comって書いているから普通そうですよね。作業中に悪戦苦闘していると物事を俯瞰的に見れないのです。
さて、URLが書き換わってしまう問題ですが、これはこの中身のコンテンツが絶対PATH参照になっていたからだったんですね。だからリンクを踏むと (Host名)/(コンテンツ) のようなPATHになってしまっていたと。
そうしたら、Apacheにて設定するVirtualHostのServerNameはDNSに登録したユニークな名前ではなく、リバースプロキシを刺す前のサブドメイン名で良いのではないか?と思いたち…
最終形態
Nginxのリバースプロキシ設定部ではそれぞれのサブドメインに対してこのように書きます。
server {
listen 111.111.11.11:80; #hoge.comのIP
server_name a.hoge.com;
...
proxy_pass http://222.222.22.22:80; #huga.comのIP
proxy_set_header Host $host; #そのままのHost名を渡す
server {
listen 111.111.11.11:80; #hoge.comのIP
server_name b.hoge.com;
...
proxy_pass http://222.222.22.22:80; #huga.comのIP
proxy_set_header Host $host; #そのままのHost名を渡す
Nginxでは受け取ったHost名をそのまま渡します。そして、Apache側ではこんな感じで
<VirtualHost *:80>
ServerName a.hoge.com
DocumentRoot /foo/bar/a/html
</VirtualHost>
<VirtualHost *:80>
ServerName b.hoge.com
DocumentRoot /foo/bar/b/html
</VirtualHost>
ApacheでのServerNameはリバースプロキシサーバにListenするHost名にしました。
プロキシサーバのnginx, Webサーバのapacheを再起動してa.hoge.com, b.hoge.comを見ると思った挙動になりました!
#苦戦した原因
これまでの運用では元々a.hoge.comとb.hoge.comは別々のマシンで動かしていました。それを1つのマシンに統合して運用しようということになり、ここでまじめにVirtualHostを複数使用してみようとなったわけです。
一応、前の環境でもVirtualHostの設定は書いていました。そのときにServerNameはa-intra.comとしていました。しかし、このVirtualHostは当然ながら1つしか存在していません。
Apacheは受け取ったHost名がVirtualHostのいずれにも引っかからなかった場合、configファイルの名前をalphabetical orderで読んで最初に引っかかったVirtualHostをdefaultに設定し、それを自動で映す仕様になっています。Host名はそのままでheaderに書いていました。
これまでHost名がVirtualHost名と一致していないという事象に気づけませんでした。
# httpd -S
でどれがdefault serverになっているかの確認ができます。
というか、DNSサーバにはVirtualHost名を登録するべきだという謎の先入観に囚われました…。