負荷分散環境で動作可能なWebアプリ(PHP/Laravel)の開発を考えたとします。「LaravelのセッションドライバをDBにしてみる」でも書きましたが、セッション管理の方法など1台のサーバでのみ運用する場合には発生しない課題を解決する必要が生じます。
上記課題に対して対処したとして、期待通りの結果が得られるかを確認するには当然ながらそれを検証できる、つまり負荷分散機能を持った動作検証環境が必要になります。既にそのような環境が存在する場合は良いですが、なかった場合に手軽に(費用をかけずに)環境を用意することはできないものか…と言うことで調べてみるとCentOS(Linux)上でL4ロードバランサと同様の機能を実現するためのソフトウェア「LVS」なる物がある模様。早速試してみることにしました。
「LVS」はIPVS(IP Virtual Server)とipvsadm(IPVSを制御するツール)と言う2つのソフトウェアから構成されるようですが、IPVSの方はカーネル(CentOS自体)に内包されているようなので、実際にはipvsadmをインストールし、必要な設定を行うことが本作業の趣旨になります。
なお、負荷分散の動作検証を行うためには最低でも1台のロードバランサと呼び出し対象となる2台のWebサーバが必要になりますが、これらはすべてVirtualBox/Vagrantで構築した仮想マシンを使用することにします。つまり実マシンとしてはPC1台あれば負荷分散の動作検証が追加費用なく実現できてしまうことになります。かなりお手軽!
今回構築する環境は以下のような運用を想定したものとします。
外部ネットワーク(インターネット)からアクセス可能なマシンはロードバランサのみとします。Webサーバ2台はロードバランサとともに1つの内部ネットワーク(LAN)に接続しており、外部からのアクセスをLAN経由で2台のWebサーバに振り分ける形にします。つまり、ロードバランサはNAT(ゲートウェイ)の役割も果たすことになります。
今回はVirtualBoxによる仮想マシンを使用しての環境構築であるため、public_network(ホストOS外からもアクセス可能)をインターネットに、private_network(ホストOS内でのみ相互にアクセス可能)をLANに見立てて環境構築します。
以上から各サーバに関する設定を以下のようにします。
【ロードバランサ】
OS : CentOS7
外部IP : 192.168.2.200(public_network)
内部IP : 192.168.33.100(private_network)
【Web1】
OS : CentOS7
内部IP : 192.168.33.111(private_network)
【Web2】
OS : CentOS7
内部IP : 192.168.33.112(private_network)
蛇足ながら、VirtualBox/Vagrantでの仮想マシン環境構築は「Virtualbox / vagrant のすゝめ」を、Webサーバ環境構築は「Webmin / Virtualmin のすヽめ」を参照してください。
Webサーバ環境構築
まずはWebサーバ側を構築してしまいます。「Virtualbox / vagrant のすゝめ」「Webmin / Virtualmin のすヽめ」に準じて環境構築を行い、ドキュメントルート直下にindex.phpを作成します。今回はそれぞれのサーバへのアクセスが適宜切り替えながら行われていることを確認したいだけなので、単にそれぞれのサーバ名を返すだけの処理にしておきます。
<?php
echo "Web1 called.";
上記はWeb1サーバの例であり、Web2サーバであれば当然ながら文字列内の「Web1」を「Web2」に変えます。
ブラウザからそれぞれのWebサーバへのアクセスを行い、前述の文字列が表示されるところまで確認しておきます。
Webサーバの設定自体は上記で十分なのですが、ロードバランサ経由でのアクセスを行うためには、もう一つ実施しておくべきことがあるようです。それはデフォルトゲートウェイの設定です。
Web1において以下のコマンドを実行します。
ip addr
以下のような結果が得られると思います。
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 52:54:00:4d:77:d3 brd ff:ff:ff:ff:ff:ff
inet 10.0.2.15/24 brd 10.0.2.255 scope global noprefixroute dynamic eth0
valid_lft 71689sec preferred_lft 71689sec
inet6 fe80::5054:ff:fe4d:77d3/64 scope link
valid_lft forever preferred_lft forever
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 08:00:27:bb:2d:8d brd ff:ff:ff:ff:ff:ff
inet 192.168.33.111/24 brd 192.168.33.255 scope global noprefixroute eth1
valid_lft forever preferred_lft forever
inet6 fe80::a00:27ff:febb:2d8d/64 scope link
valid_lft forever preferred_lft forever
Virtualbox/Vagrant環境では無条件にeth0(10.0.2.15)が定義されており、明示的に割り振ったprivate_networkのIPアドレス「192.168.33.111」はeth1として定義されています。
よって、以下のコマンドを実行し、eth1に対してデフォルトゲートウェイ「192.168.33.100」を設定します。
ip route add default via 192.168.33.100 dev eth1
結果は以下のコマンドで確認できます。
ip route
以下のような結果が得られると思います。
default via 192.168.33.100 dev eth1
default via 10.0.2.2 dev eth0 proto dhcp metric 100
10.0.2.0/24 dev eth0 proto kernel scope link src 10.0.2.15 metric 100
192.168.33.0/24 dev eth1 proto kernel scope link src 192.168.33.111 metric 101
Web2に関しても同様にデフォルトゲートウェイの設定を行います。
ロードバランサ環境構築
先に示したようにロードバランサ環境構築としてはipvsadmのインストールと設定を行います。言い換えればCentOS7とipvsadmがあれば良いのでWebmin/Virtualminのインストールは行いません。「Virtualbox / vagrant のすゝめ」に準じて仮想マシンの構築まで行います。
仮想マシンが構築できたらipvsadmをインストールします。具体的には以下の通り(yumって便利)。
yum -y install ipvsadm
インストールができたら、「IPフォワード」の設定を行います。
ロードバランサは2つのネットワーク間のゲートウェイとしても機能しますが、これは片方のネットワークから受信したパケットをもう一つのネットワークに転送すると言うような処理ができる必要があります。これが「IPフォワード」です。
デフォルトではIPフォワードが無効になっているため、有効にするよう設定を変更します。
まずは「/etc/sysctl.conf」に対して以下の記述を追加します。
net.ipv4.ip_forward=1
その後に以下のコマンドを実行すると設定変更が有効になります。
sysctl -p
次にipvsadmの設定を行います。
具体的には以下のコマンドを実行します。
ipvsadm -A -t 192.168.2.200:80 -s wlc
ipvsadm -A -t 192.168.2.200:443 -s wlc
ipvsadm -a -t 192.168.2.200:80 -r 192.168.33.111:80 -m
ipvsadm -a -t 192.168.2.200:80 -r 192.168.33.112:80 -m
ipvsadm -a -t 192.168.2.200:443 -r 192.168.33.111:443 -m
ipvsadm -a -t 192.168.2.200:443 -r 192.168.33.112:443 -m
1,2行目と3〜6行目で設定目的が異なります。
前者は本ロードバランサがIPアドレス「192.168.2.200」上の所定のポート(http(80)とhttps(443))に関する通信を負荷分散対象とすることを示しており、その際にwlc(重み付けの割合に従って接続数が一番少ないサーバーに接続させる)と言う分散方式を採用しています。wlcはデフォルトであり他にもいろいろな分散方式が実装されているようですが、今回はとりあえず2台のWebサーバに適宜アクセスが振り分けられることを確認したいだけなのでデフォルトの方式を採用しておきます。
後者はIP+ポートのセットに対して受信したパケットの転送先を設定しており、Web1(192.168.33.111)およびWeb2(192.168.33.112)に対してhttpとhttpsそれぞれの受信パケットを転送することを示しています。同一のIP+ポートに対して転送先が複数存在することで負荷分散が行われる訳です。なお、「-m」は「masquerading」つまりNAT方式を意味します。
上記設定が正しく行われたかどうかは以下のコマンドで確認できます。
ipvsadm -l
設定結果は以下のように表示されるかと思います。
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP localhost.localdomain:http wlc
-> 192.168.33.111:http Masq 1 0 0
-> 192.168.33.112:http Masq 1 0 0
TCP localhost.localdomain:https wlc
-> 192.168.33.111:https Masq 1 0 0
-> 192.168.33.112:https Masq 1 0 0
上記を実行したら以下のコマンドを実行して、設定内容を「/etc/sysconfig/ipvsadm」に保存します。
ipvsadm --save -n > /etc/sysconfig/ipvsadm
後述するipvsadmの機能時に「/etc/sysconfig/ipvsadm」の設定内容を読み込むようになっており、本ファイルが存在しないとipvsadmの起動が失敗します。よって起動に先行して上記ファイル生成を実施しておく必要があります。
なお「-n」オプションはIPやポートを数値で設定することを指定するものです。本オプションを指定しないと保存された「/etc/sysconfig/ipvsadm」において「192.168.2.200」が「localhost.localdomain」と言う表現になってしまっています。これが原因でLVSが「192.168.2.200」に対する受信待ちを正しく行えません。
ここまでの設定ができたら、ipvsadmを起動します。
systemctl start ipvsadm.service
動作確認
上記まで実行できたらブラウザから「192.168.2.200」に対してアクセスしてみます。
実行ごとにWeb1、Web2がランダムに切り替わって表示される…と思いきや、一旦どちらかのWebサーバに繋がってしまうと、しばらくリロードを繰り返しても同じWebサーバにしかアクセスしないようです。別ブラウザで同じことを試すと先とは異なるWebサーバに繋がることもあるのですが、そのブラウザではやはり同じWebサーバ側にアクセスし続けます。
なお、ブラウザからではなくcurlコマンドを使用して以下のように「192.168.2.200」へのアクセスを10回繰り返してみました。
for i in {0..9}; do curl http://192.168.2.200; done
こちらに関してはWeb1、Web2それぞれへのアクセスを交互に実施していました。
Web2 called.
Web1 called.
Web2 called.
Web1 called.
Web2 called.
Web1 called.
Web2 called.
Web1 called.
Web2 called.
Web1 called.
これが「wlc(重み付けの割合に従って接続数が一番少ないサーバーに接続させる)」を言う制御なのでしょうか。今一つはっきりしませんが、とりあえず1つのアドレスへのアクセスを複数のWebサーバへ分散させると言う本来の目的は達成できたようです。
おまけ
ipvsadm関連の設定時に「/etc/sysconfig/ipvsadm」に設定内容を保存しました。再起動後も同じ設定内容でLVSが運用されることを期待したのですが、今のところそのようになっていません。
ipvsadmのサービス設定(「/usr/lib/systemd/system/ipvsadm.service」の内容)は以下のようになっています。
[Unit]
Description=Initialise the Linux Virtual Server
After=syslog.target network.target
[Service]
Type=oneshot
ExecStart=/bin/bash -c "exec /sbin/ipvsadm-restore < /etc/sysconfig/ipvsadm"
ExecStop=/bin/bash -c "exec /sbin/ipvsadm-save -n > /etc/sysconfig/ipvsadm"
ExecStop=/sbin/ipvsadm -C
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
ipvsadmの停止時にその段階の設定内容を「/etc/sysconfig/ipvsadm」として再度保存し直すと言うことを行っているようです。
その方針自体は問題ないと思うのですが、上記処理結果「/etc/sysconfig/ipvsadm」の内容が空になってしまっています。
/bin/bash -c "exec /sbin/ipvsadm-save -n > /tmp/ipvsadm.tmp"
この処理自体を手動で実行すると設定内容を正しく保存できています。
/sbin/ipvsadm -C
上記実行は「/etc/sysconfig/ipvsadm」の内容に影響を与えません。
上記より、最新の設定内容を保存しつつ処理を停止すると言うことで問題なさそうなんですが、実際には「/etc/sysconfig/ipvsadm」が空になってしまいます。
今回はあくまで動作検証環境としてロードバランサを使用したいだけなので面倒でもその都度設定すれば良いだけなんですが、何かスッキリしません…