まだプログラマーですが何か?

プログラマーネタ中心。たまに作成したウェブサービス関連の話も https://twitter.com/dotnsf

タグ:httpd

この記事の続きです:


IBM LinuxONE コミュニティクラウド上に作った仮想サーバーにいわゆる "LAMP"(=Linux + Apache HTTPD + MySQL + PHP) 環境を構築してみます。まずは上記記事を参考に仮想サーバーを作り、SSH でリモートログインします:
2017010403


ミドルウェアの導入作業を伴うため、ルート権限を持ったユーザーにスイッチしておきます:
$ sudo /bin/bash
#


LAMP 環境に必要なミドルウェアや言語環境をまとめて導入します(赤字はコメント):
# yum install httpd -y (Apache HTTP サーバー)
# yum install mysql-server mysql -y (MySQL)
# yum install php php-mbstring php-mysql php-gd php-pear php-xml php-devel -y (PHP)

また以下は LAMP 環境構築においては必須ではありませんが、使うことも多いというか、あると便利だと思うので必要に応じて導入しておいてください:
# yum install screen -y (screen)
# yum install git -y (git)
# yum install java-1.8.0-ibm-devel -y (JDK 1.8)

ミドルウェアを起動する前にファイアウォールの設定を行います。デフォルトの LinuxONE では iptables によるファイアウォールが有効になっており、このままでは http(s) によるアクセスができません。今回の環境では iptables を無効にしておきましょう:
# /etc/init.d/iptables stop
# chkconfig iptables off

あらためて各ミドルウェアを起動し、また自動起動設定をしておきます:
# /etc/init.d/httpd start
# /etc/init.d/httpd mysqld
# chkconfig httpd on
# chkconfig mysqld on
# exit
$

この時点で Apache HTTP サーバーが動いています。iptables の解除が成功していれば http://(IPアドレス)/ にアクセスすることができるようになっているはずです:
2017010601


さて、MySQL に関しては root のパスワードを設定しておきましょう。この例では P@ssw0rd というパスワードにしていますが、ここは必要に応じて変えてください:
$ mysql -u root

mysql> set password for root@localhost=PASSWORD('P@ssw0rd');
mysql> exit

これで LinuxONE 上でも LAMP の環境が作れました! ちなみに PHP のバージョンは 5.3.3 が導入できます:
$ php -v
PHP 5.3.3 (cli) (built: Dec 15 2015 04:50:47)
Copyright (c) 1997-2010 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2010 Zend Technologies

ウェブサービスを開発・公開する上で、URL のリライトの問題があります。

例えば、アプリケーションとしては
 http://xxx.myserver.com/abc.php?country=Japan&pref=Chiba&city=Funabashi
というパラメータを受け取って処理するようなページやサービスがあったとします。

単に動かすだけなら、このパラメータ処理でいいのですが、SEO 対策や、単に長くて見難いという理由から、これを
 http://xxx.myserver.com/abc.php/Japan/Chiba/Funabashi
のような URL パターンで受け取りたいことが出てきます。

この例であれば、
 http://xxx.myserver.com/abc.php/AAA/BBB/CCC
という URL パターンを有効な URL として認識し、実際に処理する際には
 http://xxx.myserver.com/abc.php?country=AAA&pref=BBB&city=CCC
と内部的に書き直し(ReWrite)してから処理をする、ということになります。これが URL リライトです。

HTTP サーバーに Apache HTTPD を使っている場合は、この URL リライト機能が標準モジュールになっていて、mod_rewrite モジュールを有効にして、httpd.conf や .htaccess に変換ルールを記述するだけで URL リライトが実現できます。 ではウェブサーバーに Apache Tomcat など Apache HTTPD を経由しない Java アプリケーションサーバーを使っている場合のリライト処理はどうすればいいでしょう??


その答の1つが UrlRewriteFilter です。jar ファイル1つをプロジェクトに組み込み、専用の XML ファイルに変換ルールを記述することで、Java アプリケーションサーバーでも mod_rewrite のようなリライト処理を実現できるようになります:
urlrewritefilter00


実際に使ってみたサンプルを紹介します。まずは公式ページから urlrewritefilter-4.0.3.jar ファイルと、サンプルの urlrewrite.xml ファイルをダウンロードして保存します。
urlrewritefilter01


Java アプリケーションのプロジェクト内にこれらのファイルを配置します。まず urlrewritefilter-4.0.3.jar は WEB-INF/lib/ に、urlrewrite.xml は WEB-INF にそれぞれ保存します。次に web.xml を編集します:
urlrewritefilter02

