IaaS (Infrastructure as a Service) の構築に必要な大規模ネットワークを OpenFlow で実現しましょう。16 章で紹介したルーティングスイッチの応用です。
クラウドサービスの核となる機能は仮想化です。たとえばクラウドサービスの一種である IaaS は、サーバやネットワークといった物理リソースを仮想化し、まるで雲 (クラウド) のように大きな仮想リソースとしてユーザに提供します。ユーザは自分専用のリソースをこの仮想リソースプールからいつでも好きなときに借り出せます。
クラウドサービスが制御する物理リソースのうち、ネットワークの仮想化は OpenFlow の得意分野です。物理リソースのうちサーバの仮想化は、Xen などの仮想マシンモニタを使えば、一台のサーバ上に何台もの仮想マシンを起動することで多数のユーザを集約できます。もう1つの物理リソースであるネットワークの仮想化については、後に説明するように OpenFlow コントローラで同様の仕組みを実現できます。この 2 つの組み合わせにより、クラウドサービスは「仮想マシン + 仮想ネットワーク」という専用環境をユーザごとに提供します。
本章で取り上げる「スライス機能付きスイッチ」は、ネットワークを仮想化するコントローラです (図 17-1)。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
) を使えば、このネットワーク構成を実現できます。
スライス機能を有効にするには、ルーティングスイッチの trema run
に -- --slicing
オプションを付けてください。
$ ./bin/trema run ./lib/routing_switch.rb -c trema.conf -- --slicing
それでは起動したスライス機能付きスイッチを使って、さっそくいくつかスライスを作ってみましょう。
スライスの作成には slice
コマンドを使います。2 枚のスライス slice1
、 slice2
を作り、それぞれに 2 台ずつホストを追加してみましょう (図 17-3)。
スライスの追加は slice add
コマンドです。
$ ./bin/slice add slice1 $ ./bin/slice add slice2
slice add_host
コマンドでスライスにホストを追加します。host1
、host4
のポートと MAC アドレスを slice1
に、host2
と host3
を slice2
に、それぞれ追加します。
$ ./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 です。
-
同じスライスに属するホスト間で通信できること
-
異なるスライスに属するホスト間で通信できないこと
これは今までやってきた通り、trema send_packet
と trema show_stats
コマンドで簡単に確認できます。たとえば同じスライス slice1
に属するホスト host1
と host4
で通信できることを確認するには、お互いにパケットを 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
たしかに、slice1
の host1
から slice2
の host2
へのパケットは届いていません。以上から、1 つのネットワークが 2 つの独立したスライスにうまく分割できていることが確認できました。
スライス機能付きスイッチは OpenStack などのミドルウェアと連携するための REST API を提供しています。REST API はプログラミング言語を問わず使えるため、スライス機能付きスイッチの持つ仮想ネットワーク機能をさまざまなミドルウェアに簡単に組込めます。
スライス機能付きスイッチの REST API は Ruby の HTTP サーバ実装である WEBrick で動作します (図17-4)。WEBrick に「スライスの作成」や「ホストの追加」といったリクエストを HTTP で送ると、WEBrick はリクエスト内容をスライス機能付きスイッチ経由でネットワークへと反映します。また、現在のスライスやホストの状態も同様に REST API 経由で取得できます。
REST API の起動は次のコマンドです。スライス機能付きスイッチを起動した後に rackup
コマンドで WEBrick を起動します。
$ ./bin/trema run ./lib/routing_switch.rb -c trema.conf -d -- --slicing $ ./bin/rackup
それでは実際にいくつか 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"}
-
スライス作成成功を示す 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
-
ホスト追加成功を示す 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 は今回紹介した以外にも API を提供しています (表 17-1)。やりとりする JSON データ等の詳しい仕様は https://relishapp.com/trema/routing-switch/docs/rest-api で公開していますので、本格的に使いたい人はこちらも参照してください。
動作 | メソッド | URI |
---|---|---|
スライスの作成 |
POST |
|
スライスの削除 |
DELETE |
|
スライスの一覧 |
GET |
|
スライス情報の取得 |
GET |
|
ポートの追加 |
POST |
|
ポートの削除 |
DELETE |
|
ポートの一覧 |
GET |
|
ポート情報の取得 |
GET |
|
MAC アドレスの追加 |
POST |
|
MAC アドレスの削除 |
DELETE |
|
MAC アドレスの一覧 |
GET |
|
実はスライス機能は、15章で説明したルーティングスイッチへのほんの少しの改造だけで実現しています。コントローラとOpenFlowスイッチの視点で見ると、スライス機能付きスイッチは次のように動作します(図17-5)。
-
ホスト 1 がホスト 4 宛てにパケットを送信すると、ルーティングスイッチはこのパケットを Packet In としてスイッチ 1 から受け取る (この Packet In の in_port をポート s とする)
-
ルーティングスイッチはあらかじめ収集しておいたトポロジ情報 (14章) を検索し、宛先のホスト 4 が接続するスイッチ (スイッチ 6) とポート番号 (ポート g とする) を得る
-
ポート s とポート g が同じスライスに属するか判定する。もし同じスライスではない場合にはパケットを捨て、以降の処理は行わない
-
ポート s から宛先のポート g までの最短パスをトポロジ情報から計算する。その結果、ポート s → スイッチ 1 → スイッチ 5 → スイッチ 6 → ポート g というパスを得る
-
この最短パスに沿ってパケットを転送するフローエントリを書き込むために、ルーティングスイッチはパス上のスイッチそれぞれに Flow Mod を送る
-
Packet In を起こしたパケットを宛先に送るために、ルーティングスイッチはスイッチ 6 に Packet Out (ポート g) を送る
スライス機能付きスイッチがルーティングスイッチと異なるのは、ステップ 3 を追加した点だけです。ステップ 3 では送信元と宛先ホストがそれぞれ同じスライスに属しているかを判定し、同じスライスに所属している場合のみパケットを転送します。それ以外はルーティングスイッチとまったく同じです。
スライス機能は、ルーティングスイッチに次の新たなクラス 2 個を追加することで実現しています (図 17-6)。
- PathInSliceManager クラス
-
スライス内のパスを管理するコントローラの本体
- Slice クラス
-
スライスを管理する
PathInSliceManager クラスは packet_in
ハンドラでパケットの送信元と宛先が同じスライスに属するかどうかを判定します。それ以外の動作は PathManager クラスと同じなので、PathInSliceManager は PathManager を継承し packet_in
ハンドラだけをオーバーライドします。
link:vendor/routing_switch/lib/path_in_slice_manager.rb[role=include]
Packet In メッセージが PathInSliceManager へ到着すると、PathInSliceManager は次の方法でパケットを宛先へと届けます。
-
パケットの送信元 MAC アドレスと宛先 MAC アドレスが同じスライスに属するかどうか判定する。もし同じスライスだった場合には、PathManager と同様に最短パスを作り宛先ホストへパケットを届ける
-
もし同じスライスでなかった場合、パケットをすべての外部ポート (スライス機能付きスイッチが管理するスイッチ以外と接続した全てのポート) へ PacketOut する。つまり、スライスに属していないホストへとばらまく[2]
ステップ 1 で使っている Slice.find
メソッド (パケットの送信元と宛先が同じスライスに属するかどうか) といったスライスに関わる処理は、次の Slice
クラスが行います。
Slice クラスはスライスの管理クラスです。スライスの追加・削除や検索といったクラスメソッドのほか、スライスへのポートやホストの追加・削除といった機能を提供します。
たとえば先ほど使った Slice.find
メソッドは、スライスの一覧 (all
) に対して同じ find
メソッドを呼び出すだけです。
link:vendor/routing_switch/lib/slice.rb[role=include]
スライスの追加メソッド Slice.create
は指定した名前でスライスを作成します。最初に、すでに同じ名前のスライスがないかどうかを Slice.find_by
で確認します。そして、スライスオブジェクトを Slice.new
で作ります。作ったスライスはスライス一覧 (all
) に追加します。
link:vendor/routing_switch/lib/slice.rb[role=include]
Slice.destroy
は create
の逆で、スライスの削除メソッドです。最初に、削除しようとした名前のスライスがあるかどうかを Slice.find_by!
で確認します。そして、削除するスライスに属する最短パス (Path
オブジェクト) を削除します。最後に、そのスライスをスライス一覧 all
から消します。
link:vendor/routing_switch/lib/slice.rb[role=include]
Hello Trema から始めた Trema プログラミングも、いつの間にか本格的なクラウド用ネットワークを作れるまでになりました!
-
スライス機能付きスイッチが同一のスライス内の通信のみを許可する仕組み
-
クラウド構築ミドルウェアからスライスを設定するためのREST APIの使い方
次章では、Trema を使った仮想ネットワークソフトウェアであり、商用クラウドにも使われている OpenVnet を紹介します。本章で解説したスライス機能付きスイッチとはまったく異なる「分散 Trema」とも言えるスライスの実現方法は、商用クラウドの作り方として参考になります。