SDN開発エンジニアを目指した活動ブログ

〜SDNなオープンソース製品を実際に使って試してみる〜

ネットワーク仮想化技術の実践活用(2) 〜OpenvSwitch管理プロトコル(OVSDB)編〜

ネットワーク仮想化環境の特徴は、VxLAN等のオーバレイ技術を活用して多拠点間をトンネルで接続するモデルだと思います。
ただ、拠点数(n)に対して、拠点間をフルメッシュn*(n − 1)本のトンネルを構築する必要があります。
前回のブログ記事では、2拠点をVxLANトンネルで構築する程度のため、OpenvSwitchのコマンドラインで構築しましたが、拠点数が多くなると、やっぱり、集中管理的に拠点間トンネル設定を行いたくなりますよね。ということで、今回は、OpenvSwitch管理プロトコル「OVSDB」に着目したいと思います。

◆OVSDBとは ?

OVSDBとは、OpenvSwitchのデータモデルを管理する手法です。
RDBMSにおけるSQL的な役割で、DDL(Data Definition Language:データ定義言語)の側面と、DML(Data Manipulation Language:データ操作言語)の側面を併せ持っているものです。
ざっと、OpenvSwitchのテーブル構成は、以下のようになってます。
Open_vSwitch --> Open vSwitch configuration.
Bridge --> Bridge configuration.
Port --> Port configuration.
Interface --> One physical network device in a Port. Flow_Table OpenFlow table configuration
QoS --> Quality of Service configuration Queue QoS output queue.
Mirror --> Port mirroring.
Controller --> OpenFlow controller configuration. Manager OVSDB management connection. NetFlow NetFlow configuration.
SSL --> SSL configuration.
sFlow --> sFlow configuration.
IPFIX --> IPFIX configuration. Flow_Sample_Collector_Set
Flow_Sample_Collector_Set --> configuration.

また、OpenvSwitchデータベースのスキーマ定義などの詳細は、以下のリンクが参考になります。
http://openvswitch.org/ovs-vswitchd.conf.db.5.pdf

Open_vSwitchテーブル構造

OpenvSwitchデータベースを構成する多数のテーブルのうち、「Open_vSwitch」テーブル構造を、確認してみます。

tsubo@OFS1:~$ sudo ovs-vsctl list Open_vSwitch
_uuid               : 82524775-43a3-4838-90ae-61e2cbd10820
bridges             : [55d71da8-cd33-4117-b7a3-de8a5b4222a3]
cur_cfg             : 12
db_version          : "7.3.0"
external_ids        : {system-id="a7812d37-5389-4ae6-b003-42a66a6e05fe"}
manager_options     : [0cc00f03-5c47-404a-9a31-66e5105f48d2]
next_cfg            : 12
other_config        : {}
ovs_version         : "2.0.1"
ssl                 : []
statistics          : {}
system_type         : Ubuntu
system_version      : "12.04-precise"

ovs-vsctlコマンドと、いろいろと引数を指定してあげれば、様々なテーブル情報を参照できるようです。

◆外部からOpenvSwitchデータベースにアクセスするには ?

1. OpenvSwitch管理プロトコル

VMware社により、IETFのInformational RFC化されているようですが、OpenvSwitch専用だと思います。
他の類似技術として、OpenFlowスイッチ管理プロトコル「OF-Config」があります。

OVSDB管理手法の特徴としては、JSON-RPCをベースとしたデータモデリングを前提としている点です。
データ検索クエリのデータフォーマットとしては、"method", "params", "id"という構造となるようです。

RFC 7047 :The Open vSwitch Database Management Protocol

4.1.5. Monitor

   The "monitor" request enables a client to replicate tables or subsets
   of tables within an OVSDB database by requesting notifications of
   changes to those tables and by receiving the complete initial state
   of a table or a subset of a table.  The request object has the
   following members:

   o  "method": "monitor"

   o  "params": [<db-name>, <json-value>, <monitor-requests>]

   o  "id": <nonnull-json-value>

   The <json-value> parameter is used to match subsequent update
   notifications (see below) to this request.  The <monitor-requests>
   object maps the name of the table to be monitored to an array of
   <monitor-request> objects.

   Each <monitor-request> is an object with the following members:

       "columns": [<column>*]            optional
       "select": <monitor-select>        optional

   The columns, if present, define the columns within the table to be
   monitored.

