Solaris の IPMP には送信パケットの負荷を分散するアウトバウンド・ロード・スプレッディングという機能があります。マニュアル にも記載されていますので、ご存知の方も多いと思いますが、この機能を利用するとサーバからの返信時(アウトバウンド)のネットワークの負荷(ロード)を複数のインターフェイスに分散(スプレッディング)する事が可能になり、結果としてスループットを上げることができる場合があります。今回の記事ではその挙動をご覧頂きたいと思います。
検証用に次の図の様な環境を用意しました。サーバには igb1 と igb2 という 1 Gigabit のネットワークインターフェイスがあり、どちらも同じネットワークセグメントに接続されています。この 2 つのインターフェイスに負荷を分散することができれば、最大で 2 Gigabit の帯域を使用出来ることになります。
igb1 の IP アドレスは 192.168.10.1/24 で、igb2 の IP アドレスは 192.168.10.2/24 です。サーバと同じセグメントにはクライアントマシンが 2 台接続されています。クライアント A の IP アドレスは 192.168.10.3/24, クライアント B の IP アドレスは 192.168.10.4/24 です。なお、サーバにはテスト用に lighttpd をインストールしてあります。サーバもクライアントも OS は Solaris 10 10/09 です。
+-----------------+ o Switching Hub 192.168.10.0/24
| | |
| | 192.168.10.1 |
| igb1 +-----------------+
| | | 192.168.10.3 +----------+
| Server | +-----------------+ Client A |
| | 192.168.10.2 | +----------+
| igb2 +-----------------+
| | | 192.168.10.4 +----------+
| | +-----------------+ Client B |
+-----------------+ | +----------+
o
サーバ側のネットワークの設定は以下の通り行いました。
# ifconifg igb1 plumb
# ifconfig igb1 inet 192.168.10.1/24 broadcast + up
# ifconfig igb2 plumb
# ifconfig igb2 inet 192.168.10.2/24 broadcast + up
なお、ルーティングは停止してあります。
# routeadm
Configuration Current Current
Option Configuration System State
---------------------------------------------------------------
IPv4 routing disabled disabled
IPv6 routing disabled disabled
IPv4 forwarding disabled disabled
IPv6 forwarding disabled disabled
...
SPARC マシンで試す場合は local-mac-address? を true に設定して下さい。
# eeprom local-mac-address?=true
# reboot
この検証環境を使って、まずは IPMP を組んでいない場合の挙動から見て行きましょう。
テストには HTTP を使用しました。2 台のクライアントからサーバの HTTP ポートに telnet コマンドで同時に接続し、返信パケットの負荷分散が行われるかを確認します。クライアントからの送信時もネットワーク帯域を有効に利用するため、クライアントはそれぞれ別のネットワークインターフェイスを目指して接続する事にします。クライアント A の接続先は 192.168.10.1 の 80 番ポートで、クライアント B の接続先は 192.168.10.2 の 80 番ポートです。これは丁度、複数のウェブサーバの手前にロードバランサーを置いて負荷分散している構成と似ています。今回の構成はサーバが一台だけで済んでいると言う点だけが異なります。
物理構成上はサーバとクライアント A の間の通信と、サーバとクライアント B の間の通信は、全く重複しない経路を通る事が可能です。それぞれの通信が完全に別々の経路を通った場合、ネットワーク帯域は最大 2 Gigabit となります。実際にどういう動きになるか見てみましょう。
<<クライアント A から HTTP で接続>>
# telnet 192.168.10.1 80
<<クライアント B から HTTP で接続>>
# telnet 192.168.10.2 80
クライアントから接続したら、意図した通りの接続になっているかを netstat -a コマンドで確認します。コマンドの出力結果を見ると、クライアント A は 192.168.10.3 から 192.168.10.1 の 80 番ポートへ、クライアント B は 192.168.10.4 から 192.168.10.2 の 80 番ポートへきちんと接続されている事が分かります。サーバ側でも同じ IP アドレスのペアで接続を受け取っている事が確認出来ます。
<<クライアント A>>
# netstat -a | grep 80
192.168.10.3.42881 192.168.10.1.80 49640 0 49640 0 ESTABLISHED
<<クライアント B>>
# netstat -a | grep 80
192.168.10.4.37305 192.168.10.2.80 49640 0 49640 0 ESTABLISHED
<<サーバ>>
# netstat -a | grep 80
192.168.10.1.80 192.168.10.3.42881 49640 0 49640 0 ESTABLISHED
192.168.10.2.80 192.168.10.4.37305 49640 0 49640 0 ESTABLISHED
次に HTTP の通信を発生させて、実際にどのネットワークインターフェイスを通ってデータがやり取りされるかを確認します。HTTP の通信はクライアント側からファイルを GET する事で発生させます。
<<クライアント A>>
# telnet 192.168.10.1 80
Trying 192.168.10.1...
Connected to 192.168.10.1.
Escape character is '\^]'.
GET /001.txt HTTP/1.0
<<クライアント B>>
# telnet 192.168.10.2 80
Trying 192.168.10.2...
Connected to 192.168.10.2.
Escape character is '\^]'.
GET /001.txt HTTP/1.0
ネットワークインターフェイスの使用状況は、サーバ側で dladm コマンドを使用して確認します。dladm コマンドの出力は rbytes がそのインターフェイスに於ける受信バイト数、obytes が送信バイト数です。この値が 0 以外であればデータ通信が発生している事を示しています。
下記の出力結果を見ると igb1 は受信も送信もデータのやり取りが発生しています。一方、igb2 は受信バイト数は上がっていますが、送信バイト数は 0 になっており、データの送信には使われていない事が分かります。igb2 で受け付けた接続の返信パケットはどうなってしまったのでしょうか。
# dladm show-dev -s -i 1 igb1
ipackets rbytes ierrors opackets obytes oerrors
igb1 503 32192 0 1893 2797601 0
ipackets rbytes ierrors opackets obytes oerrors
igb1 389 24896 0 1975 2931778 0
ipackets rbytes ierrors opackets obytes oerrors
igb1 416 26624 0 1644 2438276 0
# dladm show-dev -s -i 1 igb2
ipackets rbytes ierrors opackets obytes oerrors
igb2 386 24704 0 0 0 0
ipackets rbytes ierrors opackets obytes oerrors
igb2 469 30016 0 0 0 0
ipackets rbytes ierrors opackets obytes oerrors
igb2 628 40192 0 0 0 0
もう少し詳しく状況を調査するため、igb1 を通るパケットを snoop コマンドでキャプチャします。snoop コマンドに -o オプションを付けて、保存先のファイル名を指定するとパケットを保存する事が出来ます。保存したファイルから読み込む場合は -i オプションでファイルを指定します。また、snoop コマンドに port 80 オプションを付けると、80 番ポートを通ったパケットだけを抽出する事が出来ます。同じく src 192.168.10.3 オプションを付けた場合は、送信元アドレスが 192.168.10.3 のパケットだけを抽出します。snoop コマンドのデフォルトの出力は一つのパケットに付き一行ずつ表示されます。出力の内容は、一番左がパケットに順番に振られる通し番号、続いて前のパケットを受け取ってからの経過時間、送信元 IP アドレス、宛先 IP アドレス、残りは通信プロトコル毎の出力です。
snoop コマンドを使用して igb1 上で port 80 番のパケットを見ると、192.168.10.2 から送信されるパケットも igb1 を通って外へ出て行っている事が分かります("->" の左側の送信元アドレスが 192.168.10.2 になっているパケットがあります)。src オプションで抽出すると、受信パケットは 192.168.10.3 から送信された物だけです。これで先ほどの dladm コマンドの出力で送信パケットが片方のインターフェイスしか使っていなかった理由が分かりました。サーバに入ってくるパケットは別々のインターフェイスを通っていますが、サーバから出て行くパケットは igb1 のインターフェイスしか使っていない様です。しかし、これでは返信パケットは片方のインターフェイスの帯域しか使用する事が出来ません。また 192.168.10.2 は igb2 に設定された IP アドレスなのに何故 igb1 から出て行っているのでしょうか。
<<サーバ>>
# snoop -d igb1 -o /var/tmp/snoop01.dump
...
\^C
# snoop -i /var/tmp/snoop01.dump port 80
1 0.00000 192.168.10.3 -> 192.168.10.1 HTTP C port=42875
2 0.00012 192.168.10.1 -> 192.168.10.3 HTTP R port=42875
3 0.00010 192.168.10.3 -> 192.168.10.1 HTTP C port=42875
4 2.21303 192.168.10.2 -> 192.168.10.4 HTTP R port=37300
5 16.15477 192.168.10.3 -> 192.168.10.1 HTTP GET /001.txt HTTP/1.0
...
# snoop -i /var/tmp/snoop01.dump src 192.168.10.3
1 0.00000 192.168.10.3 -> 192.168.10.1 HTTP C port=42875
2 0.00023 192.168.10.3 -> 192.168.10.1 HTTP C port=42875
3 18.36780 192.168.10.3 -> 192.168.10.1 HTTP GET /001.txt HTTP/1.0
4 2.85314 192.168.10.3 -> 192.168.10.1 HTTP (body)
5 0.00014 192.168.10.3 -> 192.168.10.1 HTTP C port=42875
...
# snoop -i /var/tmp/snoop01.dump src 192.168.10.4
送信元アドレスが 192.168.10.2 のパケットが何故 igb1 から送り出されるのかを調べる為にサーバ側でルーティングテーブルを確認します。netstat -ar コマンドの出力を見ると、クライアント A(192.168.10.3) 宛の経路もクライアント B(192.168.10.4) 宛の経路も igb1 になっている事が分かります。192.168.10.4 宛のパケットの送信元アドレスは igb2 に割り当てた 192.168.10.2 ですが、192.168.10.4 宛の経路は igb1 が選択されています。これが送信元アドレスが 192.168.10.2 のパケットが igb1 から送信されていた原因です。
<<サーバ>>
# netstat -ar | egrep '192.168.10.3|192.168.10.4'
192.168.10.3 -- UHA 1 1 igb1
192.168.10.4 -- UHA 1 1 igb1
たまたま経路情報が偏っていたという可能性も考えられますので、念の為にルーティングのキャッシュを消去して何度も接続をテストしてみます。結果は以下の通り、何度接続し直しても経路は片方のネットワークインターフェイスにまとめられてしまいます。これが IPMP を使用しない場合の挙動です。
<<サーバ側でルーティングテーブルのエントリを消去する>>
# arp -d 192.168.10.3; arp -d 192.168.10.4
192.168.10.3 (192.168.10.3) deleted
192.168.10.4 (192.168.10.4) deleted
# arp -d 192.168.10.3; arp -d 192.168.10.4
192.168.10.3 (192.168.10.3) -- no entry
192.168.10.4 (192.168.10.4) -- no entry
<<再びクライアントから接続した後にルーティングテーブルを確認>>
# netstat -ar | egrep '192.168.10.3|192.168.10.4'
192.168.10.3 -- UHA 2 6 igb1
192.168.10.4 -- UHA 2 6 igb1
単純にネットワークにインターフェイスを繋いだ場合、クライアントから送られて来たパケットは宛先 IP アドレスの通りのインターフェイスを通り、サーバから返信されるパケットは常に一つのインターフェイスだけを通る事が分かりました。言い換えれば、受信パケットは 2 つのインターフェイスの帯域を有効に利用しているのに対し、返信パケットが使用出来る帯域はポート 1 つ分しかないことになります。
ご覧頂きました通り、ここまでの構成では返信パケットは常に片方のポートしか通りませんでした。これでは帯域の利用効率は良くありません。IPMP のアウトバウンド・ロード・スプレッディングを使用する事で両方のポートを利用する様に変更する事が可能です。見てみましょう。
これまでの構成に加えて、サーバの igb1 と igb2 で IPMP を組みます。IPMP の構成は簡単です。ifconfig の group オプションに適当な IPMP グループ名を指定するだけで完了します。グループ名は ipmp としましたが、その他の文字列でも構いません。
# ifconfig igb1 group ipmp
# ifconfig igb2 group ipmp
IPMP を組んで ifconfig でインターフェイスの一覧を見ると groupname ipmp と表示され、インターフェイスが IPMP に参加している事が分かります。
# ifconfig -a
...
igb1: flags=1000843<UP,BROADCAST,RUNNING,MULTICAST,IPv4> mtu 1500 index 3
inet 192.168.10.1 netmask ffffff00 broadcast 192.168.10.255
groupname ipmp
ether 0:14:4f:cb:16:b1
igb2: flags=1000843<UP,BROADCAST,RUNNING,MULTICAST,IPv4> mtu 1500 index 4
inet 192.168.10.2 netmask ffffff00 broadcast 192.168.10.255
groupname ipmp
ether 0:14:4f:cb:16:b2
先ほどと同じ様に HTTP で接続してテストを行います。クライアント A は 192.168.10.1 に、クライアント B は 192.168.10.2 に、同時に接続します。返信パケットが負荷分散されるかどうか確認しましょう。
netstat コマンドで経路情報を見てみると、今度はクライアント A(192.168.10.3) への接続は igb1 を、クライアント B(192.168.10.4) への接続は igb2 を通る様になっている事が分かります。IPMP を組む前は両方とも igb1 を通る様になっていましたが、IPMP を組むと別々のインターフェイスを使う様になりました。
<<サーバ>>
# netstat -ar | egrep '192.168.10.3|192.168.10.4'
192.168.10.3 -- UHA 1 1 igb1
192.168.10.4 -- UHA 1 1 igb2
続いて dladm コマンドを使用して、実際にどの経路を通って通信が行われているのかを見てみます。以下の出力結果を見ると、igb1 の opackets も igb2 の opackets も 0 以外の数値になっています。IPMP を組んだことにより、返信のパケットも 2 つのネットワークインターフェイスに股がって分散される様になりました。
<<サーバ>>
# dladm show-dev -s -i 1 igb1
ipackets rbytes ierrors opackets obytes oerrors
igb1 369 23616 0 718 1067024 0
ipackets rbytes ierrors opackets obytes oerrors
igb1 384 24576 0 746 1107084 0
ipackets rbytes ierrors opackets obytes oerrors
igb1 384 24576 0 746 1104508 0
# dladm show-dev -s -i 1 igb2
ipackets rbytes ierrors opackets obytes oerrors
igb2 542 34688 0 1056 1555293 0
ipackets rbytes ierrors opackets obytes oerrors
igb2 414 26496 0 806 1189420 0
ipackets rbytes ierrors opackets obytes oerrors
igb2 452 28928 0 882 1308835 0
igb1 を snoop すると、igb1 のインターフェイスはクライアント A(192.168.10.3) と 192.168.10.1 の間の通信のみに使われている事が分かります。
# snoop -d igb1 -o /var/tmp/snoop_igb1.dump
# snoop -i /var/tmp/snoop_igb1.dump host 192.168.10.1
1 0.00000 192.168.10.3 -> 192.168.10.1 HTTP C port=42878
2 0.00002 192.168.10.1 -> 192.168.10.3 HTTP R port=42878
3 0.00010 192.168.10.3 -> 192.168.10.1 HTTP C port=42878
4 7.98546 192.168.10.3 -> 192.168.10.1 HTTP GET /001.txt HTTP/1.0
5 0.00001 192.168.10.1 -> 192.168.10.3 HTTP R port=42878
...
# snoop -i /var/tmp/snoop_igb1.dump host 192.168.10.2 <-- 192.168.10.2 との通信には使用されていない
# snoop -i /var/tmp/snoop_igb1.dump host 192.168.10.3
1 0.00000 192.168.10.3 -> 192.168.10.1 HTTP C port=42878
2 0.00002 192.168.10.1 -> 192.168.10.3 HTTP R port=42878
3 0.00010 192.168.10.3 -> 192.168.10.1 HTTP C port=42878
4 7.98546 192.168.10.3 -> 192.168.10.1 HTTP GET /001.txt HTTP/1.0
5 0.00001 192.168.10.1 -> 192.168.10.3 HTTP R port=42878
...
# snoop -i /var/tmp/snoop_igb1.dump host 192.168.10.4 <-- 192.168.10.4 との通信には使用されていない
一方 igb2 はクライアント B(192.168.10.4) と 192.168.10.2 の間の通信にのみ使用されている事が分かります。
# snoop -d igb2 -o /var/tmp/snoop_igb2.dump
# snoop -i /var/tmp/snoop_igb2.dump host 192.168.10.1 <-- 192.168.10.1 との通信には使用されていない
# snoop -i /var/tmp/snoop_igb2.dump host 192.168.10.2
1 0.00000 192.168.10.4 -> 192.168.10.2 HTTP C port=37303
2 0.00001 192.168.10.2 -> 192.168.10.4 HTTP R port=37303
3 0.00009 192.168.10.4 -> 192.168.10.2 HTTP C port=37303
4 6.40605 192.168.10.4 -> 192.168.10.2 HTTP GET /001.txt HTTP/1.0
5 0.00001 192.168.10.2 -> 192.168.10.4 HTTP R port=37303
...
# snoop -i /var/tmp/snoop_igb2.dump host 192.168.10.3 <-- 192.168.10.3 との通信には使用されていない
# snoop -i /var/tmp/snoop_igb2.dump host 192.168.10.4
1 0.00000 192.168.10.4 -> 192.168.10.2 HTTP C port=37303
2 0.00001 192.168.10.2 -> 192.168.10.4 HTTP R port=37303
3 0.00009 192.168.10.4 -> 192.168.10.2 HTTP C port=37303
4 6.40605 192.168.10.4 -> 192.168.10.2 HTTP GET /001.txt HTTP/1.0
5 0.00001 192.168.10.2 -> 192.168.10.4 HTTP R port=37303
...
IPMP を使用すると、サーバからの返信時もネットワーク負荷が複数のインターフェイスに分散される事が分かりました。これで複数のネットワークインターフェイスの帯域を最大限に有効利用する事が出来ます。
一度クライアントまでの経路が決定されると、その後の接続はキャッシュを元に経路が選択される様になります。既に見た通り、キャッシュは netstat -ar で確認、arp -d で削除する事が可能です。このキャッシュの有効期限は ip_ire_arp_interval と arp_cleanup_interval パラメータで設定する事が出来ます。もし経路に偏りが発生していた場合は有効期限を短く設定すると経路選択の機会が増えて偏りが解消されるかもしれません。ip_ire_arp_interval と arp_cleanup_interval の確認方法と設定方法は以下の通りです。
# ndd -get /dev/ip ip_ire_arp_interval
1200000
# ndd -set /dev/ip ip_ire_arp_interval 60000
# ndd -get /dev/arp arp_cleanup_interval
300000
# ndd -set /dev/arp arp_cleanup_interval 30000
今回は IPMP のアウトバウンド・ロード・スプレッディング機能による負荷分散をご紹介しました。要件次第では、簡単にネットワークの実行帯域を増やす事が可能です。特にウェブサーバやファイルサーバは、IPMP のアウトバウンド・ロード・スプレッディングだけで十分かもしれません。サンのサーバの多くは最小構成でもネットワークインターフェイスを4ポート装備しています。Solaris の機能を活用し、是非それらのポートを有効利用して下さい。
# ifconfig igb1 group ''
# ifconfig igb2 group ''
# ifconfig -a
...
igb1: flags=1000843<UP,BROADCAST,RUNNING,MULTICAST,IPv4> mtu 1500 index 3
inet 192.168.10.1 netmask ffffff00 broadcast 192.168.10.255
groupname ipmp
ether 0:14:4f:cb:16:b1
igb2: flags=1000843<UP,BROADCAST,RUNNING,MULTICAST,IPv4> mtu 1500 index 5
inet 0.0.0.0 netmask ff000000
groupname ipmp
ether 0:14:4f:cb:16:b2
# netstat -ar | egrep '192.168.10.3|192.168.10.4'
192.168.10.3 -- UHA 2 25 igb1
192.168.10.4 -- UHA 2 25 igb2
<<サーバ>>
# netstat -ar | egrep '192.168.11.2|192.168.12.2'
192.168.11.2 192.168.10.3 UHA 1 1 igb2 <-- ルータ経由のクライアント A
192.168.12.2 192.168.10.3 UHA 1 1 igb1 <-- ルータ経由のクライアント B