こんな夜中にOpenFlowでネットワークをプログラミング!

第11回Trema編】実践あるのみ! 生活ネットワークをOpenFlowに移行しよう

はじめに

やり方は3つしかない。

正しいやり方。間違ったやり方。俺のやり方だ

『カジノ』⁠マーティン・スコセッシ)

そろそろ独り立ちするときがやってきました。これまで本連載では、OpenFlowコントローラの書き方とTremaの仮想ネットワークを使った実行方法[1]を知り、テスト駆動によるOpenFlowコントローラの開発手法[2]を学びました。Tremaのソースコードを探検し、その設計思想にも触れました[3]⁠。OpenFlowプログラマとしてやっていくための基本的な知識はすべて身につきました。

次は何をすればよいでしょうか? あとはやってみるだけです。まずは自宅のネットワークをOpenFlowで置き換えましょう。うまくいったら、こんどは職場のネットワークをOpenFlowで置き換えましょう。その環境で実際に暮らしてみて、初めて見えてくるアイデアや改善案があります。これは、とにかくやってみなければ絶対にわからないことです。

「怒られるかもしれない⁠⁠。あなたはそう考えます。家のネットワークはともかく、職場のネットワークを止めてしまったらどうしよう……。管理者や上司に注意されたらどうしよう……。大丈夫です。筆者たちも何度も怒られたことがありますが、その経験からうまくやる方法を学びました。

今回は我々の経験を踏まえ、既存のネットワークを⁠穏便に⁠OpenFlowに移行するためのテクニックを教えます。ちょっとしたOpenFlowプログラムを書くだけで、移行の際に起こりがちなネットワーク障害を簡単に防げます。まずは、筆者たちの失敗談を振り返らせてください。

失敗談

話は2009年7月までさかのぼります。OpenFlowが登場したばかりの当時、筆者たちはさっそくOpenFlowコントローラを書いて小さなOpenFlowネットワークを職場に構築しました。うまく動作して気を良くした我々は、職場のネットワークとこのOpenFlowネットワークをいきなりつないでみました。まあ大丈夫だろうと楽観的に考えていたのです。結果的にはすぐにネットワーク障害が起こり、事態に気づいた管理者からお叱りのメールを受け取ることになりました。

当時の状況を単純化すると図1のようになります。

図1 障害を起こしたときのネットワーク構成を簡略化したもの
図1 障害を起こしたときのネットワーク構成を簡略化したもの

職場ネットワーク(レガシーネットワークとします)のスイッチにはホストがつながれており、そのうちのスイッチポート3番をOpenFlowスイッチのスイッチポート1番と接続しました。このOpenFlow スイッチは、我々が書いたBuggyController というOpenFlowコントローラで制御されています。

「警告が出ているんですけど」

具体的な障害の症状はこうでした。レガシーネットワークとOpenFlowネットワークを接続してすぐに、レガシースイッチにつながったホストどうしが通信できなくなりました。そして、ネットワークを監視するwatchdogプログラムが「Host Flappingが起こっている」という警告を出しました。これは、1つのホストがいくつかのポートの間で高速で移動しているように見えるというものです。我々はすぐにOpenFlowネットワークを切断し、原因の分析にとりかかりました。

障害の原因

分析の結果、次のようなシナリオで起こっているのではないかという結論に至りました。

  • ① host1がhost2へパケットを送信する
  • ② BuggyControllerはOpenFlowスイッチポート1番からのpacket_inを受け取り、OpenFlowスイッチのスイッチポート1番にhost1がつながっていると学習する
  • ③ host2がhost1へパケットを送信する
  • ④ BuggyControllerはスイッチポート1番から「宛先=host1」のpacket_inを受け取る。ここで、host1はOpenFlowスイッチのスイッチポート1番にあると学習しているので、スイッチポート1番にpacket_outする
  • ⑤ 結果的に、host1はポート2と3の両方から同じパケットを受け取る。外から見ると、host2がスイッチポート2番と3番を高速に移動しているように見える

つまり、BuggyController が予期せぬパケットをレガシーネットワークに送ったおかげでネットワークが混乱し、通信できない状況が起きたのです。

【教訓】これをやってはいけない

振り返ると、失敗した原因は2つありました。

1つは、OpenFlowネットワークをいきなりレガシーネットワークとつないでしまったことです。OpenFlowネットワーク単体では動いていたという言い訳はありますが、いきなりつないでしまったのは若気の至り&経験不足でした。