<snip>

2. 外部機器からデータ操作するための事前準備

外部機器から、OpenvSwitchデータベースにアクセスするために、事前に、以下のコマンドを実施しておきます。

$ sudo ovs-vsctl set-manager ptcp:6632

ポート番号は、今回は、とりあえず、"6632"としました。

3. 実際に「Open_vSwitchテーブル」を参照してみる

それでは、外部装置から、以下のPythonプログラムで「Open_vSwitchテーブル」を参照してみます。

#-*- coding: utf-8 -*-

import socket
import json

OVSDB_IP = '192.168.0.1'
OVSDB_PORT = 6632

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((OVSDB_IP, OVSDB_PORT))

monitor_query = \
{"method":"monitor",
"params":["Open_vSwitch","null",{"Open_vSwitch":{"columns":["bridges","cur_cfg","db_version","external_ids","manager_options","next_cfg","other_config","ovs_version","ssl","statistics","system_type","system_version"]}}],
"id":0}

print "----------------------------------"
print "json rpc request"
print "----------------------------------"
print json.dumps(monitor_query, sort_keys=False, indent=1)
s.send(json.dumps(monitor_query))

print "----------------------------------"
print "json rpc reply"
print "----------------------------------"
response = s.recv(8192)
result = json.loads(response)
print json.dumps(result, sort_keys=False, indent=1)

Pythonプログラムの実行結果は、こんな感じです。
さきほど、"sudo ovs-vsctl list Open_vSwitch"の実行結果と対比してみると、OVSDBの挙動がイメージしやすいかもしれません。

$ python ovsdb_show.py 
----------------------------------
json rpc request
----------------------------------
{
 "params": [
  "Open_vSwitch", 
  "null", 
  {
   "Open_vSwitch": {
    "columns": [
     "bridges", 
     "cur_cfg", 
     "db_version", 
     "external_ids", 
     "manager_options", 
     "next_cfg", 
     "other_config", 
     "ovs_version", 
     "ssl", 
     "statistics", 
     "system_type", 
     "system_version"
    ]
   }
  }
 ], 
 "method": "monitor", 
 "id": 0
}
----------------------------------
json rpc reply
----------------------------------
{
 "error": null, 
 "id": 0, 
 "result": {
  "Open_vSwitch": {
   "82524775-43a3-4838-90ae-61e2cbd10820": {
    "new": {
     "bridges": [
      "uuid", 
      "55d71da8-cd33-4117-b7a3-de8a5b4222a3"
     ], 
     "statistics": [
      "map", 
      []
     ], 
     "db_version": "7.3.0", 
     "next_cfg": 12, 
     "ovs_version": "2.0.1", 
     "other_config": [
      "map", 
      []
     ], 
     "ssl": [
      "set", 
      []
     ], 
     "system_type": "Ubuntu", 
     "external_ids": [
      "map", 
      [
       [
        "system-id", 
        "a7812d37-5389-4ae6-b003-42a66a6e05fe"
       ]
      ]
     ], 
     "system_version": "12.04-precise", 
     "cur_cfg": 12, 
     "manager_options": [
      "uuid", 
      "0cc00f03-5c47-404a-9a31-66e5105f48d2"
     ]
    }
   }
  }
 }
}

◆集中管理的にVxLANトンネルを設定してみる

OpenvSwitch管理プロトコル概要が理解できたところで、それでは、もう少し実用的なOVSDB活用にチャレンジしてみます。

1. 検証構成

基本的な検証構成は、前回のブログ記事と同様です。
ただし、今回は、OpenFlowコントローラを使用せず、OVSDBコントローラとして、Ryuコントローラを活用しました。

f:id:ttsubo:20140323213802j:plain

2. VxLANトンネル設定プログラム

