LVSで実現するロードバランサ

出典: KLablabWiki


タイトル 
フルオープンソースで実現するロードバランサ
著者名  
KLab(株) 安井 真伸 YASUI Masanobu
初出 
技術評論社刊『WEB+DB PRESS Vol.37


目次

環境構築

 それでは実際に、Linuxベースのロードバランサを構築していきます。最近では標準でIPVSをサポートしているディストリビューションが多いので、必要なパッケージをインストールして少し設定するだけで動作させることができる便利な世の中になってきています。

今回使用するソフトウエアについて

Debian GNU/Linux3.1(sarge)

 ディストリビューションはDebianを使用します。

IPVS対応カーネル

 ロードバランサの基本機能であるIPVSはカーネルの内部に実装されています。そのためIPVSに対応したカーネルが必要になります。Debian付属のカーネルイメージ(2.6.8-3)でも利用できますし、自前で再構築してもかまいません。カーネルを再構築する際の注意点については後述します。

ipvsadm

 IPVSを制御するためのツールです。仮想サーバグループの追加やリアルサーバの割り当てができるほか、現在の接続状況や転送レートの表示などができます。

keepalived

 IPVSにはヘルスチェックの機能がありません。そのため、リアルサーバがダウンしても気にせずリクエストを転送し続けてしまいます。keepalivedは定期的にリアルサーバへのヘルスチェックをし、ダウンを検出したらIPVSを制御してリクエストが流れないように調整してくれます。

iptables

 パケットフィルタ機能を制御するためのツールです。Linuxをルータやファイアウォールとして利用する場合などには必須ともいえるツールです。

iproute

 IPアドレスやルーティングテーブルを設定したり確認するためのツールです。ifconfigやrouteコマンドでも似たような事はできますが、このパッケージに含まれているコマンドでは、Linux特有の非常に細かい設定ができるようになっています。

インストール

 さくっと簡単に動かしてみたい方にはDebianパッケージでのインストールをお勧めします。上記パッケージを図1の手順でインストールするだけです。

図1 ロードバランサの準備
# aptitude install kernel-image-2.6.8-3-686
# aptitude install ipvsadm
# aptitude install keepalived
# aptitude install iptables
# aptitude install iproute

カーネル再構築について

 ディストリビューション付属のカーネルイメージでもIPVSを利用することができますが、ドライバやハードウエアの問題等でカーネルを再構築しなければならないこともあるでしょうし、最新のカーネルを使いたいという場面もあるかと思います。ここでは、ロードバランサ用のカーネルを再構築する際の注意点をご紹介したいと思います。

IPVSのバージョンに注意

 カーネル2.6.10からIPVSのバージョンがv1.2.0からv1.2.1に上がりました。Debian GNU/Linux 3.1の標準カーネルは2.6.8なので、最新カーネルに入れ替えるとIPVSのバージョンが変わってしまいます。IPVSのバージョンが変わる際にはipvsadmとkeepalivedをリビルドする必要があります。これらのビルド手順については後述します。

 ちなみに、今使っているipvsadmコマンドがどのバージョンのIPVSでビルドされているかは、--versionオプションで確認することができます。また、LinuxカーネルのIPVSのバージョンは /include/net/ip_vs.hのIP_VS_VERSION_CODEで定義されていますので、カーネルを入れ替える際には必ず確認して下さい。

CONFIG_IP_VSを有効にする

 当然といえば当然ですが、IPVSの機能を有効にしなければいけません。make menuconfigで必要な項目を有効にして下さい。  図2はカーネルバージョン2.6.16.27の例です。

図2 Virtual Server Configuration(2.6.16.27)
Device Drivers  --->
  Networking support  --->
    Networking options  --->
      IP: Virtual Server Configuration  --->
        <M> IP virtual server support (EXPERIMENTAL) 
        [*]   IP virtual server debugging 
        (12)  IPVS connection table size (the Nth power of 2)    
        ---   IPVS transport protocol load balancing support
        [*]   TCP load balancing support
        [*]   UDP load balancing support
        [*]   ESP load balancing support
        [*]   AH load balancing support
        ---   IPVS scheduler
        <M>   round-robin scheduling
        <M>   weighted round-robin scheduling
        <M>   least-connection scheduling
        <M>   weighted least-connection scheduling
        <M>   locality-based least-connection scheduling
        <M>   locality-based least-connection with replication scheduling
        <M>   destination hashing scheduling
        <M>   source hashing scheduling
        <M>   shortest expected delay scheduling
        <M>   never queue scheduling
        ---   IPVS application helper
        <M>   FTP protocol helper

