Skip to content

Latest commit

 

History

History
350 lines (255 loc) · 21.7 KB

sliceable_switch.adoc

File metadata and controls

350 lines (255 loc) · 21.7 KB

ネットワークを仮想化する

IaaS (Infrastructure as a Service) の構築に必要な大規模ネットワークを OpenFlow で実現しましょう。16 章で紹介したルーティングスイッチの応用です。

ネットワークをスライスに分ける

クラウドサービスの核となる機能は仮想化です。たとえばクラウドサービスの一種である IaaS は、サーバやネットワークといった物理リソースを仮想化し、まるで雲 (クラウド) のように大きな仮想リソースとしてユーザに提供します。ユーザは自分専用のリソースをこの仮想リソースプールからいつでも好きなときに借り出せます。

クラウドサービスが制御する物理リソースのうち、ネットワークの仮想化は OpenFlow の得意分野です。物理リソースのうちサーバの仮想化は、Xen などの仮想マシンモニタを使えば、一台のサーバ上に何台もの仮想マシンを起動することで多数のユーザを集約できます。もう1つの物理リソースであるネットワークの仮想化については、後に説明するように OpenFlow コントローラで同様の仕組みを実現できます。この 2 つの組み合わせにより、クラウドサービスは「仮想マシン + 仮想ネットワーク」という専用環境をユーザごとに提供します。

本章で取り上げる「スライス機能付きスイッチ」は、ネットワークを仮想化するコントローラです (図 17-1)。1つの物理ネットワークをたくさんのスライス、つまりユーザごとの論理的なネットワークに分割することで、たくさんのユーザを1つの物理ネットワーク上に集約できます。

slice
図 17-1: スライス機能付きスイッチは 1 つの物理ネットワークをたくさんの独立した仮想ネットワークに分割できる

スライスの実現方法

スライスを実現する代表的な既存技術が VLANです。VLAN はスイッチをポート単位や MAC アドレス単位でスライスに分割できます。また VLAN タグと呼ばれる ID をパケットにつけることでスイッチをまたがったスライスも作れます。

ただし、VLAN にはスライス数の上限が 4094 個というプロトコル上の制約があります。このため、オフィスなどといった中小規模ネットワークではともかく、IaaS のようにユーザ数がゆうに数万を越える場合には使えません。

一方 OpenFlow によるスライスではこの制約はありません。フローエントリをうまく使えば、既存の VLAN の仕組みを使わなくてもスライスを実現できるからです。つまり OpenFlow を使えば、「スライス数に制限のない仮想ネットワーク」を作れます。

スライス機能付きスイッチは OpenFlow によるスライスの実装です。これは15章で紹介したルーティングスイッチを改造することにより、上限なくたくさんのスライスを作れるようにしたものです。また、実際に OpenStack などのクラウド構築ミドルウェアの一部として使うことも考慮しており、REST API を通じてスライスの作成/削除などの操作ができます。

インストール

スライス機能付きスイッチを使って、ネットワーク仮想化を実際に試してみましょう。スライス機能付きスイッチのソースコードはルーティングスイッチのリポジトリに入っています。もしルーティングスイッチをまだインストールしていなければ、次のコマンドでインストールしてください。

$ git clone https://github.com/trema/routing_switch.git
$ cd routing_switch
$ bundle install --binstubs

スライス機能付きスイッチを起動する

スライス機能付きスイッチの動作を確認してみましょう。これまで通り Trema のネットワークエミュレータを用いて、図 17-2 のネットワークを作ります。ルーティングスイッチのソースコードに含まれる設定ファイル (trema.conf) を使えば、このネットワーク構成を実現できます。

sliceable switch network
図 17-2: スライス機能付きスイッチを実行するネットワーク

スライス機能を有効にするには、ルーティングスイッチの trema run-- --slicing オプションを付けてください。

$ ./bin/trema run ./lib/routing_switch.rb -c trema.conf -- --slicing

それでは起動したスライス機能付きスイッチを使って、さっそくいくつかスライスを作ってみましょう。

スライスを作る

スライスの作成には slice コマンドを使います。2 枚のスライス slice1slice2 を作り、それぞれに 2 台ずつホストを追加してみましょう (図 17-3)。

creating slices
図 17-3: スライスの作成例

スライスの追加は slice add コマンドです。

$ ./bin/slice add slice1
$ ./bin/slice add slice2

slice add_host コマンドでスライスにホストを追加します。host1host4 のポートと MAC アドレスを slice1 に、host2host3slice2 に、それぞれ追加します。