もう1つは、BuggyControllerがpacket_inしてきたスイッチポートにpacket_outしていたことです。assertを入れるなど防御的プログラミングが徹底できていれば防げるバグでしたが、残念ながら当時の我々では気づくことができませんでした。レガシーネットワークにつないで始めて顕在化するバグと言えます。

OpenFlowへの移行パターン

大失敗をやらかしてしまった筆者たちは、作戦を練りなおさざるを得なくなりました。いろいろな方向から考えなおしたところ、OpenFlowへの移行方法には次の3つのパターンがあることがわかりました。もちろん、それぞれでメリット/デメリットや危険度が異なります。

①独立ネットワークパターン

最初のパターンは、既存のレガシーネットワークにまったく手を加えずに、独立した形でOpenFlowネットワークを構築する方法です図2⁠。それぞれのネットワーク間でパケットの行き来はなく、お互いに完全に独立しています。

図2 レガシーネットワークとは独立したOpenFlowネットワークを構築し、徐々に拡大する
図2 レガシーネットワークとは独立したOpenFlowネットワークを構築し、徐々に拡大する

この状態から、レガシーネットワーク内のサーバや端末を徐々にOpenFlowネットワークに移動することで移行していきます。

それぞれのネットワーク間ではパケットが行き来できないので、OpenFlowネットワークがレガシーネットワークに悪影響を及ぼす可能性はほとんどありません。ただし、OpenFlowネットワークに移行する際には関連する機器どうし(ファイルサーバとクライアント群など)を一度に移行する必要があります。これはトラブルを起こす可能性が高いため、移行が難しいという問題があります。

②いきなり接続パターン

次のパターンは、我々がやったようにレガシーネットワークとOpenFlowネットワークをいきなりつなげてしまう方法です図3⁠。

図3 レガシーネットワークとOpenFlowネットワークを直結してしまう
図3 レガシーネットワークとOpenFlowネットワークを直結してしまう

相互に通信できるのでネットワーク間でのサーバや端末の移動は自由にできます。このため、独立ネットワークパターンに比べて移行の手間はずっと小さいと言えます。

OpenFlowネットワークのコントローラが完璧に作られていれば、このようにいきなりつなげても問題はありませんが、完璧なテストというのは難しいです。ユニットテストによって関数レベルで細かくテストすることもできますが[4]⁠、それだけでは不十分です。というのも、我々が失敗したように、生のトラフィックをコントローラに流し込んでみて初めて見つかるバグがあるからです。よって、この方法は自宅ネットワークなど自由にいじれるネットワーク以外では推奨できません。

③逆流防止パターン

最後のパターンは、今までに挙げてきた2つのパターンのいいとこどりです。2つのネットワークを接続するのですが、そのときに「逆流防止弁」を付けてパケットの逆流が起きないようにします図4⁠。

図4 レガシーネットワークとOpenFlowネットワークの間での逆流を防止する
図4 レガシーネットワークとOpenFlowネットワークの間での逆流を防止する

たとえば、⁠レガシーネットワーク→OpenFlowネットワーク」のような一方向のパケットは通しますが、同じパケットがレガシー側に戻ることを防ぎます。逆方向でも同じです。

この方法の利点は、逆流を防ぐだけでかなりの障害を未然に防げることです。また、使い勝手はいきなり接続した場合と同じなのでOpenFlowへの移行も楽です。

検討の結果、このパターンが一番良さそうでした。この逆流防止弁はOpenFlowコントローラとして実装できそうです。前置きが長くなりましたが、さっそくTremaを使って実装してみましょう。

逆流防止弁

逆流防止弁は1つのpacket_inに対して2つのフローを設定します。1つは順方向のフローで、入ってきたパケットをもう1つのスイッチポートに転送します。もう1つは逆方向のフローで、同じパケットが逆方向に流れてきたときにこのパケットを落とします。

実装

逆流防止弁(OneWayBridgeコントローラ)のソースコードをリスト1に示します。このコントローラは、packet_inとflow_removedのハンドラを定義しています。