OVSDBを制御するPythonプログラムは、以下になります。OVSDBコントローラのNorthBoundインタフェースとして、REST-APIを採用しております。

import json

from webob import Response
from ryu.base import app_manager
from ryu.app.wsgi import ControllerBase, WSGIApplication, route
from ryu.lib.dpid import DPID_PATTERN
from ryu.lib.ovs import bridge

OVSDB_ADDR1 = 'tcp:192.168.0.1:6632'
OVSDB_ADDR2 = 'tcp:192.168.0.2:6632'

class SetOvsdb(app_manager.RyuApp):

    _CONTEXTS = {
        'wsgi': WSGIApplication
    }

    def __init__(self, *args, **kwargs):
        super(SetOvsdb, self).__init__(*args, **kwargs)
        wsgi = kwargs['wsgi']
        wsgi.register(OvsdbController, {'SetOvsdb' : self})


    def setTunnel(self, id, name, type, local_ip, remote_ip):
        if id == 1:
            ovs_bridge = bridge.OVSBridge(id, OVSDB_ADDR1)
        elif id == 2:
            ovs_bridge = bridge.OVSBridge(id, OVSDB_ADDR2)
        else:
            return 1

        ovs_bridge.init()
        ovs_bridge.add_tunnel_port(name, type, local_ip, remote_ip)
        return 0



class OvsdbController(ControllerBase):
    def __init__(self, req, link, data, **config):
        super(OvsdbController, self).__init__(req, link, data, **config)
        self.ovsdb_spp = data['SetOvsdb']

    @route('router', '/ovsdb/{dpid}/tunnel', methods=['POST'], requirements={'dpid': DPID_PATTERN})
    def set_tunnel_port(self, req, dpid, **kwargs):

        tunnel_param = eval(req.body)
        result = self.setTunnel(int(dpid, 16), tunnel_param)

        message = json.dumps(result)
        return Response(status=200,
                        content_type = 'application/json',
                        body = message)


    def setTunnel(self, id, tunnel_param):
        simpleOvsdb = self.ovsdb_spp
        name = tunnel_param['tunnel']['name']
        type = tunnel_param['tunnel']['type']
        local_ip = tunnel_param['tunnel']['local_ip']
        remote_ip = tunnel_param['tunnel']['remote_ip']

        simpleOvsdb.setTunnel(id, name, type, local_ip, remote_ip)

        return {
            'id': '%016d' % id,
            'tunnel': {
                'name': '%s' % name,
                'type': '%s' % type,
                'local_ip': '%s' % local_ip,
                'remote_ip': '%s' % remote_ip
            }
        }

3. OVSDBコントローラ上でのプログラム動作イメージ

実際に、プログラムを動作させてみました。REST-API経由で、VxLANトンネル設定パラメータを受信した様子がわかると思います。

$ ryu-manager setOvsdb.py
loading app setOvsdb.py
creating context wsgi
instantiating app setOvsdb.py of SetOvsdb
(4017) wsgi starting up on http://0.0.0.0:8080/
(4017) accepted ('127.0.0.1', 37197)
127.0.0.1 - - [23/Mar/2014 17:35:17] "POST /ovsdb/0000000000000001/tunnel HTTP/1.1" 200 250 0.285298
(4017) accepted ('127.0.0.1', 37201)
127.0.0.1 - - [23/Mar/2014 17:35:21] "POST /ovsdb/0000000000000002/tunnel HTTP/1.1" 200 250 0.287827

4. OFS1へのVxLANトンネル設定

実際に、OVSDBコントローラの上位より、REST-APIを経由してOFS1用のVxLANトンネル設定パラメータを指定しました。

$ curl -s -X POST -d '{"tunnel": {"name": "vxlan1", "type": "vxlan", "local_ip": "172.16.0.1", "remote_ip": "172.16.0.2"}}' http://localhost:8080/ovsdb/0000000000000001/tunnel | python -mjson.tool
{
    "id": "0000000000000001", 
    "tunnel": {
        "local_ip": "172.16.0.1", 
        "name": "vxlan1", 
        "remote_ip": "172.16.0.2", 
        "type": "vxlan"
    }
}