netfilterを有効にする

 IPVSとnetfilterの機能は密接に絡んでいます。図3の項目は必須なので必ず有効にして下さい。また、ここには書いていないIP tablesの機能も有効にしておくと便利です。  図3はカーネルバージョン2.6.16.27の例です。

図3 Netfilter Configuration(2.6.16.27)
Device Drivers  --->
  Networking support  --->
    Networking options  --->
      Network packet filtering (replaces ipchains)  --->
        IP: Netfilter Configuration  --->
          <M> Connection tracking (required for masq/NAT)
          [*] Connection mark tracking support
          <M> IP tables support (required for filtering/masq/NAT) 
          <M>   Packet filtering
          <M>   Full NAT 
          <M>     MASQUERADE target support 
          <M>     REDIRECT target support         
          <M>   Packet mangling
          <M>     MARK target support

ポリシールーティングを有効にする

 IPVSの機能とは直接関係ないので今回は深追いしませんが、VIPの制御はポリシールーティングを使うと簡単です。いろいろ使い道がありますので図4の項目を有効にして下さい。図4はカーネルバージョン2.6.16.27の例です。

図4 policy routing(2.6.16.27)
Device Drivers  --->
  Networking support  --->
    Networking options  --->
      [*] TCP/IP networking
      [*]   IP: advanced router
      [*]     IP: policy routing 
      [*]       IP: use netfilter MARK value as routing key  

ipvsadmとkeepalivedのビルド手順

 ここでは、ipvsadmとkeepalivedをパッケージではなくソースからビルドする手順を説明します。

 ビルドする前には図5の手順でDebianパッケージのipvsadmとkeepalivedを削除しておきましょう。

図5 パッケージをアンインストール
# aptitude remove ipvsadm
# aptitude remove keepalived

 keepalivedのビルドにはlibssl-devとlibpopt-devが必要です。また、カーネルソースを/usr/src/linux/に置いておく必要があります。(図6)

 ビルドはいたって簡単です。図7と図8の手順で進めて下さい。

図6 ビルドの準備
# aptitude install libssl-dev
# aptitude install libpopt-dev
# ln -s {KernelSourceDir} /usr/src/linux
図7 ipvsadmのビルド手順
$ wget http://www.linuxvirtualserver.org/software/kernel-2.6/ipvsadm-1.24.tar.gz
$ tar zxf ipvsadm-1.24.tar.gz
$ cd ipvsadm-1.24
$ make
# make install
図8 keepalivedのビルド手順
$ wget http://www.keepalived.org/software/keepalived-1.1.13.tar.gz
$ tar zxf keepalived-1.1.13.tar.gz
$ cd keepalived-1.1.13
$ ./configure
   ・
   ・
(中略)
   ・
   ・
Keepalived configuration
------------------------
Keepalived version       : 1.1.13
Compiler                 : gcc
Compiler flags           : -g -O2
Extra Lib                : -lpopt -lssl -lcrypto
Use IPVS Framework       : Yes
IPVS sync daemon support : Yes
Use VRRP Framework       : Yes
Use LinkWatch            : Yes
Use Debug flags          : No
$ make
# make install

リアルサーバを用意する

 ロードバランサ1台だけでは何もできないので、負荷分散対象となるリアルサーバを3台用意しましょう。Webサーバさえ動けばWindowsでもなんでも良さそうな気がしますが、今回は訳あってロードバランサと同じくDebianを使います。必要なパッケージは以下の通りで、インストール方法は図9の通りです。クライアントからアクセスする簡単なテストページも用意します。

apache2

 言わずと知れたWebサーバです。Apacheが嫌いな方はlighttpdやthttpdなどでもかまいません。

iptables

 おそらく「なにに使うんだ?」と思う方もいるかもしれませんが、後述するDSRで必要となります。

図9 リアルサーバの準備
# aptitude install apache2
# aptitude install iptables
# hostname > /var/www/test.html
# echo OK  > /var/www/health.html

クライアントを用意する

 ロードバランサとリアルサーバの他に、クライアントが必要です。これがないと動作確認できません。WindowsマシンでIEを使ってもよいのですが、今回は説明を簡略化するために他のマシンと同じくDebianを利用し、以下のパッケージを図10の手順でインストールします。

