本格的なOpenFlowプログラミングの第一歩として、スイッチのフローテーブルを書き換えてみましょう。マイクロベンチマークツールCbenchを題材に、Packet InとFlow Modメッセージの使い方を学びます。
CbenchはOpenFlow1.0コントローラのためのベンチマークです。このベンチマークの内容は、1秒あたりにコントローラが出せるFlow Modの数を計測するというものです。これはOpenFlowプロトコル全体のうちのごく一部の性能だけを対象にしているので、ベンチマークの中でもマイクロベンチマークに分類できます。
Cbenchは図 5-1のように動作します。まずcbenchプロセスはOpenFlowスイッチのふりをしてコントローラに接続し、コントローラにPacket Inを連続して送ります。コントローラはPacket Inを受け取るとcbenchプロセスにFlow Modを返します。cbenchプロセスは決められた時間の間に受け取ったFlow Modの数をカウントし、ベンチマークのスコアとします。つまり大量のPacket Inに反応し素早くFlow Modを返せるコントローラほど「速い」とみなします。
Cbenchの実行にはopenflow.org [1] の配布するベンチマークスイートOflopsを使います。GitHubのtrema/cbenchリポジトリには、Oflops一式とCbenchコントローラのソースコードが入っています。次のコマンドでダウンロードしてください。
$ git clone https://github.com/trema/cbench.git
ダウンロードしたソースツリー上で bundle install --binstubs
を実行すると、Tremaの ./bin/trema
コマンドと Cbench の実行コマンド ./bin/cbench
など必要な実行環境一式を自動的にインストールできます。
$ cd cbench $ bundle install --binstubs
以上でCbenchとTremaのセットアップは完了です。
さっそくCbenchを実行してみましょう。まず、コントローラを次のように起動します。
$ ./bin/trema run ./lib/cbench.rb
そして別ターミナルを開き、次のコマンドでcbenchプロセスを実行しベンチマークを開始します[2]。
$ ./bin/cbench --port 6653 --switches 1 --loops 10 --ms-per-test 10000 --delay 1000 --throughput cbench: controller benchmarking tool running in mode 'latency' connecting to controller at localhost:6653 faking 1 switches :: 10 tests each; 10000 ms per test with 100000 unique source MACs per switch starting test with 1000 ms delay after features_reply ignoring first 1 "warmup" and last 0 "cooldown" loops debugging info is off 1 switches: fmods/sec: 807 total = 0.080652 per ms 1 switches: fmods/sec: 797 total = 0.079694 per ms 1 switches: fmods/sec: 799 total = 0.079730 per ms 1 switches: fmods/sec: 797 total = 0.079698 per ms 1 switches: fmods/sec: 801 total = 0.080003 per ms 1 switches: fmods/sec: 800 total = 0.079965 per ms 1 switches: fmods/sec: 802 total = 0.080159 per ms 1 switches: fmods/sec: 802 total = 0.080182 per ms 1 switches: fmods/sec: 806 total = 0.080549 per ms 1 switches: fmods/sec: 801 total = 0.080082 per ms RESULT: 1 switches 9 tests min/max/avg/stdev = 79.69/80.55/80.01/0.26 responses/s
この例では、10秒間のベンチマークを10回実行しその結果を表示しています。fmods/sec
の右側の数字が、実際に 1 秒間に打った Flow Mod の数です。実行環境によって値は変わりますが、Trema は秒間に数百回のFlow Modを打てることがわかります。
Cbenchが正しく実行できたところで、CbenchのソースコードからPacket InとFlow Modメッセージの処理方法を見ていきましょう。ファイルは lib/cbench.rb です。
link:vendor/cbench/lib/cbench.rb[role=include]
Cbench のソースコードを眺めると、いくつか見慣れない品詞や構文が登場していることに気付きます。この節では順にそれぞれを紹介していきますが、最初からすべてを覚える必要はありません。もし後でわからなくなったときには見直すようにしてください。
Cbench のソースコードにはいくつかのメソッド呼び出しがあります。
-
logger.info(…)
(3 章「Hello, Trema!」で解説) -
ExactMatch.new(…)
-
packet_in.buffer_id
-
SendOutPort.new(…)
-
packet_in.in_port
このようにメソッドは普通、変数や定数の後にドットでつなげます。定数や変数が名詞なら、メソッドはちょうど動詞と同じです。
door.open
上の例では open
がメソッドです。英語のopenは動詞なので、当然メソッドであるとも言えます。
ふつう、メソッド呼び出しによって何らかの動作をすると新しい結果が得られます。
'redrum'.reverse
#=> "murder"
この場合、文字が逆順になった新しい文字列が返ってきました。
メソッドは引数を取るものもあります。次の例は配列の各要素の間に指定した文字をはさんで連結 (join
) します。
['M', 'A', 'S', 'H'].join('★')
#=> "M★A★S★H"
Rubyにはこのようなメソッドが何百種類もあります。それぞれの動作は名前から大体想像できるものがほとんどです。
link:vendor/cbench/lib/cbench.rb[role=include]
前章と同じく、start
ハンドラでコントローラの起動をログに書き込みます。引数は今回も使っていないので、名前を _args
のようにアンダースコアで始めます。
コントローラに上がってくる未知のパケットを拾うには、Packet Inハンドラをコントローラクラスに実装します。Packet Inハンドラは次の形をしています。
def packet_in(datapath_id, packet_in)
...
end
packet_in
ハンドラはその引数として、Packet Inを起こしたスイッチ(cbenchプロセス)の Datapath ID とPacket Inメッセージを受け取ります。
packet_in
ハンドラの2番目の引数はPacket Inメッセージオブジェクトで、PacketIn
クラスのインスタンスです。この PacketIn
クラスは主に次の3種類のメソッドを持っています。
-
Packet Inを起こしたパケットのデータやその長さ、およびパケットが入ってきたスイッチのポート番号などOpenFlowメッセージ固有の情報を返すメソッド
-
Packet Inを起こしたパケットの種別 (TCPかUDPか。VLANタグがついているかどうか、など)を判定するための
?
で終わるメソッド -
送信元や宛先のMACアドレスやIPアドレスなど、パケットの各フィールドを調べるためのアクセサメソッド
PacketIn
クラスは非常に多くのメソッドを持っており、またTremaのバージョンアップごとにその数も増え続けているためすべては紹介しきれません。そのかわり、代表的でよく使うものを表5-1に紹介します。
メソッド | 説明 |
---|---|
:raw_data |
パケットのデータ全体をバイナリ文字列で返す |
:in_port |
パケットが入ってきたスイッチのポート番号を返す |
:buffered? |
Packet Inを起こしたパケットがスイッチにバッファされているかどうかを返す |
:buffer_id |
バッファされている場合、そのバッファ領域の ID を返す |
:total_length |
パケットのデータ長を返す |
:source_mac_address |
パケットの送信元MACアドレスを返す |
:destination_mac_address |
パケットの宛先MACアドレスを返す |
:ipv4? |
パケットがIPv4である場合 |
:ipv4_protocol |
IPのプロトコル番号を返す |
:ipv4_source_address |
パケットの送信元IPアドレスを返す |
:ipv4_destination_address |
パケットの宛先IPアドレスを返す |
:ipv4_tos |
IPのToSフィールドを返す |
:tcp? |
パケットがTCPである場合 |
:tcp_source_port |
パケットのTCPの送信元ポート番号を返す |
:tcp_destination_port |
パケットのTCP宛先ポート番号を返す |
:udp? |
パケットがUDPである場合 |
:udp_source_port |
パケットのUDPの送信元ポート番号を返す |
:udp_destination_port |
パケットのUDPの宛先ポート番号を返す |
:vlan? |
パケットにVLANヘッダが付いている場合 |
:vlan_vid |
VLANのVIDを返す |
:vlan_priority |
VLANの優先度を返す |
:ether_type |
イーサタイプを返す |
このようなメソッドは他にもたくさんあります。完全なメソッドのリストや詳しい情報を知りたい場合には、3 章「Hello, Trema!」で紹介した Trema ホームページを参照してください。
Cbenchの仕様によると、コントローラからCbenchへと送るFlow Modメッセージは、次の内容にセットする必要があります。
-
マッチフィールド: Packet In メッセージのExactMatch (後述)
-
アクション: Packet In メッセージのin_portに+1したポートへ転送
-
バッファID: Packet In メッセージのバッファID
それぞれの指定方法を順に見ていきましょう。
マッチフィールドを指定するには、send_flow_mod_add
の引数に match:
オプションとしてマッチフィールドオブジェクト (Match.new(…)
または ExactMatch.new(…)
) を渡します。
send_flow_mod_add(
datapath_id,
match: Match.new(...), # (1)
...
)
-
マッチフィールドを指定する
match:
オプション
マッチフィールドを作るには、Match.new
に指定したい条件のオプションを渡します。たとえば、送信元 MAC アドレスが 00:50:56:c0:00:08 で VLAN ID が 3 というルールを指定したマッチフィールドを Flow Mod に指定するコードは、次のようになります。
send_flow_mod_add(
datapath_id,
match: Match.new(
source_mac_address: '00:50:56:c0:00:08'
vlan_vid: 3
)
...
OpenFlow 1.0 において指定できるマッチフィールドは 12 種類です。Match.new
のオプションには、以下の12種類の条件を指定できます(表5-2)。
オプション | 説明 |
---|---|
:in_port |
スイッチの物理ポート番号 |
:source_mac_address |
送信元MACアドレス |
:destination_mac_address |
宛先MACアドレス |
:ether_type |
イーサネットの種別 |
:source_ip_address |
送信元IPアドレス |
:destination_ip_address |
宛先IPアドレス |
:ip_protocol |
IPのプロトコル種別 |
:tos |
IPのToSフィールド |
:transport_source_port |
TCP/UDPの送信元ポート番号 |
:transport_destination_port |
TCP/UDPの宛先ポート番号 |
:vlan_vid |
VLAN IDの値 |
:vlan_priority |
VLANのプライオリティ |
Note
|
2章「OpenFlow の仕様」で説明したように、OpenFlow 1.3 でマッチフィールドは 40 種類に増えました。しかし、OpenFlow 1.3 での Match オブジェクトのオプションの指定方法は、OpenFlow 1.0 の場合と変わりません。詳しくは OpenFlow 1.3 を扱う 8章「OpenFlow1.3版ラーニングスイッチ」や14章「ルータ (マルチプルテーブル編)」を参照してください。 |
マッチフィールドの中でもすべての条件を指定したものをExact Matchと呼びます。たとえばPacket Inとしてコントローラに入ってきたパケットとマッチフィールドが定義する12個の条件がすべてまったく同じ、というのがExact Matchです。
マッチフィールドを作る構文 Match.new
にこの12種類の条件すべてを渡せば、次のようにExact Matchを作れます。
def packet_in(datapath_id, packet_in)
...
send_flow_mod_add(
datapath_id,
match: Match.new(
in_port: packet_in.in_port,
source_mac_address: packet_in.source_mac_address,
destination_mac_address: packet_in.destination_mac_address,
...
しかし、マッチフィールドを1つ作るだけで12行も書いていたら大変です。そこで、TremaではPacket InメッセージからExact Matchを楽に書ける次のショートカットを用意しています。
def packet_out(datapath_id, packet_in)
send_flow_mod_add(
datapath_id,
match: ExactMatch.new(packet_in),
...
たった1行で書けました! Tremaにはこのようにコードを短く書ける工夫がたくさんあります。
Note
|
ExactMatch が使えるのは OpenFlow 1.0 のみです。OpenFlow 1.3 ではマッチフィールドの種類が増えたため、ExactMatch は廃止されました。 |
アクションを指定するには、send_flow_mod_add
の引数に actions:
オプションとして単体のアクションまたはアクションのリストを渡します[3]。
send_flow_mod_add(
datapath_id,
...
actions: アクション # (1)
)
または
send_flow_mod_add(
datapath_id,
...
actions: [アクション0, アクション1, アクション2, ...] # (2)
)
-
actions:
オプションでアクションを 1 つ指定 -
actions:
オプションにアクションを複数指定
たとえば、「VLAN ヘッダを除去しポート2番に転送」というアクションを Flow Mod に指定するコードは、次のようになります。
send_flow_mod_add(
datapath_id,
...
actions: [StripVlanHeader.new, SendOutPort.new(2)] # (1)
)
-
アクションを 2 つ指定
アクションには表5-3の13種類のアクションを単体で、または組み合わせて指定できます。
アクション | 説明 |
---|---|
SendOutPort |
指定したスイッチポートにパケットを出力する |
SetEtherSourceAddress |
送信元MACアドレスを指定した値に書き換える |
SetEtherDestinationAddress |
宛先MACアドレスを指定した値に書き換える |
SetIpSourceAddress |
送信元のIPアドレスを指定した値に書き換える |
SetIpDstinationAddress |
宛先のIPアドレスを指定した値に書き換える |
SetIpTos |
IPのToSフィールドを書き換える |
SetTransportSourcePort |
TCP/UDPの送信元ポート番号を書き換える |
SetTransportDestinationPort |
TCP/UDPの宛先ポート番号を書き換える |
StripVlanHeader |
VLANのヘッダを除去する |
SetVlanVid |
指定したVLAN IDをセットする、または既存のものがあれば書き換える |
SetVlanPriority |
指定したVLANプライオリティをセットする、または既存のものがあれば書き換える |
Enqueue |
指定したスイッチポートのキューにパケットを入れる |
VendorAction |
ベンダ定義の独自拡張アクションを実行する |
まだ使っていないアクションについては、続く章で具体的な使い方を見ていきます。
バッファIDを指定するには、buffer_id:
オプションを send_flow_mod_add
の引数に指定します。たとえば次のコードは、バッファ ID に Packet Inメッセージのバッファ ID を指定する典型的な例です。
send_flow_mod_add(
datapath_id,
match: ...,
actions: ...,
buffer_id: packet_in.buffer_id # (1)
)
-
Flow Mod のオプションにバッファ ID を指定
send_flow_mod_add
で指定できるすべてのオプションは表5-4の通りです。
オプション | 説明 |
---|---|
:match |
フローエントリのマッチフィールドを指定する。本章で紹介した |
:actions |
フローエントリのアクションを指定する。アクションには単体のアクションまたは複数のアクションを配列 (4章で解説) によって指定できる |
:buffer_id |
アクションが参照するパケットがバッファされている領域の ID を指定する |
:idle_timeout |
フローエントリが一定時間参照されなかった場合に破棄されるまでの秒数を指定する。デフォルトは0秒で、この場合フローエントリは破棄されない |
:hard_timeout |
フローエントリの寿命を秒数で指定する。デフォルトは0秒で、この場合フローエントリは破棄されない |
:priority |
フローエントリの優先度(符号なし16ビット、大きいほど優先度高)。Packet Inメッセージはこの優先度順にフローエントリのマッチフィールドと照らし合わされる。デフォルトは |
:send_flow_removed |
タイムアウトでフローエントリが消えるときに、Flow Removedメッセージをコントローラに送るかどうかを指定する。デフォルトは |
:check_overlap |
|
:emerg |
|
:cookie |
任意の用途に使える64ビットの整数。使い道としては、同じクッキー値を持つフローエントリ同士をまとめて管理するなどといった用途がある |
こうしたオプションも、続くいくつかの章で具体的な使い方を紹介します。
Note
|
OpenFlow1.0 では2章「OpenFlow の仕様」で解説したインストラクションは使いません。そのためインストラクションの代わりに、アクションを直接フローエントリに指定します。OpenFlow 1.3 でのインストラクションの使い方は、8章「OpenFlow1.3版ラーニングスイッチ」にて詳しく説明します。 |
Tremaはシングルスレッドで動作するので、実のところ Cbench の結果はあまり速くありません。シングルスレッドとはつまり、同時に1つの packet_in
ハンドラしか実行できないという意味です。たとえ cbench プロセスがたくさん Packet In メッセージを送ってきても、Trema は順に 1 つひとつ処理するため遅くなります。
Trema がシングルスレッドである理由は、マルチスレッドプログラミング由来のやっかいなバグを避けるためです。たとえば次のようなマルチスレッドで動作する multi_threaded_packet_in
というハンドラがあったとして、この中でスレッドセーフでない変数の内容を変更すると、タイミングや環境に起因するやっかいなバグが発生してしまいます。
def start(_args)
@db = DB.new # (1)
end
# マルチスレッド版 packet_in ハンドラ
def multi_threaded_packet_in(datapath_id, packet_in) # (2)
# !!! ここで @db の読み書きは危険 !!!
return if @db.lookup(packet_in.in_port)
@db.add packet_in.source_mac_address, packet_in.in_port
end
-
スレッドセーフでないインスタンス変数
-
独立したスレッドで動く Packet In ハンドラ
マルチスレッドプログラミングでは、スレッド間で共有するリソースに競合が起こらないように、注意深くコードを書く必要があるのです。
スレッド間の競合を解決する代表的な方法が Mutex による排他制御です。スレッド間で競合の起こる箇所を Mutex で囲むことで、その箇所へは同時に 1 つのスレッドしか入れなくなります。
def start(_args)
@db = DB.new
@mutex = Mutex.new # (1)
end
def multi_threaded_packet_in(datapath_id, packet_in)
@mutex.synchronize do # (2)
# この中で@dbを読み書きすれば安全
return if @db.lookup(packet_in.in_port)
@db.add packet_in.source_mac_address, packet_in.in_port
end
end
-
排他制御用の Mutex
-
do…end
の中には同時に 1 つのスレッドしか入れない
これでひとまず競合は回避できたので、packet_in
をスレッド化してみましょう。次のように高速化したいハンドラメソッドの中身を別スレッドで起動し、インスタンス変数へのアクセスを Mutex で排他制御してやります。
def start(_args)
@db = DB.new
@mutex = Mutex.new
end
def packet_in(datapath_id, packet_in)
Thread.start do # (1)
@mutex.synchronize do
return if @db.lookup(packet_in.in_port)
@db.add packet_in.source_mac_address, packet_in.in_port
end
end
end
-
packet_in
ハンドラの中身をスレッドで起動
しかし、これでもまだ問題は残ります。Thread.start
によるスレッド起動はそれなりにコストのかかる処理なので、Packet In が届くたびに Tread.new
でスレッドを作っていては速くなりません。
そこで、次のようにあらかじめいくつかワーカースレッドを作って待機させておく、いわゆるスレッドプールという手法が使えます。そして packet_in
ハンドラが呼ばれるたびに待機中のスレッドに packet_in
の処理をまかせます。
def start(_args)
@db = DB.new
@mutex = Mutex.new
@work_queue = Queue.new # (1)
10.times { start_worker_thread } # (2)
end
def packet_in(datapath_id, packet_in)
@work_queue.push [datapath_id, packet_in] # (3)
end
private
# ワーカースレッドを開始
def start_worker_thread # (4)
Thread.new do
loop do
datapath_id, packet_in = @work_queue.pop # (5)
@mutex.synchronize do
next if @db.lookup(packet_in.in_port)
@db.add packet_in.source_mac_address, packet_in.in_port
end
end
end
end
-
スレッドにまかせたい仕事を入れておくキュー
-
スレッドプールに 10 個のスレッドを追加
-
Packet In が届いたら datapath_id と Packet In をキューに入れる
-
ワーカースレッドを起動するプライベートメソッド
-
スレッドの中でキューから datapath_id と Packet In を取り出す。Queue クラスはスレッドセーフなので、
@mutex.synchronize do … end
の中に入れる必要はないことに注意
このスレッドプールを使った、最終的なマルチスレッド版 Cbench コントローラは次のようになります。
# A simple openflow controller for benchmarking (multi-threaded version).
class MultiThreadedCbench < Trema::Controller
def start(_args)
@work_queue = Queue.new
10.times { start_worker_thread }
logger.info 'Cbench started.'
end
def packet_in(datapath_id, packet_in)
@work_queue.push [datapath_id, packet_in]
end
private
def start_worker_thread
Thread.new do
loop do
datapath_id, packet_in = @work_queue.pop
send_flow_mod_add(datapath_id,
match: ExactMatch.new(packet_in),
buffer_id: packet_in.buffer_id,
actions: SendOutPort.new(packet_in.in_port + 1))
end
end
end
end
実際に性能を計測してみましょう。
$ ./bin/trema run lib/multi_threaded_cbench.rb
別ターミナルで Cbench を起動します。
$ ./bin/cbench --port 6653 --switches 1 --loops 10 --ms-per-test 10000 --delay 1000 --throughput cbench: controller benchmarking tool running in mode 'throughput' connecting to controller at localhost:6653 faking 1 switches :: 10 tests each; 10000 ms per test with 100000 unique source MACs per switch starting test with 1000 ms delay after features_reply ignoring first 1 "warmup" and last 0 "cooldown" loops debugging info is off 1 switches: fmods/sec: 748 total = 0.074746 per ms 1 switches: fmods/sec: 714 total = 0.071319 per ms 1 switches: fmods/sec: 705 total = 0.070448 per ms 1 switches: fmods/sec: 704 total = 0.070376 per ms 1 switches: fmods/sec: 718 total = 0.071747 per ms 1 switches: fmods/sec: 734 total = 0.073346 per ms 1 switches: fmods/sec: 739 total = 0.073763 per ms 1 switches: fmods/sec: 736 total = 0.073487 per ms 1 switches: fmods/sec: 732 total = 0.073146 per ms 1 switches: fmods/sec: 730 total = 0.072917 per ms RESULT: 1 switches 9 tests min/max/avg/stdev = 70.38/73.76/72.28/1.25 responses/s
おや?シングルスレッド版よりも若干遅くなってしまいました。これには 2 つの原因があります。まず、Ruby のスレッドは OS のネイティブスレッドであるため、スレッド切り替えのオーバーヘッドが大きくかかります。しかも、Packet In 処理は一瞬で終わるため、マルチスレッド化しても並列性はあまり上がりません。これらの原因から、マルスレッド化によって新たにスレッド切り替えのオーバーヘッドがかかった分、元のバージョンより遅くなってしまったのです。
ほかに高速化の方法はないでしょうか。実は cbench プロセスが送ってくる Packet In はすべて同じ中身なので、cbench プロセスへ送る Flow Mod メッセージを何度も使いまわすことで簡単に高速化できます。最初のコードでは Packet In ハンドラの中で send_flow_mod_add
で毎回 Flow Mod メッセージを作り直していました。この無駄な処理をなくすために、一度作った Flow Mod メッセージをキャッシュしておいて、2 回目以降はキャッシュした Flow Mod を send_message
で送るのです。
このキャッシュによって高速化したものがこちらです。ただしこれはただ Cbench のために無理やり高速化したコードなので、すべてを理解する必要はありません。
# A simple openflow controller for benchmarking (fast version).
class FastCbench < Trema::Controller
def start(_args)
logger.info "#{name} started."
end
def packet_in(dpid, packet_in)
@flow_mod ||= create_flow_mod_binary(packet_in) # (1)
send_message dpid, @flow_mod # (2)
end
private
def create_flow_mod_binary(packet_in)
options = {
command: :add,
priority: 0,
transaction_id: 0,
idle_timeout: 0,
hard_timeout: 0,
buffer_id: packet_in.buffer_id,
match: ExactMatch.new(packet_in),
actions: SendOutPort.new(packet_in.in_port + 1)
}
FlowMod.new(options).to_binary.tap do |flow_mod| # (3)
def flow_mod.to_binary
self
end
end
end
end
-
最初は
@flow_mod
がnil
なので、最初だけcreate_flow_mod_binary
で Flow Mod メッセージを作る。二回目以降は呼ばれない -
キャッシュした Flow Mod メッセージを cbench プロセスに送る
-
send_message
のときに Trema が呼び出すFlowMod#to_binary
を軽くするため、キャッシュしたバイナリを返す特異メソッドを定義
実行結果は次のようになります。秒間約 6000 発の Flow Mod が打てており、元のバージョンに比べて 10 倍近く高速化できました!
cbench: controller benchmarking tool running in mode 'throughput' connecting to controller at localhost:6653 faking 1 switches :: 10 tests each; 10000 ms per test with 100000 unique source MACs per switch starting test with 1000 ms delay after features_reply ignoring first 1 "warmup" and last 0 "cooldown" loops debugging info is off 1 switches: fmods/sec: 6741 total = 0.674018 per ms 1 switches: fmods/sec: 6400 total = 0.639859 per ms 1 switches: fmods/sec: 6508 total = 0.650710 per ms 1 switches: fmods/sec: 6334 total = 0.633349 per ms 1 switches: fmods/sec: 6325 total = 0.632465 per ms 1 switches: fmods/sec: 6293 total = 0.629207 per ms 1 switches: fmods/sec: 6276 total = 0.627579 per ms 1 switches: fmods/sec: 6332 total = 0.633133 per ms 1 switches: fmods/sec: 6219 total = 0.621860 per ms 1 switches: fmods/sec: 6293 total = 0.629266 per ms RESULT: 1 switches 9 tests min/max/avg/stdev = 621.86/650.71/633.05/7.77 responses/s
Warning
|
Cbenchの注意点
Cbench のスコアを盲信しないようにしてください。現在、OpenFlow コントローラの多くがその性能指標として Cbench のスコアを使っているのをよく見掛けます。たとえば Floodlight (http://www.projectfloodlight.org/) は 1 秒間に 100 万発の Flow Mod を打てると宣伝しています。実際にこれはなかなかすごい数字です。スレッドを駆使してめいっぱい I/O を使い切るようにしなければなかなかこの数字は出ません。でも、この数字はほとんど無意味です。 Flow Mod を 1 秒間に 100 万発打たなければならない状況を考えてみてください。それは、Packet In が 1 秒間に 100 万回起こるということになります。Packet In が 1 秒間に 100 万発起こるとはどういうことでしょうか。スイッチで処理できないパケットがすべてコントローラへやってくる、これが 1 秒間に 100 万回も起こるということです。明らかにフローテーブルの設計がうまく行っていません。 コントローラが Packet In を何発さばけるかという性能は、極端に遅くない限りは重要ではありません。データセンターのように、どこにどんなマシンがありどういう通信をするか把握できている場合は、フローテーブルをちゃんと設計していれば Packet In はそんなに起こらないからです。力技で Packet In をさばくよりも、いかに Packet In が起こらないフローテーブル設計をするかの方がずっと大事です。 Cbench のようなマイクロベンチマークでは、測定対象が何でその結果にはどんな意味があるかを理解しないと、針小棒大な結論を招きます。Cbench のスコアは参考程度にとどめましょう。 |
Packet InとFlow Modの最初の一歩として、ベンチマークツールCbenchと接続できるコントローラを書きました。
-
フローエントリを追加するための
send_flow_mod_add
を使って、スイッチのフローテーブルを書き換える方法を学んだ -
マッチフィールドの作り方と、指定できるルールを学んだ
-
SendOutPort アクションによるパケットの転送と、その他のアクションを学んだ
-
コントローラをマルチスレッド化する方法を学んだ
OpenFlow プログラミングの基礎はできたので、そろそろ実用的なツールを作ってみましょう。続く章では、遠隔操作可能なソフトウェアパッチパネルを作ります。もう、ネットワークケーブルを挿し替えるためだけにサーバルームまで出向く必要はなくなります。