Apacheによるバーチャルホスト構築レシピ mod_vhost_alias/mod_lua編
ども、大瀧です。
小規模なWebサイトの運用は、「お金はかけられないけど、ハードウェア障害などでサイトが落ちると困る」というように、運用コストが問題になりやすいITシステムではないでしょうか。そんなシステムへのソリューションとして、運用コストを削減できるクラウドの利用とWebサーバー集約は案件として最近よく耳にします。
AWSでのWebサイト集約はAmazon S3のStatic Website Hostingが挙げられますが、BASIC認証に対応しておらず企業や会員向けサイトなどでは要件が満たせないことがあります。そこで今回は、EC2でApache httpd(以下Apache)を実行し、Webサーバーの集約としてバーチャルホストを構築するレシピをご紹介します。
Apacheのバーチャルホストのキホンと限界
Apacheのネームベースのバーチャルホストは、NameVirtualHostディレクティブ *1および<VirtualHost>ディレクティブで設定できますが、これらの情報はWebに溢れているため今回は敢えて書きません!これらの方法だと、バーチャルホストの数だけディレクティブを定義しなければならないため、運用面で以下の問題があります。
- バーチャルホストの設定を追加する度に、Apacheの再起動が必要
- バーチャルホストの設定がひとつでも間違っているとApache自体が起動しないため、全サイトが動かなくなる
サイト数が増えてくると、恐怖の運用が待っていることがイメージできると思います。そこで、再起動の必要がない方法を模索しました。
mod_vhost_aliasモジュール
mod_vhost_aliasモジュールは、Apache 2.2からサポートされている比較的新しいモジュールです。モジュールを有効にすると、VirtualDocumentRootディレクティブでバーチャルホストごとに異なるドキュメントルートを設定できます。ドキュメントルートのパスには変数を含めることができ、%0がドメイン名に置換されます。Amazon Linuxのhttpd24パッケージであれば、既定でmod_vhost_aliasモジュールが有効なので、例えば/etc/httpd/conf.d以下に設定ファイルを追加し構成できます。
$ sudo vim /etc/httpd/conf.d/vhost.conf UseCanonicalName Off VirtualDocumentRoot /var/www/vhosts/%0/html $ sudo service httpd restart Stopping httpd: [ OK ] Starting httpd: [ OK ] $
これにより、/var/www/vhost/<ドメイン名>/htmlがドメインごとのドキュメントルートとして動作します。適当なファイルを配置し、動作を確認してみます。
$ ls /var/www/vhosts/ example.com example.jp example.org $ cat /var/www/vhosts/example.com/html/index.html example.com $
今回は手元のMBAで検証するため、/etc/hostsに<EC2のPublic IP> example.comを追加し、ブラウザでアクセスしてみました。
ちゃんと表示されました!
これでコンテンツをドメインごとに分割できますが、mod_vhost_aliasモジュールではドキュメントルート以外にScriptAliasしか設定できないため、従来のバーチャルホスト機能と比較すると以下の問題点があります。
- アクセスログ、エラーログがバーチャルホスト単位で分割できない。
- バーチャルホストの対象となるドメインを絞り込めない。ドキュメントルートとなるディレクトリが存在しないドメインへのアクセスは、全て404 Not Foundになる。
あと一歩、かゆいところに手が届かないという印象です。ただ、設定自体はとても簡単ですし、Apacheの設定変更なしでドメインをどんどん増やしていけるので簡易バーチャルホストとしては強力な機能だと思います。
mod_luaモジュール
mod_luaは、mod_perlやmod_phpと同様にApacheでLua言語を実行するモジュールですが、今回はCGIとしての実行ではなく、Apacheの動作をフック、カスタマイズする用途で利用します。
動作確認環境
- OS : Fedora 20 64bit
- Apache : 2.4.7(ソースからインストール、理由は後述)
- Lua : 5.2.2(既定のRPMパッケージ)
こちらは、Vagrant + VirtualBoxで検証しました。Apache 2.4.7が動く環境であれば、AWS EC2でも同様でしょう。まずは、mod_luaを有効にしApacheをインストールします。
$ sudo yum install apr apr-util apr-devel apr-util-devel lua-devel $ wget http://ftp.riken.jp/net/apache//httpd/httpd-2.4.7.tar.bz2 $ tar jxf httpd-2.4.7.tar.bz2 $ cd httpd-2.4.7 $ ./configure --with-lua $ make $ sudo make install
続いて、mod_luaでApacheの動作をフックするハンドラを定義します。ハンドラ定義はApacheの動作のうちカスタマイズしたいタイミングによって、以下のディレクティブをApacheの設定ファイルに記述します。
ディレクティブ名 | 説明 |
---|---|
LuaQuickHandler | 最初に評価される |
LuaHookTranslateName | URIからファイル名を解釈するタイミング。mod_alias、mod_rewriteモジュール相当 |
LuaHookMapToStorage | 物理、キャッシュ、プロキシなどストレージを解釈するタイミング。プロキシやキャッシュ処理に相当 |
LuaHookAccessChecker | クライアントがリソースにアクセスできるかどうかを評価するタイミング。ユーザー認証の前に評価されることに注意 |
LuaHookCheckUserID | ユーザーIDを評価するタイミング |
LuaHookAuthChecker or LuaAuthzProvider | ユーザーIDなどでユーザー権限を認証するタイミング |
LuaHookFixups | コンテンツを処理する前の最後のタイミング |
LuaHookLog[Apache 2.4.7以降で動作] | コンテンツ処理後のログ処理のタイミング。 |
今回はバーチャルホストの動作としてURIの解釈とログ処理をフックするために、LuaHookTranslateNameディレクティブ、LuaHookLogディレクティブでそれぞれハンドラを登録します。FedoraやUbuntu、Amazon LinuxなどにバンドルされるApacheは2013/12/30現在 バージョン2.4.6がほとんどだったため、LuaHookLogディレクティブを使うためにソースからインストールしました。
バーチャルホストのディレクトリレイアウトは、以下にしてみました。
- ドキュメントルート : /usr/local/apache2/vhosts/<ドメイン名>/html/
- ログディレクトリ : /usr/local/apache2/vhosts/<ドメイン名>/logs/
/usr/local/apache2/conf/httpd.comf(末尾に以下を追加)
Include conf/extra/httpd-lua.conf
/usr/local/apache2/conf/extra/httpd-lua.conf
LoadModule lua_module modules/mod_lua.so # LuaHoot* <Luaスクリプトファイル名> <ハンドラ関数名> LuaHookTranslateName /usr/local/apache2/conf/lua/vhost.lua set_docroot LuaHookLog /usr/local/apache2/conf/lua/vhost-log.lua log_handler <Directory "/usr/local/apache2/vhosts/*/html"> Require all granted </Directory>
Luaスクリプト側の実装はとてもシンプルで、上記で定義したハンドラに対応するハンドラ関数をスクリプトに記述するだけです。実装のポイントは、ハンドラ関数の引数になるrequest_rec構造体とapache2パッケージです。
request_rec構造体
ハンドラ関数で受け取る引数に代入される構造体です。クライアントのリクエスト情報など、Apacheのデータを参照するために利用します。多くのコードサンプルでは変数rで受け取り、r.hostname(リクエストのホスト名)やr.uri(リクエストのURI)として参照します。詳細はmod_luaガイドにありますが、未実装でエラーになるのものもあることに注意します。
apache2パッケージ
実行しているApacheの情報が取れるパッケージです。現状はバージョン番号と、Apacheの動作をコントロールする以下の定数(これ重要)を参照できます。定数は、主に以下をハンドラ関数のreturn文に指定して使用します。
- apache2.OK : リクエスト処理の終了を示す。luaスクリプトで直接レスポンスを生成する場合に指定する
- apache2.DONE : LuaHookLogディレクティブでログ処理をフックする際に、OKの代わりに指定する
- apache2.DECLINED : Apacheの既定の動作を示す。"luaスクリプトではApacheの動作設定などを変更し、レスポンスはApacheのコンテンツを返す"というような場合に指定する
apache2パッケージの詳細はApacheドキュメントで参照できますが、そのページにあるサンプルスクリプトBuggyで動作しないものもある(バーチャルホストのサンプルはエラーになりました)ので、注意してください。
バーチャルホストの実装例
ではコードを示してみます。まずは、ドキュメントルートを変更するvhost.luaから。
/usr/local/apache2/conf/lua/vhost.lua
local vhosts = { "example.com", "example.jp" } -- 対応するハンドラ関数set_docrootの定義、request_rec構造体を変数rで受け取る function set_docroot(r) -- 配列vhostsのループ for key, domain in ipairs(vhosts) do -- r.hostnameにドメインが含まれる場合 if r.strcmp_match(r.hostname, domain) then local docroot = "/usr/local/apache2/vhosts/" .. domain .. "/html/" -- ドキュメントルートの変更 r:set_document_root(docroot) end end -- コンテンツはApacheで対応 return apache2.DECLINED end
続いて、ログをフックするvhost-log.luaを。
/usr/local/apache2/conf/lua/vhost-log.lua
local vhosts = { "example.com", "example.jp" } -- 対応するハンドラ関数log_handlerの定義 function log_handler(r) for key, domain in ipairs(vhosts) do -- バーチャルホストのドメインの場合、専用のアクセスログに出力 if r.strcmp_match(r.hostname, domain) then local f = io.open("/usr/local/apache2/vhosts/" .. domain .. "/logs/access_log", "a") if not r.user then r.user = '-' end if not r.headers_in['Referer'] then r.headers_in['Referer'] = '-' end -- Apache Combinedに似せたフォーマットで出力 f:write(r.useragent_ip .. " - " .. r.user .. " [" .. os.date("%d/%b/%Y:%H:%M:%S %z") .. "] \"" .. r.the_request .. "\" " .. r.status .. " - \"" .. r.headers_in['Referer'] .. "\" \"" .. r.headers_in['User-Agent'] .. "\"\n") f:close() -- アクセスログを出力したので、既定のアクセスログは出力しない return apache2.DONE end end -- 他のドメインは、既定のアクセスログに出力 return apache2.DECLINED end
アクセスログは、request_rec構造体からリクエストの情報を取り出してApacheのcombinedログに整形しています。レスポンスのバイトサイズは見当たらなかったので未実装としました。-を仮の値として入れています。それからエラーログも見つからなかったため、実装できていません。
$ cat /usr/local/apache2/vhosts/example.com/logs/access_log 10.0.X.X - - [30/Dec/2013:14:29:52 +0900] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.71 Safari/537.36" 10.0.X.X - - [30/Dec/2013:14:29:52 +0900] "GET /favicon.ico HTTP/1.1" 404 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.71 Safari/537.36" $
きっちりアクセスログとして書き出されていますね!
まとめ
mod_luaモジュールはステータスがまだexperimentalで、ドキュメントに記載があっても動作しない機能があったり発展途上な感もありますが、Apacheの動作をカスタマイズする手段として期待できる印象が持てました。今後もウォッチしていきたいと思います。
また、Apacheの拡張として最近ホットなmod_mrubyも、そのうち検証してみたいです。そういえば、Nginxだと標準機能で今回のmod_lua相当のバーチャルホストが構成できるようなので、そちらも試してみないと。
脚注
- Apacheバージョン2.4以降では定義不要になり、<VirtualHost>ディレクティブのみで動作します。 ↩