curl

 簡単なテキストベースのHTTPクライアントです。

apache2-utils

 Apacheのユーティリティ群です。今回はab(ApacheBench)で簡単な性能測定っぽい事もしてみたいと思います。

図10 クライアントの準備
# aptitude install curl
# aptitude install apache2-utils

ネットワーク構成

 図11は今回構築するシステムのネットワーク構成図です。リアルサーバとクライアントのデフォルトゲートウエイはロードバランサに向けます。内部セグメントと外部セグメントの境界にロードバランサを設置し、クライアントとリアルサーバ間の通信には必ずロードバランサを経由するような構成にします。各マシンのホスト名やIPアドレスは表1のように設定してください。ここで、図中にあるVIP(10.0.0.100)はどのインターフェイスにも割り当てていませんのでご注意下さい。また、今回構築するロードバランサはルータとしての役割も担うため、以下の追加設定が必要になりますので図12のコマンドを実行してください。

IPフォワーディングを有効にする

 Linuxをルータとして利用する場合に必須の設定です。パケット転送を許可します。

rp_filterを無効にする

 Linuxには送信元アドレスを偽装したパケットを排除する機能(rp_filter)があります。後述するDSR構成では、応答パケットがrp_filterに引っかかってしまって通信できなくなるので、あらかじめ無効にしておきます。ただし、無効にするのはeth1(内部セグメント側)のrp_filterのみです。eth0(外部セグメント側)のrp_filterは無効にしないで下さい。あくまでも「中から外に出ていくパケット」に対するrp_filterを無効にするだけですのでご注意下さい。

図11 ネットワーク構成図
図11 ネットワーク構成図


ネットワーク設定一覧
種別 ホスト名 IPアドレス デフォルトゲートウエイ
ロードバランサ lv1 eth0:10.10.31.11/24 なし
eth1:192.168.31.11/24
リアルサーバ w100 eth0:192.168.31.100/24 192.168.31.11
w101 eth0:192.168.31.101/24 192.168.31.11
w102 eth0:192.168.31.102/24 192.168.31.11
クライアント c101 eh0:10.10.31.100/24 10.10.31.11


図12 ロードバランサの追加設定
lv1:# sysctl -w net.ipv4.ip_forward=1
lv1:# sysctl -w net.ipv4.conf.eth1.rp_filter=0