WEB-INF/web.xml の </web-app> の直前に次の内容を追加して、このウェブアプリケーション内の全ての URL パターン(/*)に UrlRewriteFilter フィルターを適用することを宣言します:
  :
  :
<filter>
  <filter-name>UrlRewriteFilter</filter-name>
  <filter-class>org.tuckey.web.filters.urlrewrite.UrlRewriteFilter</filter-class>
</filter>
<filter-mapping>
  <filter-name>UrlRewriteFilter</filter-name>
  <url-pattern>/*</url-pattern>
  <dispatcher>REQUEST</dispatcher>
  <dispatcher>FORWARD</dispatcher>
</filter-mapping>

</webapp>

実際の変換パターンは WEB-INF/urlrewrite.xml ファイルの <urlrewrite> 要素内に記述します。以下の例では2つの変換パターンを記載しています:
<urlrewrite>
  :
  :
  <rule>
    <from>/urlrewrite/(.*)/(.*)/(.*)</from>
    <to>/urlrewrite?x=$1&y=$2&z=$3</to>
  </rule>
  <rule>
    <from>/urlrewrite/(.*)/(.*)</from>
    <to>/urlrewrite?a=$1&b=$2</to>
  </rule>
</urlrewrite>

前者では /urlrewrite/ 以下に / をセパレータとして3つのパラメータを受け取る、つまり
 /urlrewrite/XXX/YYY/ZZZ 
のような URL 指定があった場合に、内部的に
 /urlrewrite?x=XXX&y=YYY&z=ZZZ
のようにリライトして処理する、という変換パターンを記載しています。

後者では同様に2つのパラメータを受け取った時、つまり
 /urlrewrite/AAA/BBB
のような URL 指定があった場合に、内部的に 
 /urlrewrite?a=AAA&b=BBB
のようにリライトして処理する、という変換パターンを記載しています。

#上記例ではパラメータが2つの時と3つの時とで、異なるパラメータ変数に渡すようにしています。
 もちろんこの部分は定義次第です。


後はこれらのパラメータ(リライト後のパラメータ)に対応して処理するような /urlrewrite を(サーブレットなどで)実装すればよい、ということになりますね。




 

ウェブサーバー(HTTP サーバー)を使う場合、なんらかのアクセスログを残すことになります。例えば Apache HTTPD の場合、デフォルトでアクセスログは /var/log/httpd/access_log に残ります。そのフォーマットは /etc/httpd/conf/httpd.conf 内で設定/カスタマイズできます。
2015012200


デフォルトの httpd.conf では、以下の様なログフォーマットが指定されています:
LogFormat "%h %l %u %t \"%r\" %>s %b common

暗号のような記述ですが、左から順にこのような意味があります:
 %h : 接続元のリモートホスト名(IPアドレス)
 %l : クライアント識別子(取得できない場合は - )
 %u : 認証時のユーザー名(取得できない場合は - )
 %t : 時刻
 %r : リクエストの最初の行の値
 %s : レスポンスステータス
 %b : 送信バイト数(0バイトの場合は - )

このログフォーマットであれば、以下の様なログが取得できることになります:
192.168.1.101 - - [20/Jan/2015:11:32:29 +0900] "GET / HTTP/1.1" 200 -


最初の値がアクセス元のホストになります。つまりこれで(プロクシが使われている場合はプロクシのアドレスになりますが)どこからのアクセスがあったのか、という情報が分かります。


ところが、このアクセス元ホストが分からなくなる場合があります。それは HTTP サーバーがロードバランサ経由で使われていた場合です。サーバーの負荷軽減などの目的で、サーバーを複数台構成にしてロードバランサ経由で利用する、というケースは珍しくないと思います(クラウド環境によっては1台構成でもロードバランサ経由になることもあります)。ただその場合、この設定だとアクセス元ホストは必ずロードバランサの IP アドレスになってしまい、結果として常に同じホストからのアクセスがあったとログに記録されることになります。

これを避けるには httpd.conf をカスタマイズする必要があります。具体的にはこんな感じに:
LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %>s %b common


%h の代わりに %{X-Forwarded-For}i という記述に変えました。この意味ですが、まず %{XXX}i という記述は「リクエストヘッダに含まれるヘッダー名 XXX の値」です。つまりこの記述は「リクエストヘッダに含まれる X-Forwarded-For ヘッダの値」をログに書き出すよう指示していることになります。

そしてリクエストヘッダの X-Forwarded-For ですが、これはプロクシやロードバランサなどのキャッシングサーバーに接続するクライアントの送信元 IP アドレスです。つまりキャッシングサーバーから見た時の %h の値がこのヘッダ値に入ってくるので、その値を取り出してログに記録すればよい、ということになるわけです。 そこで上記のような設定をすることでロードバランサ経由であっても、元のクライアントの IP アドレスを記録できるようになるのでした。

なお、この方法は HTTP プロトコルに限って有効です。HTTPS では暗号化によって HTTP ヘッダに送信元 IP アドレスが記録できるかどうか、ロードバランサによって変わってきてしまいます。お使いのロードバランサが HTTPS をサポートしているか、サポートしていない場合の対応方法などはお使いのクラウド業者に問い合わせる必要があると思っています。



 

Apache HTTPD でバーチャルホストを使う設定手順の紹介です。一応 CentOS 6 環境を前提としますが、多くのケースで他の環境にも応用できる情報だと思っています。


バーチャルホストは、1つのウェブサーバー(Apache HTTPD)で複数のウェブサイトを運用するものです。 「複数のウェブサイト」の考え方ですが、例えば以下の例のようにディレクトリ単位で分けるのであればバーチャルホストを使わなくても1つのウェブサーバーで(ドキュメントルート以下のディレクトリを分けるだけで)運用できます:
 http://www.xxxxxx.com/host1/  (ホスト1)
 http://www.xxxxxx.com/host2/  (ホスト2)
 http://www.xxxxxx.com/host3/  (ホスト3)

でも以下のような、ディレクトリの違いではなく、ホスト名やサブドメインの違いによる複数のウェブサイトを1台のウェブサーバーで運用するにはバーチャルホストの設定が必要です:
 http://www1.xxxxxx.com/  (ホスト1)
 http://www2.xxxxxx.com/  (ホスト2)
 http://www3.xxxxxx.com/  (ホスト3)

上記のようなケースでのバーチャルホストの設定方法を紹介します。なお、前提条件として以下のような条件とします:
(1) DNS は設定済み(www1.xxxxxx.com, www2.xxxxxx.com, www3.xxxxxx.com いずれにも同じ IP アドレスが設定されていて、これらの名前全てでバーチャルホストを設定するマシンが参照できる)
(2) ユーザーが http://www1.xxxxxx.com/ にアクセスしてきたらドキュメントルート1(/var/www/html/vh1/)以下に誘導する
(3) ユーザーが http://www2.xxxxxx.com/ にアクセスしてきたらドキュメントルート2(/var/www/html/vh2/)以下に誘導する
(4) ユーザーが http://www3.xxxxxx.com/ にアクセスしてきたらドキュメントルート3(/var/www/html/vh3/)以下に誘導する
(5) ユーザーが IP アドレスなど上記以外の名前で http アクセスを行ってきたらエラーとする(ホスト名でしか参照できなくする)


まず最初に (1) の名前解決についての設定をしておきます。本当にドメイン(ここでは xxxxxx.com としますが、他のドメインの場合は読み替えてください)やグローバルIPアドレスを取得していて、www1.xxxxxx.com / www2.xxxxxx.com / www3.xxxxxx.com いずれも世界中から参照できる IP アドレスに結びつけることができるよう DNS 設定が可能な場合はそのように設定しておいてください。 一方、実際にはドメインを取得しておらず(或いは取得しているドメインは使わず)あくまでテスト目的でバーチャルホスト環境を構築するのであれば、/etc/hosts ファイルを編集して以下の赤字部分を追記します:
127.0.0.1      localhost localhost.localdomain www1.xxxxxx.com www2.xxxxxx.com www3.xxxxxx.com

この赤字部分を追記することで、とりあえずこのマシン上からは www1.xxxxxx.com / www2.xxxxxx.com / www3.xxxxxx.com いずれの名前でも 127.0.0.1 に結びつきました。つまり、このマシン上からは www1.xxxxxx.com / www2.xxxxxx.com / www3.xxxxxx.com の名前でアクセスできる環境が整いました。このマシン以外からの動作確認を行う場合は 127.0.0.1 ではなく、実際の IP アドレスに紐付けるように hosts ファイルを編集してください。


名前解決の準備ができたら Apache HTTPD サーバーをインストールします。CentOS であれば以下のコマンドでインストールできます:
# yum install httpd

ではここからバーチャルホストの設定を行います。まずは3つの運用ホストそれぞれに対応するドキュメントルートディレクトリを作成しておきましょう:
# mkdir /var/www/html/vh1
# mkdir /var/www/html/vh2
# mkdir /var/www/html/vh3

次に Apache HTTPD サーバーの設定ファイルをカスタマイズします。/etc/httpd/conf/httpd.conf を編集して、サーバー名の指定があればコメントし、またバーチャルホストの有効化を指定します:
  :
#ServerName xxxxxx.com:80  ←ServerName をコメント
  :
  :
  :
NameVirtualHost *:80  ←NameVirtualHost のコメントを外してバーチャルホスト有効化
  :

そして各ホストや設定内容に合わせた設定ファイルを追加していきます。上記の設定 (2) を実現するための設定ファイルを /etc/httpd/conf.d/virtualhost-vh1.xxxxxx.com.conf として作成(新規追加)します。内容は以下のようにします:
(/etc/httpd/conf.d/virtualhost-vh1.xxxxxx.com.conf)
<VirtualHost *:80%>
  ServerName www1.xxxxxx.com
  DocumentRoot /var/www/html/vh1
  ErrorLog logs/vh1-error_log
  CustomLog logs/vh1-access_log combined env=!no_log
</VirtualHost>

この中で http://www1.xxxxxx.com/ でアクセスがあった場合のドキュメントルートが /var/www/html/vh1 であることや、HTTPD のエラーログ/アクセスログのファイル名を指定しています。

同様にして、上記設定 (3), (4) のための設定ファイルを /etc/httpd/conf.d/virtualhost-vh2.xxxxxx.com.conf と /etc/httpd/conf.d/virtualhost-vh3.xxxxxx.com.conf をそれぞれ以下の内容で新規に作成します:

(/etc/httpd/conf.d/virtualhost-vh2.xxxxxx.com.conf)
<VirtualHost *:80%>
  ServerName www2.xxxxxx.com
  DocumentRoot /var/www/html/vh2
  ErrorLog logs/vh2-error_log
  CustomLog logs/vh2-access_log combined env=!no_log
</VirtualHost>
(/etc/httpd/conf.d/virtualhost-vh3.xxxxxx.com.conf)
<VirtualHost *:80%>
  ServerName www3.xxxxxx.com
  DocumentRoot /var/www/html/vh3
  ErrorLog logs/vh3-error_log
  CustomLog logs/vh3-access_log combined env=!no_log
</VirtualHost>

最後に上記設定 (5) のための設定ファイルを追加します。ここではサーバー名でのアクセスがあった場合のみ適合するドキュメントルートを参照させていますが、IP アドレスを指定してアクセスがあった場合はエラーとするように決めています。その内容を設定するために以下の内容で /etc/httpd/conf.d/virtualhost-00.conf ファイルを新規に作成します:
(/etc/httpd/conf.d/virtualhost-00.conf)
<VirtualHost *:80%>
  ServerName any
  <Location>
    Order deny,all
    Deny from all
  </Location>
</VirtualHost>

これで設定準備そのものは完了です。最後の準備として動作確認用に3つのドキュメントルートそれぞれに簡単な index.html ファイルを作成して、バーチャルホストが有効になっていることががテスト時に分かるようにします:
# echo "VirtualHost 1" > /var/www/html/vh1/index.html

# echo "VirtualHost 2" > /var/www/html/vh2/index.html

# echo "VirtualHost 3" > /var/www/html/vh3/index.html

これで目的のバーチャルホストを実現するための準備は完了です。最後に Apache HTTPD を再起動して、この準備内容を反映させます:
# /etc/init.d/httpd restart

動作確認は(ホスト名で区別するので) www1.xxxxxx.com / www2.xxxxxx.com / www3.xxxxxx.com の名前でこのホストの IP アドレスが引ける環境から行う必要があります。上記のように /etc/hosts の 127.0.0.1 で設定したのであれば Apache HTTPD サーバーと同じマシンからブラウザを起動して行います。

まずは http://www1.xxxxxx.com/ の場合。ちゃんと期待通りの内容が表示できてます:
2014120901


同様にして http://www2.xxxxxx.com/ や http://www3.xxxxxx.com/ の場合もそれぞれ正しいドキュメントルートを参照できているはずです:
2014120902

2014120903


最後に http://localhost/ (或いはhttp://IPアドレス) でアクセスしてみます。localhost で HTTPD サーバーが動いていることは間違いないのですが、ホスト名で指定されていないので(上記 (5) の設定が有効になっているので)エラー扱いとなるはずです:
2014120904


バーチャルホスト(とDNS)をうまく使うと、負荷やリソースとの兼ね合いもありますが、1台のサーバーで複数の HTTP ホストを運用できて便利です。


 

以前にこの記事を書きました:
CentOS に Apache Tomcat を導入する


上記ページの下の方で、Apache Tomcat を(一般的な HTTP のポートである)80 番ポートで実行するための指定方法も書き加えています。簡単に言うと、Apache Tomcat 自体は 8080 番ポートで動かしながら 80 番ポートへのリクエストを 8080 番に転送することで、外部からは 80 番ポートで動いているように見せる、というものでした。

もちろんこの方法は今でも有効で、比較的簡単に Apache Tomcat を 80 番ポートで動かすことができるようになります。ただ1つ、「事実上 80 番ポートを専有してしまう」という難点もあります。つまり実際には 8080 番ポートでしか動いていないのに、80 番ポートも含めて専有してしまうということです。

この結果、どんな不都合があるでしょう? 例えば Apache Tomcat が 8080 番ポートのままであれば、同じシステムに Apache HTTPD を導入して 80 番ポートで平行して動かすことができます。例えば PHP と MySQL と WordPress まで導入した上で、80 番ポートでは Apache HTTPD で WordPress を動かし、8080 番ポートでは Apache Tomcat で Java アプリケーションサーバーを同時に動かす、ということが可能でした。 上記の 80 番ポートを 8080 番ポートに転送するという方法を取ってしまうと、80 番ポートで Apache HTTPD を動かすことができなくなってしまいます。つまり2つの HTTP サーバーを同時に動かすことはできなくなってしまいます。

とはいえ、Apache Tomcat が 8080 番ポートのままですと、Java アプリケーションサーバーにアクセスさせるには
  http://xxx.xxx.xxx:8080/hello
という、一般的とは言えないちょっと冗長な URL をユーザーに指定させる必要が出てきます。普通の HTTP(80番)リクエストが許可されていない環境は珍しいかもしれませんが、8080 番ポートとなると会社レベルのファイアウォールが許可していない可能性も出てきます。


これを解決する方法もあります。例えば WordPress が
 http://xxx.xxx.xxx/wordpress
という URL で動いていて、同時に Java アプリケーションが
 http://xxx.xxx.xxx:8080/hello
という URL で動いている場合であれば、以下の様なルールを作ります:

・原則的には、全てのリクエストを Apache HTTPD (80番ポート)で処理する
・但し /hello という URL パターンだった場合のみ、Apache Tomcat に渡して処理してもらう

これにより、http://xxx.xxx.xxx/hello というリクエストがあった場合には内部的に http://xxx.xxx.xxx:8080/hello に転送※して Apache Tomcat 側で処理をする、という作業分担が可能になります。 また同時に全てのリクエストをパフォーマンスのいい Apache HTTPD がいったん処理した上で分担処理することになるとか、サーバーのファイアウォールは 80 番ポートのみ開けておけばよくなる(Apache Tomcat の 8080 番ポートと停止することもできる)、という副次的なメリットも生まれます。

※実際には 8080 番ではなく、内部的な処理をするために 8009 番ポートを使います


この作業分担を実現するのが Apache Tomcat に付属している AJP(Apache Java Protocol) です。AJP は 8009 番ポートを使います。

具体的な作業の前に Apache Tomcat の設定を元に戻しておきます。つまり上記リンク先のような 80 番ポートへのリクエストを 8080 番ポートに転送するような指定がされている場合は無効にして、普通に Apache Tomcat が 8080 番ポートで動いているような状態にします。

続いて /etc/httpd/conf.d/proxy_ajp.conf というファイルを、以下の内容で作成します:
ProxyPass /hello/ ajp://localhost:8009/hello/

これで Apache HTTPD を再起動(# /etc/init.d/httpd restart)します。すると 80 番ポートへのリクエストは Apache HTTPD が受けるのですが、この AJP の設定により /hello/ というパスへのリクエストに関しては 8009 番ポートへの /hello/ に変換されます。そして内部的な 8009 番ポートで待ち構えている Apache Tomcat の AJP によって処理されるようになります。

結果的に、ユーザーからは WordPress へは
  http://xxx.xxx.xxx/wordpress/
Java アプリケーションへは
  http://xxx.xxx.xxx/hello/
それぞれの URL で利用できるようになる、ということです。

なお、この記述をすることで Apache Tomcat を 8080 番ポートで動かす必要はなくなります。消してしまうのであれば Apache Tomcat の server.xml から該当箇所を削除してください。


注意点としては、Java アプリケーションへの転送を proxy_ajp.conf で記述することになるため、複数の Java アプリケーションがあって、その全てを転送したいのであれば、全てを proxy_ajp.conf に(複数行で)記述する必要が出てきます。

もう1点、実は私自身はこの AJP をあまり信用していないというか、AJP コンテナ自身がそれほどでもない負荷を捌ききれなかった経験をしています。原因調査についてはこちらの記事を参考にしていますが、AJP を使う前提での根本解決はできていないので、ちとヤな感じではあります:
http://m97087yh.seesaa.net/article/212767022.html









 

このページのトップヘ