Railsのアプリケーションを作ったはいいが、過負荷になった時にどうスケールしてよいか分かっていなかったので、検証環境を作って試してみることにした。高価なハードウェアの負荷分散装置なんて買って試すわけにもいかないので、Linuxの負荷分散システム(LVS)を利用して環境を構築してみることにする。用意する機器は以下となる。
・Railsのアプリケーションが動作するPC 2台
・負荷分散(LVS)を実装するPC 1台
・DBサーバー用PC 1台
・検証用PC1台
・ブロードバンドルーター1台
実際には仮想化環境なんかも使えるので、三台ぐらいPCがあれば、なんとかなります。動きゃいいという前提ですので、古いPCでも全然OK。ネットワーク構成は以下のような感じ。ごく普通の市販のブロードバンド配下にローカルネットワークが構成されていて、その内の1台がLVS(PCLVS)、残り2台がRailsサーバー(PCRails1、PCRails2)、RailsサーバーのどちらかにDBサーバー機能(PCRails1)も付与する。
ローカルLAN以外の環境に検証用のPCを1台(PCTest)を配置するが、実際には検証用PCもローカルLAN内に配置して、SoftEtherVPNを使ってローカルネットワーク外からアクセスする環境を作ります。

具体的なテストは、検証用PCから ブロードバンドルーターに割り当てられたGlobalIPに向けて HTTPリクエストを送り、ブロードバンドルーターに設定されたNAT情報に基づきリクエストがLAN内のPCLVSへ転送される。PCLVSでは、設定されたアルゴリズム、転送先に基づきリクエストを転送する。最終的にリクエストを処理するRailsサーバー(PCRails1 or PCRails2)は、通常通りリクエストを処理し、PCLVSを介さずに直接検証用クライアントへレスポンスを返す、という流れです。
では、それぞれのPCにセットアップしたOSや設定情報なんかを記載していきたいと思います。
ブロードバンドルーターの設定
ブロードバンドルーターの設定画面にログインし、80番ポートへやってきたリクエストを、PCLVS(192.168.1.100)へ転送するように設定する。既に80番ポートを別の用途で利用している場合などは、1080とか2080とか適当な公開ポートを設定し、そのポートへ来たリクエストを PCLVSの80番に転送するように設定する。設定が出来たら、たいていルーターの再起動が必要なので、再起動してやってください。
Railsサーバーの設定
Railsの実行環境は Ubuntuで実装した。これは別にWindowsでも構いませんが、後でちょっと小細工をしますので、Linux系の方がよろしいかと。
最終的には Passengerなどを使ってApacheにホストさせたRailsアプリにアクセスさせたいが、いきなりそこまで行くにはちょっと敷居が高いので、Apacheを入れて、テストページを表示させて、負荷分散処理の成否を確認したいと思います。Railsサーバーへの負荷分散は後述します。
[bash]
$ sudo nano /var/www/health_check.html
中身は以下
Health Check Page Alive Host portage(Apache PCRails1)
[/bash]
HTMLの中身はApacheにアクセスしたこと、ホスト名がPCRails1であることが、理解できれば何でもOKです。
ついでにRailsのテストページも作っておきます。
(Railsのプロジェクトフォルダが、/home/yourname/workspace/railsapp/ )
[bash]
$ sudo nano /home/yourname/workspace/railsapp/public/health_check.html
中身は以下
Health Check Page Alive Host portage(Rails PCRails1)
[/bash]
HTMLの中身はRailsにアクセスしたこと、ホスト名がPCRails1であることが、理解できれば何でもOKです。
この2つのファイルを PCRails1とPCRails2に作成してください。
で、apacheを再起動します。
[bash]
sudo apache2ctl restart
[/bash]
Linux負荷分散(LVS) の設定 〜 ipvsadm
今回は CentOS 6.5を利用しましたので、その手順を記載します。
・まずは、ごく普通にMinimalインストールなんかでOSのセットアップを行なってください。私はVirtualBoxのVM環境にCentOSをセットアップしました。HDDは3G、RAM512MBぐらいで十分です。
NIC eth0 を eth0 と eth0:0 に分割し、それぞれに固定IPアドレスを割り当てます。
[diff mark=”3,10″]
# nano /etc/sysconfig/network-script/ifcfg-eth0
DEVICE=eth0
HWADDR=08:00:27:55:66:B1
TYPE=Ethernet
UUID=ff461a31-7788-4bb7-9489-0b94053dcb78
ONBOOT=yes
NM_CONTROLLED=yes
BOOTPROTO=static
IPADDR=192.168.1.99
NETMASK=255.255.255.0
GATEWAY=192.168.1.1
[/diff]
実際に外部からの仮想ターゲットとなるNIC eth0:0 の設定
[diff mark=”4,9″]
# nano /etc/sysconfig/network-script/ifcfg-eth0:0
DEVICE=eth0:0
TYPE=Ethernet
ONBOOT=yes
NM_CONTROLLED=yes
BOOTPROTO=none
IPADDR=192.168.1.100
NETMASK=255.255.255.0
GATEWAY=192.168.1.1
[/diff]
ネットワークを再起動し、ifconfigで、設定が正しく反映されているかを確認します。
[bash]
# /etc/rc.d/init.d/network restart
# ifconfig
eth0 Link encap:Ethernet HWaddr 08:00:27:55:66:B1
inet addr:192.168.1.99 Bcast:192.168.1.255 Mask:255.255.255.0
inet6 addr: fe80::a00:27ff:fe41:34b1/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:111269 errors:0 dropped:0 overruns:0 frame:0
TX packets:140565 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:25255122 (24.0 MiB) TX bytes:18048699 (17.2 MiB)
eth0:0 Link encap:Ethernet HWaddr 08:00:27:41:34:B1
inet addr:192.168.1.100 Bcast:192.168.1.255 Mask:255.255.255.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:16436 Metric:1
RX packets:341 errors:0 dropped:0 overruns:0 frame:0
TX packets:341 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:24690 (24.1 KiB) TX bytes:24690 (24.1 KiB)
[/bash]
では、負荷分散機能を提供する ipvsadmとkeepalived などをインストールしましょう。Keepalivedはバージョン違いで動作に不具合が出ることもあるそうですが、CentOS 6.5の環境ではデフォルトのrpmで問題なく動作しました。
[bash]
# yum -y install ipvsadm keepalived iproute curl
[/bash]
インストールが終わったら以下のコマンドで負荷分散設定を確認します。
当然、何もセットされていません。
[bash]
# ipvsadm -Ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
[/bash]
仮想ターゲットNIC(192.168.1.100) にアルゴリズム rr(ラウンドロビン)を設定し、負荷分散をONにします。
[bash]
# ipvsadm -A -t 192.168.1.100:80 -s rr
[/bash]
実際のサービスを提供するリアルサーバー(Railsサーバー、PCRails1とPCRails2)のIPアドレスを DRで登録します。
[bash]
# ipvsadm -a -t 192.168.1.100:80 -r 192.168.1.22:80 -g
# ipvsadm -a -t 192.168.1.100:80 -r 192.168.1.25:80 -g
[/bash]
以下のコマンドで、設定が有効になっているか確認してください。
[bash]
# ipvsadm -Ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 192.168.1.100:80 rr
-> 192.168.1.22:80 Route 1 1 0
-> 192.168.1.25:80 Route 1 1 0
[/bash]
最初は ipvsadmで転送ポートまで指定できるのかと思っていましたが、いろいろ試した結果、うまく動作しません。とりあえず、今はPort80に来たリクエストを素直にリアルサーバーのPort80へ転送するという事にしておきます。
検証用PCの設定
モバイル環境などがあれば、直接その環境を利用してテストすればOKです。モバイル環境が手元にない場合は、LAN内に配置した検証用PCにSoftEtherVPNをインストールし、VPN Gate経由で擬似的にインターネット側からブロードバンドルーターのGlobal IPへアクセスします。
セットアップ方法はたいして難しくもないので、本家サイトのマニュアルなんかを参照ください。
では、検証用PCのコンソールを起動し、以下のコマンドを発行してみます。
[bash]
$ curl http://100.3.4.5
[/bash]
残念ながら応答なしです。
原因は LVSであるCentOSでパケットフィルタリングがかかっていて、80番Portがふさがっているためです。LVSのコンソールから、以下のコマンドを実行し、ポートを開放します。
[bash]
# nano /etc/sysconfig/iptables
以下の2行を追加
-A INPUT -m state –state NEW -m tcp -p tcp –dport 80 -j ACCEPT
-A INPUT -m state –state NEW -m tcp -p tcp –dport 443 -j ACCEPT
COMMIT
設定を反映
# /etc/rc.d/init.d/iptables restart
[/bash]
更にパケットフォワードの設定をしておきます。
[bash]
# nano /etc/sysctl.conf
以下の2行を追加
net.ipv4.ip_forward = 1
net.ipv4.conf.eth0.rp_filter = 0
設定を反映
# sysctl -p
[/bash]
さぁ、検証用PCのコンソールで以下のコマンドを発行してみます。
[bash]
$ curl http://100.3.4.5
Health Check Page Alive Host portage(Apache PCRails1)
$ curl http://100.3.4.5
Health Check Page Alive Host portage(Apache PCRails2)
[/bash]
コマンドを発行するたびに、別々のサーバーから応答が帰ってきていることが確認できました!!
これで最低限の負荷分散機能は完成です。
Linux負荷分散(LVS) の設定 〜 keepalived
既に keepalivedのインストールは完了しているので、設定ファイルの変更を行います。
[bash]
# nano /etc/keepalived/keepalived.conf
[/bash]
1つのファイルですが、前半部分と後半部分に分けて説明します。
前半
■LVSに対しての設定
・LVSの種別・・・MasterではなくBackupに
・負荷分散対象となるNICインターフェイス・・・ eth0
・負荷分散対象となるIPアドレス・・・ 192.168.1.100
[diff mark=”14-15,24″]
! Configuration File for keepalived
global_defs {
notification_email {
acassen@firewall.loc
}
notification_email_from Alexandre.Cassen@firewall.loc
smtp_server localhost
smtp_connect_timeout 30
router_id LVS_DEVEL
}
vrrp_instance VI_1 {
state BACKUP
interface eth0
virtual_router_id 51
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass 1111
}
virtual_ipaddress {
192.168.1.100
}
}
[/diff]
後半
ここでLVSに対しての設定と、リアルサーバーに対しての設定を行います。
■LVSに対しての設定
・負荷分散対象となるIPアドレス 192.168.1.100
・負荷分散対象となるポート 80
・ヘルスチェック間隔 6 秒
・負荷分散のアルゴリズム rr (ラウンドロビン)
・負荷分散の種類 DR (ダイナミックルーティング)
・ごめんなさいサーバー 192.168.1.100:1080 LVS自体にapacheを導入し、そこにメンテナンス中を示すHTMLを作成
・
■リアルサーバーに対しての設定
・リアルサーバーのIPアドレス 192.168.1.22 or 192.168.1.25
・リアルサーバーのポート 80
・重み付け 1
・応答なしの時はweightをゼロに inhibit_on_failure
・ヘルスチェックに用いるプロトコル HTTP_GET
・ヘルスチェック対象となるURL /health_check.html
・ヘルスチェック対象となるポート 3000
[diff mark=”1,2,3,4,8,10,11,12,15,18″]
virtual_server 192.168.1.100 80 {
delay_loop 6
lb_algo rr
lb_kind DR
#persistence_timeout 5
protocol TCP
sorry_server 192.168.1.100 1080
real_server 192.168.1.25 80 {
weight 1
inhibit_on_failure
HTTP_GET {
url {
path /health_check.html
status_code 200
}
connect_port 3000
connect_timeout 3
}
}
real_server 192.168.1.22 80 {
weight 1
inhibit_on_failure
HTTP_GET {
url {
path /health_check.html
status_code 200
}
connect_port 3000
connect_timeout 3
}
}
}
[/diff]
KeepAlivedは LVSPCからリアルサーバーに対して HTTP_GETでヘルスチェックを行います。この時Railsが生きているかどうかを確認したいので connect_port に 3000を指定して、Railsのプロジェクトフォルダ/public/health_check.htmlにアクセスします。Passengerを使っているときは 80番ポートのままで良いので、ヘルスチェック対象となるページのURLをPassengerで定義したApacheの仮想フォルダとしてやればOKでしょう。
ipvsadmとkeepalivedの再起動 (順番守ってください)
[bash]
# /etc/rc.d/init.d/ipvsadm restart
# /etc/rc.d/init.d/keepalived restart
[/bash]
リアルサーバーの設定調整
これでほぼOKなのですが、この設定のままでは、LVSPC(192.168.1.100:80)に飛んできたパケットがそのままリアルサーバーに転送されることになり、リアルサーバー側で自分宛(192.168.1.22 or 192.168.1.25)のパケットではないという理由で破棄されてしまうことがあります。これを防ぐためにリアルサーバー側に細工をしてやります。
[bash mark=”1,2″]
$ sudo iptables -t nat -A PREROUTING -d 192.168.1.100 \
-p tcp –dport 80 -j REDIRECT –to-port 3000
[/bash]
実際に何をやっているかというと、宛先が LVSPC(192.168.1.100:80)のパケットを 自分宛のパケットとみなし port 3000へリダイレクトするという事を行なっています。
ここでようやく 80番ポートではない別のポートへ処理を転送できました。
Railsの公開にはPassenger等を利用することが前提で、Passengerなら 80番ポートでOKなのですが、テストなので WEBrickで動作しているRailsのポートへパケットを転送してやることにより、負荷分散を実現します。
この設定はリブートのたびになくなりますので、どこかに仕込んでおいてやってください。Ubuntuでiptablesの設定ノウハウがないので、あしからず。
追記
arpの設定をします。詳しくはよくわかっておりません。今後研究します。
[bash]
# sudo nano /etc/sysctl.conf
以下を追記
net.ipv4.conf.lo.arp_ignore = 1
net.ipv4.conf.lo.arp_announce = 2
net.ipv4.conf.all.arp_ignore = 1
net.ipv4.conf.all.arp_announce = 2
設定を反映
# sudo sysctl -p
[/bash]
動作検証1
負荷分散の状況は以下のコマンドで確認できます。
[bash]
# ipvsadm -Ln –stats
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Conns InPkts OutPkts InBytes OutBytes
-> RemoteAddress:Port
TCP 192.168.1.100:80 2 12 0 966 0
-> 192.168.1.22:80 0 0 0 0 0
-> 192.168.1.25:80 2 12 0 966 0
[/bash]
片方のサーバーを殺してしばらく待ってみてから、以下のコマンドを実行すると、死んでいるサーバーのWeightが0になっていることが確認できます。
(死んだサーバーを起こしてやると、めでたく1に戻ります。)
[bash]
# ipvsadm -Ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 192.168.1.100:80 rr
-> 192.168.1.22:80 Route 1 0 0
-> 192.168.1.25:80 Route 0 0 0
[/bash]
また、実際に検証用クライアントから接続テストを行い、片方が死んでいる場合は、生きている方のサーバーへのみアクセスが行われることを確認してみてください。
動作検証2
全てのリアルサーバーがダウンしている場合、今のままでは応答なしとなってしまいます。ここで、keepalived.confに設定した sorryserverの設定を活かしてみましょう。sorryserverはとりあえず、LVSにapacheを導入し、そこにメンテナンス中を示すページを作ることで対応します。また LVSの80は利用されているので、とりあえず別のポート(1080)で公開することにします。
LSVPCにて
[bash]
# mkdir /var/www/sorry
# nano /var/www/sorry/index.html
只今メンテナンス中です
# chmod 755 /var/www/sorry/ -R
[/bash]
バーチャルサーバーの設定
[diff mark=”3-5″]
# nano /etc/httpd/conf.d/virtual.conf
Listen 1080
DocumentRoot /var/www/sorry
ServerName xxxxxxx
ServerAlias xxxxxxx
ErrorLog logs/sorry-error_log
CustomLog logs/sorry-access_log common
[/diff]
apache再起動
[bash]
# /etc/rc.d/init.d/httpd restart
[/bash]
実際にリアルサーバーを全てダウンさせて、メンテナンス中ページが表示されるか確認してみてください!
以上 お疲れ様でした。
Leave a comment