接続確認

 さて、実際に負荷分散をする前に、ここまでの構成がきちんとできているか確認してみましょう。cl01からリアルサーバにアクセスして図13の応答が返ってくれば成功です。

 実際のインターネット環境では、外部セグメントのマシンから直接リアルサーバに接続できるような構成にするべきではありませんが、今回はIPVSを経由した場合としない場合とでパフォーマンスに違いがでるかどうかを見てみたいので、このような構成にしています。 せっかくなので、ここで一度ベンチマークを取っておきます。図14がcl01からabを実行した結果で、598.68 [#/sec] という測定値がでました。「え?これしかでないの?」って思われるかもしれませんが、かなり性能の低い機材でシステムを構築したためですので不思議に思わないで下さい。

図13 疎通確認
図14 ベンチマーク結果(LVSなし)
cl01:$ ab -n 1000 -c 100 'http://192.168.31.101/test.html'
Server Software:        Apache/2.2.3
Server Hostname:        192.168.31.101
Server Port:            80

Document Path:          /test.html
Document Length:        5 bytes

Concurrency Level:      100
Time taken for tests:   1.670348 seconds
Complete requests:      1000
Failed requests:        0
Write errors:           0
Total transferred:      246000 bytes
HTML transferred:       5000 bytes
Requests per second:    598.68 [#/sec] (mean)
Time per request:       167.035 [ms] (mean)
Time per request:       1.670 [ms] (mean, across all concurrent requests)
Transfer rate:          143.68 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        1   10   8.8      8      38
Processing:    33  146  21.7    149     189
Waiting:       32  144  21.9    148     184
Total:         64  157  17.9    159     215

LVS-NATによる負荷分散

 いよいよ負荷分散の設定に入ります。まずはロードバランサで図15のコマンドを実行してください。各コマンドの詳細は割愛させていただきますが、ロードバランサがVIP(10.0.0.100)宛のパケットを処理できるようにするためのものとご理解下さい。さらに、リスト1の内容で/etc/keepalived/keepalived.confを作成します。

 リスト1の各項目の意味は以下の通りです。 (1)仮想サーバグループ(VSG)を定義します。 (2)LVS-NATを使った負荷分散をします。 (3)TCP接続を負荷分散します。 (4)HTTP_GETによるヘルスチェックで付加するHOSTヘッダです (5)リアルサーバがすべて停止した場合の転送先サーバ(w100)です (6)リアルサーバ(w101)の設定です。 (7)リアルサーバ(w102)の設定です。

図15 VIP宛パケットの操作
lv1:# iptables -t mangle -A PREROUTING -d 10.0.0.100 -j MARK --set-mark 1
lv1:# ip rule add prio 100 fwmark 1 table 100
lv1:# ip route add local 0/0 dev lo table 100
リスト1 keepalived.conf
virtual_server_group HTTP100 {      ┐
  10.0.0.100  80                    │ (1)
}                                   ┘

virtual_server group HTTP100 {
  delay_loop   3
  lvs_sched    rr                     
  lvs_method   NAT                  ← (2)
  protocol     TCP                  ← (3)
  virtualhost  example.org          ← (4)
  sorry_server 192.168.31.100 80    ← (5)
  real_server  192.168.31.101 80 {  ┐
    weight 1                        │
    inhibit_on_failure              │ (6)
    HTTP_GET {                      │
      url {                         │
        path /health.html           │
        status_code 200             │
      }                             │
      connect_timeout 3             │
    }                               │
  }                                 ┘
  real_server  192.168.31.102 80 {  ┐
    weight 1                        │
    inhibit_on_failure              │ (7)
    HTTP_GET {                      │
      url {                         │
        path /health.html           │
        status_code 200             │
      }                             │
      connect_timeout 3             │
    }                               │
  }                                 ┘
} 

 お待たせしました。そろそろkeepalivedを起動してみましょう。

lv1:# /etc/init.d/keepalived start

 ipvsadmコマンドで仮想サーバテーブルにエントリが追加されているかを確認します。図16のような結果が表示されれば成功です。これで、http://10.0.0.100/test.html にアクセスするとw101とw102のどちらかが応答するはずです。

図16 仮想サーバテーブルの表示結果
lv1:# ipvsadm -L -n
IP Virtual Server version 1.2.0 (size=4096)
Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
TCP  10.0.0.100:80 rr
  -> 192.168.31.102:80            Masq    1      0          0
  -> 192.168.31.101:80            Masq    1      0          0

負荷分散を体感してみよう

 それでは実際にクライアントからHTTPで接続してみます。cl01からhttp://10.0.0.100/test.html に対して何度かアクセスしてみます。(図17)

 如何でしょうか、同じURLにアクセスしているのに、w101とw102が交互に応答している様子を見ることができると思います。また、先ほどと同様にabでベンチマークをとってみたところ、584.88[#/sec] という結果になりました。(図18)

 IPVSを経由しないで測定した結果が598.68[#/sec]だったので、この数値をみる限り、IPVSのオーバーヘッドは全体の性能にほとんど影響を与えないくらいに小さいものと考えてよいかもしれません。 あと、念のためお断りさせて頂きたいのですが、あくまでこれらの数値は「どこかの誰かの環境でabを実行してみた結果」でしかなく、IPVSの性能を定量的に表しているものではありません。本来きちんと性能評価をするのであれば、マシンスペックや試験環境をオープンにした上で比較的大きなデータを用意して時間と負荷をかけて測定すべきだと思いますが、ここでの趣旨とは異なりますのでそこまではやりません。なので今回は「とりあえずabをかけてみたところ、少なくともIPVSがボトルネックになって遅くなることはなさそう」くらいの感覚だとご理解頂けると幸いです。まあ、いわゆる「参考値」にもならない「気持値(きもち?)」ってやつですかね。

図17 ロードバランスを体感できた瞬間
cl01:$ curl 'http://10.0.0.100/test.html'
w102
cl01:$ curl 'http://10.0.0.100/test.html'
w101
cl01:$ curl 'http://10.0.0.100/test.html'
w102
cl01:$ curl 'http://10.0.0.100/test.html'
w101
図18 ベンチマーク結果(LVS-NAT)
cl01:$ ab -n 1000 -c 100 'http://10.0.0.100/test.html'
Server Software:        Apache/2.2.3
Server Hostname:        10.0.0.100
Server Port:            80

Document Path:          /test.html
Document Length:        5 bytes

Concurrency Level:      100
Time taken for tests:   1.709765 seconds
Complete requests:      1000
Failed requests:        0
Write errors:           0
Total transferred:      246246 bytes
HTML transferred:       5005 bytes
Requests per second:    584.88 [#/sec] (mean)
Time per request:       170.976 [ms] (mean)
Time per request:       1.710 [ms] (mean, across all concurrent requests)
Transfer rate:          140.37 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        1   24  15.1     23     267
Processing:    13  126  78.1    118     335
Waiting:       13  124  78.0    116     335
Total:         37  150  77.8    144     361

ipvsadmでパケット転送レートを表示する

 ipvsadmは--rateオプションで現在のパケット転送レートを表示することができます。

 図19はabを実行した直後に転送レートを表示してみた結果です。各項目の意味は以下の通りです。

  • CPS [connections/second]
    1秒間の接続数数です。
  • InPPS [packets/second]
    1秒間の転送パケット数です。(クライアント → サーバ)
  • OutPPS [packets/second]
    1秒間の転送パケット数です。(サーバ → クライアント)
  • InBPS [bytes/second]
    1秒間の転送バイト数です。(クライアント → サーバ)
  • OutBPS [bytes/second]
    1秒間の転送バイト数です。(サーバ → クライアント)
図19 転送レートを表示
lv1:# ipvsadm -L -n --rate
IP Virtual Server version 1.2.0 (size=4096)
Prot LocalAddress:Port                 CPS    InPPS   OutPPS    InBPS   OutBPS
  -> RemoteAddress:Port
TCP  10.0.0.100:80                      98      362      265    25871    28811
  -> 192.168.31.102:80                  56      181      122    13097    13437
  -> 192.168.31.101:80                  42      180      143    12774    15374

リアルサーバが落ちたときの動作

 ロードバランサを導入する目的として「可用性の向上」というものがありました。「リアルサーバが1台くらいこけても平気」っていうあれです。keepalivedは定期的に全リアルサーバに対してヘルスチェックを行い、失敗するとそのサーバを仮想サーバグループから外してくれます。今度はこの動作を体感してみましょう。

w101のApacheを停止する

 w101のApacheを停止すると図20のような状況になります。

  • curlでアクセスするとw102しか返ってこなくなります。
  • ipvsadmで確認するとw101のWeightが0になっています。

w102のhealth.htmlを読めなくする

 keepalivedのヘルスチェックではhealth.htmlへのアクセスでステータスコード200が返ってくるか否かを判断するように設定しています。つまり、サーバをシャットダウンしなくてもApacheを止めなくても好きなサーバを負荷分散対象から外すことができます。 図21では、chmod -rでhealth.htmlのパーミッションを落とすことで負荷分散対象から除外してみました。もし、なんらかのトラブルが発生して特定のサーバだけ調子が悪くなった場合などに、このような手法でApacheを止めずに(現状を維持したまま)サービスから切り離すことができ、トラブルシューティングが容易になるというメリットがあります。

w101とw102のApacheを停止する

 すべてのリアルサーバが停止すると、リクエストはsorry_serverに設定されているw100(192.168.31.100)に転送されるようになります。(図22)

 実際の運用では、sorry_serverに指定したサーバはあらかじめ「どのようなHTTPリクエストが来ても必ず『ただいま込み合っています』というメッセージを返す設定」にしておきます。そうすることで、万一すべてのリアルサーバが停止した場合でも「サーバが見つからない」、「接続できない」という最悪な状況は回避することができます。

図20 w101のApacheを停止
w101:# /etc/init.d/httpd stop

————————————————————————————————————
cl01:$ curl 'http://10.0.0.100/test.html'
w102
cl01:$ curl 'http://10.0.0.100/test.html'
w102
cl01:$ curl 'http://10.0.0.100/test.html'
w102

————————————————————————————————————
lv1:# ipvsadm -L -n
IP Virtual Server version 1.2.0 (size=4096)
Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
TCP  10.0.0.100:80 rr
  -> 192.168.31.102:80            Masq   1      0          0
  -> 192.168.31.101:80            Masq   0      0          0

————————————————————————————————————
w101:# /etc/init.d/httpd start

————————————————————————————————————
cl01:$ curl 'http://10.0.0.100/test.html'
w102
cl01:$ curl 'http://10.0.0.100/test.html'
w101
図21 w102のヘルスチェックが失敗
w102:# chmod -r /var/www/health.html

————————————————————————————————————
cl01:$ curl 'http://10.0.0.100/test.html'
w101
cl01:$ curl 'http://10.0.0.100/test.html'
w101
cl01:$ curl 'http://10.0.0.100/test.html'
w101

————————————————————————————————————
lv1:# ipvsadm -L -n
IP Virtual Server version 1.2.0 (size=4096)
Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
TCP  10.0.0.100:80 rr
  -> 192.168.31.102:80            Masq   0      0          0
  -> 192.168.31.101:80            Masq   1      0          0

————————————————————————————————————
w102:# chmod +r /var/www/health.html

————————————————————————————————————
cl01:$ curl 'http://10.0.0.100/test.html'
w101
cl01:$ curl 'http://10.0.0.100/test.html'
w102
図22 両方のApacheを停止してみる
w101:# /etc/init.d/httpd stop
w102:# /etc/init.d/httpd stop

————————————————————————————————————
cl01:$ curl 'http://10.0.0.100/test.html'
w100
cl01:$ curl 'http://10.0.0.100/test.html'
w100

————————————————————————————————————
lv1:# ipvsadm -L -n
IP Virtual Server version 1.2.0 (size=4096)
Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
TCP  10.0.0.100:80 rr
  -> 192.168.31.100:80            Masq    1      0          0
  -> 192.168.31.102:80            Masq    0      0          0
  -> 192.168.31.101:80            Masq    0      0          0

————————————————————————————————————
w101:# /etc/init.d/httpd start
w102:# /etc/init.d/httpd start

————————————————————————————————————
cl01:$ curl 'http://10.0.0.100/test.html'
w102
cl01:$ curl 'http://10.0.0.100/test.html'
w101

DSRに挑戦

DSR(Direct Server Return)とは

 NAT構成(図23)では、ロードバランサがリアルサーバにパケットを転送する際に送信先IPアドレスを書き換えます。そのため、リアルサーバからのレスポンスパケットに対しても、ロードバランサが送信元IPアドレスを書き戻す必要があります。したがってNAT方式のロードバランサは、In/Out両方のパケットすべてに対してIPアドレスの書き換えをしなければいけません。これは、大規模なシステムになればなるほどロードバランサの負荷が増大し、ボトルネックになる可能性が高くなることを意味します。  DSR構成(図24)では、ロードバランサがIPアドレスを書き換える事はありません。書き換えるのはMACアドレスだけです。「MACアドレスを書き換える」というと大変な仕事のように聞こえるかもしれませんが、単なるルータの仕事(パケット転送)そのものです。要はDSR構成においてロードバランサがやるべき処理は、普通のルータとなんらかわらないのです。ただ大きく違うのは、ルータはルーティングテーブルの情報を元にして転送先を選択しますが、ロードバランサは仮想サーバテーブルに従って転送先を選択する点です。

 もっとわかりやすくいうと、ルータの仕事は、

  • 「送信先アドレスがあそこ宛のパケットが飛んできたら、とにかくここに飛ばせばいいや」

 そして、DSR構成のロードバランサの仕事は、

  • 「送信先アドレスがあそこで、ポート番号がこれだから、あのサーバか、このサーバのどちらかに飛ばすのね」
  • 「えっと、たしかさっきはあのサーバに飛ばしたはずだから今度はこのサーバに飛ばしてあげよう」

 こんなイメージになります。ルータに比べればきめ細かな仕事をしているので大変そうに見えますが、きめ細かい処理をするのはリクエストのみで、リアルサーバからの応答については単純なパケット転送になります。一般的にWebサービスのトラフィックはHTTPレスポンスの方が圧倒的に多いので、DSR構成がロードバランサの負荷軽減に威力を発揮することはおわかり頂けるかとおもいます。

図23 NAT構成のパケットの流れ
図23 NAT構成のパケットの流れ
図24 DSR構成のパケットの流れ
図24 DSR構成のパケットの流れ

keepalivedの設定変更

 ロードバランサの設定をDSRに変更するのは簡単です。keepalived.confをリスト2のように書き換えて下さい。変更点は1カ所だけ、NATをDRにするだけです。  設定ファイルを変更したらkeepalivedを再起動します。

lv1:# /etc/init.d/keepalived restart

 先ほどと同じようにipvsadmで仮想サーバテーブルの内容を確認してみます。(図25)

 NATの時はForwardがMasqでしたが、DSRではRouteになります。さあ、これで『ロードバランサ』の設定は完了です。早速接続確認してみましょう。

 あれれ?応答が返ってきません。接続できないって言われてしまいます。(図26)

リスト2 DSRの設定
virtual_server group HTTP100 {
  delay_loop   3
  lvs_sched    rr
  lvs_method   DR   ← NATをDRに変更 
  protocol     TCP 
  virtualhost  example.org 
図25 LVS-DSRの仮想サーバテーブル
lv1:# ipvsadm -L -n
IP Virtual Server version 1.2.0 (size=4096)
Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
TCP  10.0.0.100:80 rr
  -> 192.168.31.102:80            Route   1      0          0
  -> 192.168.31.101:80            Route   1      0          0
図26 DSRにしたら繋がらなくなって苦しんでいる場面
cl01:$ curl 'http://10.0.0.100/test.html'
curl: (7) couldn't connect to host

パケットを覗いてみよう

 うまく接続できない場合に原因を調べるためのツールとして有名なのがtcpdumpです。接続できない原因がどこにあるのか順を追って探ってみましょう。

 まずはロードバランサ上でtcpdumpしながらcl01でcurlを実行してみて、ロードバランサにパケットが到達しているかどうかを確認します。(図27)

 どうやらロードバランサにはパケットは到達しているようです。次に、ロードバランサがリアルサーバへパケットを転送しているかどうかを確認します。(図28)

 eth1でちゃんとキャプチャできているのでIPVSは正常にパケットを転送しているようです。DSRなので送信先アドレスがVIPのままになっていることもわかります。 この際なので、IPVSの接続テーブルも確認してみたところ図29の結果になりました。なぜかstateがSYN_RECVになっているみたいですね。この辺の詳細な解説はTCP関連の解説書に譲りますが、どうやら現状は、リアルサーバからのSYN_ACKが返ってこないためにTCPセッションを確立できていないようです。さらに状況を絞り込むと、

(1)リアルサーバまでSYNパケットが届いていない

(2)リアルサーバにSYNパケットは届いているがSYN_ACKを返していない

(3)リアルサーバはSYN_ACKを返しているのにlv1まで届いていない

 この中のどれかと考えられます。この切り分けのため、今度はリアルサーバ上でtcpdumpを動かしてみます。この際、負荷分散されるのは面倒なのでw101のApacheは止めておきます。すると図30の結果となり、cl01からVIP(10.0.0.100)宛のSYNパケットが届いている事はわかりますが、何故かSYN_ACKを返していないようです。この時点で、上記の(2)の状況であると特定できます。

 何故リアルサーバはSYN_ACKを返せないのでしょうか。ネットワーク構成を眺めていると、ふとあることに気づきました。

  • リアルサーバにVIP(10.0.0.100)が割り当てられていないじゃないか

 当たり前すぎてすっかり見落としてましたが、自分のIPアドレス宛じゃないパケットを受け取っても応答を返せるはずがありませんよね。

 NAT構成ではIPVSがパケットの送信先アドレスをリアルサーバのIPアドレスに書き換えてくれていたので気にしなくて済んでいましたが、DSR構成ではリアルサーバ自身でVIP宛のパケットを受け取れなければいけません。 では具体的にどうすればよいのでしょうか。IPアドレスが割り当てられていないのが問題だから割り当ててしまえばいい?いや、ちょっとまって下さい。そうすると、すべてのリアルサーバに同じIPアドレスを振らなければいけなくなります。それもいろいろ問題がありそうです。

図27 クライアントからロードバランサへ向かうパケットの様子
lv1:# tcpdump -n -i eth0 host 10.10.31.100 and port 80
listening on eth0, link-type EN10MB (Ethernet), capture size 96 bytes
IP 10.10.31.100.55800 > 10.0.0.100.80: S 348457510:348457510(0) win 5840 <mss 1460,sackOK,timestamp 283092311 0,nop,wscale 2>
図28 ロードバランサからリアルサーバへ向かうパケットの様子
lv1:~# tcpdump -n -i eth1 host 10.10.31.100 and port 80
listening on eth1, link-type EN10MB (Ethernet), capture size 96 bytes
IP 10.10.31.100.55802 > 10.0.0.100.80: S 384216672:384216672(0) win 5840 <mss 1460,sackOK,timestamp 283103033 0,nop,wscale 2>
図29 IPVSコネクションテーブルの内容
lv1:# ipvsadm -L -c -n
IPVS connection entries
pro expire state       source             virtual            destination
TCP 00:59  SYN_RECV    10.10.31.100:40838 10.0.0.100:80      192.168.31.102:80
図30 リアルサーバまでパケットが届いた様子
w102:# tcpdump -n -i eth0 host 10.10.31.100 and port 80
listening on eth0, link-type EN10MB (Ethernet), capture size 96 bytes
IP 10.10.31.100.46315 > 10.0.0.100.80: S 1262057443:1262057443(0) win 5840 <mss 1460,sackOK,timestamp 283307907 0,nop,wscale 2>

DSR構成はリアルサーバの協力も必要

 この問題については実際のところ様々な解決策がありますが、今回は筆者らの環境で採用している方法をご紹介します。

 とりあえず、だまされたと思って各リアルサーバ上(w100,w101,w102)で以下のコマンドを実行してみて下さい。

# iptables -t nat -A PREROUTING -p tcp -d 10.0.0.100 -j REDIRECT

 iptablesの-j REDIRECTは、localhost宛てとして取り込む指定です。VIP(10.0.0.100)宛のパケットを自分宛のように扱うことができます。

 これでhttp://10.0.0.100/test.html へアクセスできるようになります。先ほどと同じようにtcpdumpを見てみると、きちんと応答が返るようになったことがわかると思います。(図31)

図31 DSRで繋がるようになったところ
cl01:$ curl 'http://10.0.0.100/test.html'
w102

————————————————————————————————————
w102:# tcpdump -n -i eth0 host 10.10.31.100 and port 80
listening on eth0, link-type EN10MB (Ethernet), capture size 96 bytes
IP 10.10.31.100.38580 > 10.0.0.100.80: S 1934269793:1934269793(0) win 5840 <mss 1460,sackOK,timestamp 287536514 0,nop,wscale 2>
IP 10.0.0.100.80 > 10.10.31.100.38580: S 758437474:758437474(0) ack 1934269794 win 5792 <mss 1460,sackOK,timestamp 135297857 287536514,nop,wscale 2>
IP 10.10.31.100.38580 > 10.0.0.100.80: . ack 1 win 1460 <nop,nop,timestamp 287536515 135297857>
IP 10.10.31.100.38580 > 10.0.0.100.80: P 1:179(178) ack 1 win 1460 <nop,nop,timestamp 287536515 135297857>
IP 10.0.0.100.80 > 10.10.31.100.38580: . ack 179 win 1716 <nop,nop,timestamp 135297858 287536515>
IP 10.0.0.100.80 > 10.10.31.100.38580: P 1:228(227) ack 179 win 1716 <nop,nop,timestamp 135297858 287536515>
IP 10.10.31.100.38580 > 10.0.0.100.80: . ack 228 win 1728 <nop,nop,timestamp 287536516 135297858>
IP 10.10.31.100.38580 > 10.0.0.100.80: F 179:179(0) ack 228 win 1728 <nop,nop,timestamp 287536516 135297858>
IP 10.0.0.100.80 > 10.10.31.100.38580: F 228:228(0) ack 180 win 1716 <nop,nop,timestamp 135297859 287536516>
IP 10.10.31.100.38580 > 10.0.0.100.80: . ack 229 win 1728 <nop,nop,timestamp 287536517 135297859>

最後に

 ここまででWebサーバの冗長化は完成しました。しかし、ネットワーク構成図をよく見ていただくとわかりますが、この構成ではまだ単一故障点(Single Point Of Failure = SPOF)が残ってしまっています。即ち、ロードバランサ自身です。この構成では、いくらリアルサーバが元気でも、肝心のロードバランサが落ちてしまえばサイトが見えなくなってしまいます。次章では不幸にもロードバランサが落ちたときに備えて、ロードバランサの冗長化をしてみたいと思います。

■このサイトについて

このサイトはKLab株式会社が開発したソフトウェアやノウハウ、実験的なサービスを公開していきます。

 RSS Feed

■メニュー