$ ./bin/slice add_host --port 0x1:1 --mac 11:11:11:11:11:11 --slice slice1
$ ./bin/slice add_host --port 0x6:1 --mac 44:44:44:44:44:44 --slice slice1
$ ./bin/slice add_host --port 0x4:1 --mac 22:22:22:22:22:22 --slice slice2
$ ./bin/slice add_host --port 0x5:1 --mac 33:33:33:33:33:33 --slice slice2

ネットワークがスライスにうまく分割できているか、パケットを送って確認してみましょう。

スライスを確認する

スライスが正しく動作しているか確認するには、次の 2 つを試せば OK です。

  1. 同じスライスに属するホスト間で通信できること

  2. 異なるスライスに属するホスト間で通信できないこと

これは今までやってきた通り、trema send_packettrema show_stats コマンドで簡単に確認できます。たとえば同じスライス slice1 に属するホスト host1host4 で通信できることを確認するには、お互いにパケットを 1 つずつ送信し、それぞれのホストでパケットを 1 つずつ受信できているかどうかを見ます。

$ ./bin/trema send_packet --source host1 --dest host4
$ ./bin/trema send_packet --source host4 --dest host1
$ ./bin/trema show_stats host1
Packets sent:
  192.168.0.1 -> 192.168.0.4 = 1 packet
Packets received:
  192.168.0.4 -> 192.168.0.1 = 1 packet
$ ./bin/trema show_stats host4
Packets sent:
  192.168.0.4 -> 192.168.0.1 = 1 packet
Packets received:
  192.168.0.1 -> 192.168.0.4 = 1 packet

たしかに問題なく通信できています。それでは異なるスライス間での通信はどうでしょう。同様に調べてみましょう。

$ ./bin/trema reset_stats host1
$ ./bin/trema send_packet --source host1 --dest host2
$ ./bin/trema send_packet --source host2 --dest host1
$ ./bin/trema show_stats host1
Packets sent:
  192.168.0.1 -> 192.168.0.2 = 1 packet
$ ./bin/trema show_stats host2
Packets sent:
  192.168.0.2 -> 192.168.0.1 = 1 packet

たしかに、slice1host1 から slice2host2 へのパケットは届いていません。以上から、1 つのネットワークが 2 つの独立したスライスにうまく分割できていることが確認できました。

REST API を使う

スライス機能付きスイッチは OpenStack などのミドルウェアと連携するための REST API を提供しています。REST API はプログラミング言語を問わず使えるため、スライス機能付きスイッチの持つ仮想ネットワーク機能をさまざまなミドルウェアに簡単に組込めます。

スライス機能付きスイッチの REST API は Ruby の HTTP サーバ実装である WEBrick で動作します (図17-4)。WEBrick に「スライスの作成」や「ホストの追加」といったリクエストを HTTP で送ると、WEBrick はリクエスト内容をスライス機能付きスイッチ経由でネットワークへと反映します。また、現在のスライスやホストの状態も同様に REST API 経由で取得できます。

rest overview
図17-4: スライス機能付きスイッチの REST API 構成

REST API の起動は次のコマンドです。スライス機能付きスイッチを起動した後に rackup コマンドで WEBrick を起動します。

$ ./bin/trema run ./lib/routing_switch.rb -c trema.conf -d -- --slicing
$ ./bin/rackup

それでは実際にいくつか REST API を試してみましょう。

REST API でスライスを作る

REST API 経由でスライスを作るには、スライスの情報が入った JSON を HTTP POST で REST サーバに送ります。たとえば yutaro_slice という名前のスライスを作る JSON は次の通りです。

{"name": "yutaro_slice"}

次にこの JSON を /slices という URI に HTTP POST メソッドで送ります。curl コマンドを使えば、次のように手軽に REST サーバとやりとりできます。なお REST サーバである WEBrick のデフォルト待ち受けポートは 9292 です。

$ curl -sS -X POST -d ’{"name": "yutaro_slice"}’ 'http://localhost:9292/slices' -H Content-Type:application/json -v

成功すると次のようにスライスの作成成功を示す HTTP ステータスコード 201 が返ってきます。