リスト1 逆流防止弁(OneWayBridgeコントローラ)
class OneWayBridge < Controller

  def packet_in datapath_id, message ← 順方向と逆方向のフローを設定する
    out_port = { 1 => 2, 2 => 1 }[ message.in_port ]
    add_flow datapath_id, message.macsa, message.in_port, out_port
    send_packet datapath_id, message, out_port
    add_drop_flow datapath_id, message.macsa, out_port
  end

  def flow_removed datapath_id, message ← 順方向と逆方向のフローのいずれかが消えたときに、もう1つも消す
    delete_flow datapath_id, message.match.dl_src
  end

  private ← 以下、プライベートメソッド

  def add_flow datapath_id, macsa, in_port, out_port ← 送信元MACアドレスがmacsaで、スイッチポートin_portからout_portへのフローを追加
    send_flow_mod_add(
      datapath_id,
      :idle_timeout => 10 * 60,
      :match => Match.new( :in_port => in_port, :dl_src => macsa ),
      :actions => ActionOutput.new( :port => out_port )
    )
  end

  def send_packet datapath_id, message, out_port ← パケットをスイッチポートout_portへ転送
    send_packet_out(
      datapath_id,
      :packet_in => message,
      :actions => ActionOutput.new( :port => out_port )
    )
  end

  def add_drop_flow datapath_id, macsa, in_port ← 逆流してきたパケットを落とすフローを追加(:actionsを指定していないので、マッチしたパケットを落とす)
    send_flow_mod_add(
      datapath_id,
      :idle_timeout => 10 * 60,
      :match => Match.new( :in_port => in_port, :dl_src => macsa )
    )
  end

  def delete_flow datapath_id, macsa ← 順方向と逆方向のフローを両方とも消す
    send_flow_mod_delete(
      datapath_id,
      :match => Match.new( :dl_src => macsa )
    )
  end
end

packet_inハンドラでは、packet_inしたスイッチポートとは別のポートへパケットを転送するフロー(たとえば、スイッチポート1番から入ってきたパケットはスイッチポート2番に転送するフロー)を設定し(add_flowメソッド⁠⁠、実際にパケットを転送します(send_packetメソッド⁠⁠。また、同じパケットが逆向きに流れないようにするフローを設定することで逆流を防ぎます(add_drop_flowメソッド⁠⁠。

flow_removedハンドラは、順方向または逆方向のフローが消えたときに呼ばれます。これらのフローはどちらもdl_srcに同じMACアドレスが指定されているので、delete_flowでもう片方を消します。なおここではやっていませんが、flow_removed メッセージに乗ってくる統計情報[5]を使って、逆流パケットがあった場合には警告メッセージを出すようにするとさらに効果的でしょう。

実行

それではさっそく実行してみましょう。実行のためには、レガシーネットワークとOpenFlowネットワークの間にOneWayBridgeコントローラで制御する仮想スイッチ(vswitch)をはさみます図5⁠。vswitchのポートは、vswitchを実行するマシンのNIC(eth0、eth1)に結び付けます。

図5 逆流防止弁(OneWayBridgeコントローラ)を実行するときの物理構成
図5 逆流防止弁(OneWayBridgeコントローラ)を実行するときの物理構成

図5の物理構成をTrema設定ファイルにしたものがリスト2(one-way-bridge.conf)です。仮想リンク(link で始まる行)の端点にインターフェース名eth0、eth1を指定していることに注目してください。

リスト2 逆流防止弁(OneWayBridgeコントローラ)の設定ファイル
vswitch ( "bridge" ) {
  datapath_id 0xabc
}

link "bridge", "eth0"
link "bridge", "eth1"

実行するには、この設定ファイルをtrema runの-cオプションに渡します。

% ./trema run ./one-way-bridge.rb -c ./one-way-bridge.
conf

使ってみた

さっそくこの逆流防止弁を導入したところ、期待していたとおり、問題は起こらなくなりました。現在、OpenFlowスイッチ5台、ホスト約100台から構成されるOpenFlowネットワークを職場ネットワークと接続して運用しています。もちろん、このOpenFlowネットワークはどんどん拡大しつつあり、ゆくゆくは職場ネットワークを置き換える予定です。

まとめ

職場のネットワークを安全にOpenFlowに移行するためのTipsを学びました。今回学んだことは次の2つです。

  • 既存のレガシーネットワークをOpenFlowに移行するいくつかのパターンを見ました。自宅ネットワークなど自由にできるネットワークでは「いきなり接続パターン」で十分ですが、職場ネットワークでは「逆流防止パターン」が最適です
  • 逆流防止弁を実現するOpenFlowコントローラを実装しました。基本的には2つのフローを設定するだけで、簡単に逆流を防止できます

これまでは、おもにレイヤ2スイッチング機能の実装を紹介してきましたが、OpenFlowではIPなどより上位のレイヤの情報を用いることができます。例として、次回はIPマルチキャスト転送を行うスイッチをOpenFlowで実現します。

おすすめ記事

記事・ニュース一覧