弊社インフラチームが使っているmuninをh2oのHTTP/2で高速化して、諸々の制御はmrubyでコントロールした。チームから「はやーい!」「スクロールで勝てない!」「こんな早い表示最近みたことなかったですよ!」と喜びの声が多数届いております。
— MATSUMOTO, Ryosuke (@matsumotory) 2015, 7月 30
h2oをリバースプロキシで使えば、http://
でのアクセスはHTTP/1.xでコンテンツを返しつつ、https://
からのアクセスについてはHTTP/2+TLSに対応したブラウザであればHTTP/2、対応していなければHTTP/1.xでコンテンツを返す事ができます。
また、h2oがmrubyによる機能拡張をサポートし、かつ、先日アクセス元IPアドレスをmruby内で参照することができるようになったので、IPアドレスを用いたアクセス制御もh2o+mruby側で簡単に制御できるようになりました。
今日はその設定例を簡単に紹介したいと思います。
H2Oの設定
h2oの設定は大体以下のように書くと良いでしょう。このサンプルでは、本来muninが動いていたApacheを127.0.0.1:8000
でListenするようにし、フロントにh2oを配置する状況を想定しています。
user: daemon pid-file: /var/run/h2o.pid access-log: /var/log/h2o/access_log error-log: /var/log/h2o/error_log hosts: "munin.example.jp": listen: 80 listen: port: 443 ssl: certificate-file: /etc/h2o/tls/server.crt key-file: /etc/h2o/tls/server.key paths: /: proxy.timeout.io: 300000 proxy.reverse.url: http://127.0.0.1:8000/ mruby.handler_path: /etc/h2o/mruby/h2o.rb /munin/: file.dir: /var/www/html/munin mruby.handler_path: /etc/h2o/mruby/h2o.rb
こんな感じで、staticなファイルが配置される/munin/
はh2oが直接フロントで返し、動的コンテンツはバックエンドのApacheが実行するので、そのままリバースプロキシするようにしています。
また、それぞれのリクエストにおいてmruby.handler_path
により、mrubyスクリプトh2o.rb
をフックするようにしています。
mrubyでIPアドレスベースのアクセス制御を実装
続いて、h2o.confで呼ばれいてる/etc/h2o/mruby/h2o.rb
を実装しましょう。この中で、IPアドレスベースのアクセス制御を実装します。
以下のようになります。
allow_seg_list = %w{ 192.168.1 192.168.2 192.168.3 192.168.11 192.168.12 192.168.13 } remote_seg = H2O::Connection.new.remote_ip.gsub(/\.\d{1,3}$/, "") if allow_seg_list.include? remote_seg H2O.return H2O::DECLINED else H2O.return 403, "Forbidden", "Forbidden" end
例えば、allow_seg_list
に書かれているセグメントからのみアクセスを許可する場合は上記のようにかけます。このmrubyでは正規表現を使うために、以下のようなbuild_config.rb
を用いてmrubyをビルドしています。
MRuby::Build.new do |conf| toolchain :gcc conf.gembox 'full-core' conf.gem :github => 'iij/mruby-io' conf.gem :github => 'iij/mruby-env' conf.gem :github => 'iij/mruby-dir' conf.gem :github => 'iij/mruby-digest' conf.gem :github => 'iij/mruby-process' conf.gem :github => 'iij/mruby-pack' conf.gem :github => 'mattn/mruby-json' conf.gem :github => 'mattn/mruby-onig-regexp' conf.gem :github => 'matsumoto-r/mruby-sleep' conf.gem :github => 'matsumoto-r/mruby-userdata' conf.gem :github => 'matsumoto-r/mruby-uname' conf.gem :github => 'matsumoto-r/mruby-cache' conf.gem :github => 'matsumoto-r/mruby-mutex' end
これをクローンしてきたmrubyリポジトリ内で以下のようにビルド環境にインストールします。
cd mruby && make && sudo cp build/host/lib/libmruby.a build/host/lib/libmruby_core.a /usr/lib/. && sudo cp -r include/* /usr/include/.
現在のh2oとmrubyは、ビルドを連携して処理することができないため、上記のように様々なmrbgemをリンクした場合は、h2oのビルドに追加で必要なフラグをh2oのCMakeLists.txt
に追記が必要になります。
今回は、mrubyリポジトリ内のbuild/host/lib/libmruby.flags.mak
を参考に、以下のようにCMakeLists.txt
にフラグを追記しました。
(snip) SET(CMAKE_C_FLAGS "-O2 -g -Wall -Wno-unused-function ${CMAKE_C_FLAGS} -DH2O_ROOT=\"\\\"${CMAKE_INSTALL_PREFIX}\\\"\" -L/home/vagrant/mruby/build/host/mrbgems/mruby-onig-regexp/onig-5.9.5/.libs") (snip) LIST(INSERT EXTRA_LIBRARIES 0 ${MRUBY_LIBRARIES} "m" "onig") (snip)
これを書いてから、以下のようにh2oをビルドすると、ちゃんとビルドできるはずです。
cmake28 -DWITH_MRUBY=ON . make h2o
H2Oの起動スクリプトやその他設定
いざh2oをサーバでちゃんと使っていこうとすると、以下のようなファイルが必要になります。
- 起動スクリプト
- logrotateの設定
- ディレクトリ構成
これらをざっと用意したので参考までに紹介します。
起動スクリプト
h2oはサーバユーティリティが同封されており、デーモンモードで動かす時はその中のstart_serverを使います。それらはh2oリポジトリの中のshare/h2o
の中に入っています。
これらを使う事で、gracefulの処理も簡単に行う事ができるので、それを以下のように起動スクリプトとしてwrapしました。
#!/bin/bash #chkconfig: 2345 85 15 #descpriction: h2o Web Server # source function library . /etc/rc.d/init.d/functions RETVAL=0 SERVICE_NAME=`basename $0` start() { echo -n $"Starting $SERVICE_NAME: " /usr/sbin/h2o -m daemon -c /etc/h2o/conf/h2o.conf RETVAL=$? if [ $RETVAL == 0 ]; then success else failure fi echo } stop() { echo -n $"Stopping $SERVICE_NAME: " kill -TERM `cat /var/run/h2o.pid` RETVAL=$? if [ $RETVAL == 0 ]; then success else failure fi echo } reload() { echo -n $"Graceful $SERVICE_NAME: " kill -HUP `cat /var/run/h2o.pid` RETVAL=$? if [ $RETVAL == 0 ]; then success else failure fi echo } case "$1" in start) start ;; stop) stop ;; reload|graceful) reload ;; restart) stop start ;; *) echo $"Usage: $0 {start|stop}" exit 1 esac exit $RETVAL
これを/etc/rc.d/init.d
以下において、
sudo chkconfig h2o on
などとしておきましょう。
logrotateの設定
上記の様に起動スクリプトを作り、かつ、h2oの設定通り/var/log/h2o/
以下にログを保存するようにしているので、以下のようなlogrotateの設定を書きます。
/var/log/h2o/*log { missingok notifempty sharedscripts delaycompress postrotate /sbin/service h2o graceful > /dev/null 2>/dev/null || true endscript }
これでうまくいけばログローテート時にgracefulが走るようになります。
ディレクトリ構成
そして、全体のディレクトリ構成を大体以下のようなシェルで用意します。
# h2oのバイナリと同封されているサーバユーティリティ群をコピー cp -v h2o /usr/sbin/. cp -rv share/h2o /usr/local/share/. # h2oの設定 mkdir -pv /etc/h2o/conf cp -v h2o.conf /etc/h2o/conf/. # HTTP/2をchorome等の有名ドコロブラウザから使おうとするとTLS必須なのでオレオレを準備 mkdir -pv /etc/h2o/tls cp -v server.crt server.key /etc/h2o/tls/. # IPベースのアクセス制御をmrubyで実装 mkdir -pv /etc/h2o/mruby cp -v h2o.rb /etc/h2o/mruby/h2o.rb # ログディレクトリを掘っとく mkdir -pv /var/log/h2o/ # h2oの起動スクリプトをコピーしてパーミッションも適切なものに cp -pv rc_script/h2o /etc/rc.d/init.d/. chown root.root /etc/rc.d/init.d/h2o chmod 755 /etc/rc.d/init.d/h2o # ログローテートの配置 cp -v logrotate/h2o /etc/logrotate.d/.
イメージとしては、大体こんな感じで上記で作ったファイルを配置していきます。
その他気をつける事
HTTP/1側でアクセスする分には特に問題ありませんが、HTTP/2でアクセスするとバックエンドのApacheが動的に処理するCGIなどが、ブラウザのセッション数に依存しない数の同時接続数が発生します。
そのため、画像生成するCGIがApacheで動いていた場合に、その画像数が一つのページに数十個あった場合は、HTTP/1.xアクセスだと大体同時には6セッション分ぐらい、つまり6並列ぐらいでしかCGIが実行されていなかったのに対し、HTTP/2だと一気に数十個のCGIが同時多発的に処理されることになります。
つまり、バックエンドのチューニングを見なおして、最低でもCGIを同時に並行で処理してもサーバが問題ない程度のMaxClientにしておくと、HTTP/2アクセスが沢山発生してもサーバがフルにリソースを使って処理することができるようになり、より快適にHTTP/2を体験できるかと思います。h2oに同封のfastcgi機能などを使えばこの限りではありませんが、今回は既存のApacheを簡単に置き換える事を想定しているので、その点についての言及は省略します。
CGIの処理の重さにもよりますが、muninのように外部に公開していないものの、一つのCGI実行がCPUコア1つを占有しがちな処理の場合には、サーバのコア数の2〜3倍程度のMaxClinetsぐらいにしておくとそれなりにバランスよく処理できていました。CGIの処理が重たければ重たい程、コア数に近い数でも良いぐらいでしょうか。
その結果、
弊社インフラチームが使っているmuninをh2oのHTTP/2で高速化して、諸々の制御はmrubyでコントロールした。チームから「はやーい!」「スクロールで勝てない!」「こんな早い表示最近みたことなかったですよ!」と喜びの声が多数届いております。
— MATSUMOTO, Ryosuke (@matsumotory) 2015, 7月 30
というような反応を頂いております。
まとめ
ということで、h2oとmrubyを使って既存のWebサイトをHTTP/2化しながらもmrubyで柔軟にアクセス制御する方法を紹介しました。また、その中でバックエンドのチューニングも、HTTP/2を考慮した場合には再設計し直す必要があるかもしれない、という点についても述べました。
是非このエントリを参考に、自身のブログやWebサイトをHTTP/2化してみてはどうでしょうか!