* Hostname was NOT found in DNS cache
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 9292 (#0)
> POST /slices HTTP/1.1
> User-Agent: curl/7.35.0
> Host: localhost:9292
> Accept: */*
> Content-Type:application/json
> Content-Length: 21
>
* upload completely sent off: 21 out of 21 bytes
< HTTP/1.1 201 Created # (1)
< Content-Type: application/json
< Content-Length: 21
* Server WEBrick/1.3.1 (Ruby/2.0.0/2014-10-27) is not blacklisted
< Server: WEBrick/1.3.1 (Ruby/2.0.0/2014-10-27)
< Date: Mon, 30 Mar 2015 08:15:22 GMT
< Connection: Keep-Alive
<
* Connection #0 to host localhost left intact
{"name": "yutaro_slice"}
  1. スライス作成成功を示す HTTP ステータスコード 201

スライスにホストを追加する

作ったスライスにはホストを追加できます。追加するホストを指定するには、ホストのつながっているスイッチの dpid とポート番号、そしてホストの MAC アドレスを使います。これをホスト追加の URI である /slices/:slice_id/ports/:port_id/mac_addresses に HTTP POST メソッドで送ります。たとえば、スライス yutaro_slice に dpid = 0x1, ポート番号 = 1, MAC アドレス = 11:11:11:11:11:11 のホストを追加するコマンドは次のようになります。

$ curl -sS -X POST -d ’{"name": "11:11:11:11:11:11"}’ 'http://localhost:9292/slices/yutaro_slice/ports/0x1:1/mac_addresses' -H Content-Type:application/json -v

次のようにホスト追加の成功を示す HTTP ステータスコード 201 が返ってくれば成功です。

[{"name": "11:11:11:11:11:11"}]
* Hostname was NOT found in DNS cache
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 9292 (#0)
> POST /slices/foo/ports/0x1:1/mac_addresses HTTP/1.1
> User-Agent: curl/7.35.0
> Host: localhost:9292
> Accept: */*
> Content-Type:application/json
> Content-Length: 29
>
} [data not shown]
* upload completely sent off: 29 out of 29 bytes
< HTTP/1.1 201 Created # (1)
< Content-Type: application/json
< Content-Length: 31
* Server WEBrick/1.3.1 (Ruby/2.0.0/2014-10-27) is not blacklisted
< Server: WEBrick/1.3.1 (Ruby/2.0.0/2014-10-27)
< Date: Tue, 31 Mar 2015 00:20:45 GMT
< Connection: Keep-Alive
<
{ [data not shown]
* Connection #0 to host localhost left intact
  1. ホスト追加成功を示す HTTP ステータスコード 201

スライスの構成を見る

これまでの設定がきちんと反映されているか確認してみましょう。/slices/:slice_id/ports に HTTP GET メソッドでアクセスすることで、スライスに追加したポート一覧を取得できます。先ほど作った slice_yutaro スライスの情報を取得してみましょう[1]

$ curl -sS -X GET 'http://localhost:9292/slices/yutaro_slice/ports'
[{"name": "0x1:1", "dpid": 1, "port_no": 1}]

たしかに、スライス yutaro_slice にはスイッチ 0x1 のポート 1 番が追加されています。このポートに接続した host1 の情報は /slices/:slice_id/ports/:port_id/mac_addresses で取得できます。

$ curl -sS -X GET 'http://localhost:9292/slices/yutaro_slice/ports/0x1:1/mac_addresses'
[{"name": "11:11:11:11:11:11"}]

REST API 一覧

REST API は今回紹介した以外にも API を提供しています (表 17-1)。やりとりする JSON データ等の詳しい仕様は https://relishapp.com/trema/routing-switch/docs/rest-api で公開していますので、本格的に使いたい人はこちらも参照してください。

Table 1. 表 17-1: REST API 一覧
動作 メソッド URI

スライスの作成

POST

/slices

スライスの削除

DELETE

/slices/:slice_id

スライスの一覧

GET

/slices

スライス情報の取得

GET

/slices/:slice_id

ポートの追加

POST

/slices/:slice_id/ports

ポートの削除

DELETE

/slices/:slice_id/ports/:port_id

ポートの一覧

GET

/slices/:slice_id/ports

ポート情報の取得

GET

/slices/:slice_id/ports/:port_id

MAC アドレスの追加

POST

/slices/:slice_id/ports/:port_id/mac_addresses

MAC アドレスの削除

DELETE

/slices/:slice_id/ports/:port_id/mac_addresses/:mac_address_id

MAC アドレスの一覧

GET

/slices/:slice_id/ports/:port_id/mac_addresses

スライス機能付きスイッチの実装

実はスライス機能は、15章で説明したルーティングスイッチへのほんの少しの改造だけで実現しています。コントローラとOpenFlowスイッチの視点で見ると、スライス機能付きスイッチは次のように動作します(図17-5)。

sliceable switch internals
図 17-5: スライス機能付きスイッチの動作
  1. ホスト 1 がホスト 4 宛てにパケットを送信すると、ルーティングスイッチはこのパケットを Packet In としてスイッチ 1 から受け取る (この Packet In の in_port をポート s とする)

  2. ルーティングスイッチはあらかじめ収集しておいたトポロジ情報 (14章) を検索し、宛先のホスト 4 が接続するスイッチ (スイッチ 6) とポート番号 (ポート g とする) を得る

  3. ポート s とポート g が同じスライスに属するか判定する。もし同じスライスではない場合にはパケットを捨て、以降の処理は行わない

  4. ポート s から宛先のポート g までの最短パスをトポロジ情報から計算する。その結果、ポート s → スイッチ 1 → スイッチ 5 → スイッチ 6 → ポート g というパスを得る

  5. この最短パスに沿ってパケットを転送するフローエントリを書き込むために、ルーティングスイッチはパス上のスイッチそれぞれに Flow Mod を送る

  6. Packet In を起こしたパケットを宛先に送るために、ルーティングスイッチはスイッチ 6 に Packet Out (ポート g) を送る

スライス機能付きスイッチがルーティングスイッチと異なるのは、ステップ 3 を追加した点だけです。ステップ 3 では送信元と宛先ホストがそれぞれ同じスライスに属しているかを判定し、同じスライスに所属している場合のみパケットを転送します。それ以外はルーティングスイッチとまったく同じです。

スライス機能付きスイッチのソースコード

スライス機能は、ルーティングスイッチに次の新たなクラス 2 個を追加することで実現しています (図 17-6)。

PathInSliceManager クラス

スライス内のパスを管理するコントローラの本体

Slice クラス

スライスを管理する

sliceable switch classes
図 17-6: スライス機能付きスイッチを構成するクラス

PathInSliceManager クラス

PathInSliceManager クラスは packet_in ハンドラでパケットの送信元と宛先が同じスライスに属するかどうかを判定します。それ以外の動作は PathManager クラスと同じなので、PathInSliceManager は PathManager を継承し packet_in ハンドラだけをオーバーライドします。

PathInSliceManager#packet_in
link:vendor/routing_switch/lib/path_in_slice_manager.rb[role=include]

Packet In メッセージが PathInSliceManager へ到着すると、PathInSliceManager は次の方法でパケットを宛先へと届けます。

  1. パケットの送信元 MAC アドレスと宛先 MAC アドレスが同じスライスに属するかどうか判定する。もし同じスライスだった場合には、PathManager と同様に最短パスを作り宛先ホストへパケットを届ける

  2. もし同じスライスでなかった場合、パケットをすべての外部ポート (スライス機能付きスイッチが管理するスイッチ以外と接続した全てのポート) へ PacketOut する。つまり、スライスに属していないホストへとばらまく[2]

ステップ 1 で使っている Slice.find メソッド (パケットの送信元と宛先が同じスライスに属するかどうか) といったスライスに関わる処理は、次の Slice クラスが行います。

Slice クラス

Slice クラスはスライスの管理クラスです。スライスの追加・削除や検索といったクラスメソッドのほか、スライスへのポートやホストの追加・削除といった機能を提供します。

たとえば先ほど使った Slice.find メソッドは、スライスの一覧 (all) に対して同じ find メソッドを呼び出すだけです。

スライスの検索 (lib/slice.rb)
link:vendor/routing_switch/lib/slice.rb[role=include]

スライスの追加メソッド Slice.create は指定した名前でスライスを作成します。最初に、すでに同じ名前のスライスがないかどうかを Slice.find_by で確認します。そして、スライスオブジェクトを Slice.new で作ります。作ったスライスはスライス一覧 (all) に追加します。

スライスの追加 (lib/slice.rb)
link:vendor/routing_switch/lib/slice.rb[role=include]

Slice.destroycreate の逆で、スライスの削除メソッドです。最初に、削除しようとした名前のスライスがあるかどうかを Slice.find_by! で確認します。そして、削除するスライスに属する最短パス (Path オブジェクト) を削除します。最後に、そのスライスをスライス一覧 all から消します。

スライスの削除 (lib/slice.rb)
link:vendor/routing_switch/lib/slice.rb[role=include]

まとめ

Hello Trema から始めた Trema プログラミングも、いつの間にか本格的なクラウド用ネットワークを作れるまでになりました!

  • スライス機能付きスイッチが同一のスライス内の通信のみを許可する仕組み

  • クラウド構築ミドルウェアからスライスを設定するためのREST APIの使い方

次章では、Trema を使った仮想ネットワークソフトウェアであり、商用クラウドにも使われている OpenVnet を紹介します。本章で解説したスライス機能付きスイッチとはまったく異なる「分散 Trema」とも言えるスライスの実現方法は、商用クラウドの作り方として参考になります。


1. curl の出力を短くするために、冗長オプション (-v) は省略しています
2. この処理からわかるように、スライスに属していないホスト同士はデフォルトで通信できる仕様になっています