結果として、OFS1上で、想定どおりにVxLANトンネルが構築できました。

tsubo@OFS1:~$ sudo ovs-vsctl show
82524775-43a3-4838-90ae-61e2cbd10820
    Manager "ptcp:6632"
    Bridge "br0"
        Controller "tcp:192.168.0.100:6633"
        Port "eth1"
            Interface "eth1"
        Port "br0"
            Interface "br0"
                type: internal
        Port "vxlan1"
            Interface "vxlan1"
                type: vxlan
                options: {local_ip="172.16.0.1", remote_ip="172.16.0.2"}
    ovs_version: "2.0.1"

5. OFS2へのVxLANトンネル設定

同様に、OVSDBコントローラの上位より、REST-APIを経由してOFS2用のVxLANトンネル設定パラメータを指定しました。

$ curl -s -X POST -d '{"tunnel": {"name": "vxlan2", "type": "vxlan", "local_ip": "172.16.0.2", "remote_ip": "172.16.0.1"}}' http://localhost:8080/ovsdb/0000000000000002/tunnel | python -mjson.tool
{
    "id": "0000000000000002", 
    "tunnel": {
        "local_ip": "172.16.0.2", 
        "name": "vxlan2", 
        "remote_ip": "172.16.0.1", 
        "type": "vxlan"
    }
}

結果として、OFS2上で、想定どおりにVxLANトンネルが構築できました。

tsubo@OFS2:~$ sudo ovs-vsctl show
845f5da2-fb63-4273-b32a-c4cf77b0b943
    Manager "ptcp:6632"
    Bridge "br0"
        Controller "tcp:192.168.0.100:6633"
        Port "br0"
            Interface "br0"
                type: internal
        Port "eth2"
            Interface "eth2"
        Port "vxlan2"
            Interface "vxlan2"
                type: vxlan
                options: {local_ip="172.16.0.2", remote_ip="172.16.0.1"}
    ovs_version: "2.0.1"

6. VxLANトンネル上での通信確認

VxLANトンネル構築が完了したので、ちゃんと動作できるかの確認として、MacBook Airからインターネットに接続して、Ustream映像を閲覧してみました。ustream映像も問題なく再生できておりました。あと、余談ですが、ustream映像の「BABYMETAL」は、要注目ですよ。
f:id:ttsubo:20140323215130j:plain

7. 留意事項

VxLANトンネルの登録・削除を繰り返すと、OFポート番号が、インクリメントしてしまうようです。
OpenFlowでVxLANトンネルを制御する際には、考慮が必要となりますね。

tsubo@OFS1:~$ sudo ovs-ofctl dump-ports-desc br0 --protocols=OpenFlow13
OFPST_PORT_DESC reply (OF1.3) (xid=0x2):
 1(eth1): addr:00:10:f3:1d:3d:1d
     config:     0
     state:      0
     current:    1GB-FD COPPER AUTO_NEG
     advertised: 10MB-HD 10MB-FD 100MB-HD 100MB-FD 1GB-FD COPPER AUTO_NEG
     supported:  10MB-HD 10MB-FD 100MB-HD 100MB-FD 1GB-FD COPPER AUTO_NEG
     speed: 1000 Mbps now, 1000 Mbps max
 5(vxlan1): addr:de:be:49:23:35:a6
     config:     0
     state:      0
     speed: 0 Mbps now, 0 Mbps max
 LOCAL(br0): addr:00:10:f3:1d:3d:1d
     config:     0
     state:      0
     speed: 0 Mbps now, 0 Mbps max

◆終わりに

以前から、VxLANトンネル等のネットワーク仮想化の構成管理って、OpenvSwitchコマンド主体だと煩雑になる懸念がありました。OVSDBプロトコル自体の活用シーンは、OpenvSwitch専用ツールが前提となります。ただし、今後も、SDN技術領域におけるOpenvSwitchの役割りは大きなウエイトを占めると予想されますので、もっとちゃんと使いこなせるように調査探求していきたいです。