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

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

Ryu SDN Frameworkで、VRRP動作を試してみる

これまで、あまり知られておりませんでしたが、Ryu SDN Frameworkでは、VRRP機能を動作させることができます。
VRRP機能を自由に操れるようになれば、柔軟なネットワーク冗長構成も構築できそうです。
今回の元ネタは、こちらになります。
http://ryu.readthedocs.org/en/latest/test-vrrp.html

◆ まずは、VRRP動作環境の準備

Ryu VRRPを動作させるLinux環境としては、Ubuntu Server版を使用しました。

$ cat /etc/lsb-release 
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=14.04
DISTRIB_CODENAME=trusty
DISTRIB_DESCRIPTION="Ubuntu 14.04.3 LTS"

VRRP動作環境としては、2台のLinuxサーバ間でVRRPを動作させるイメージです。
ただし、今回のブログ記事では、あくまでもVRRPプロトコルによるコントロールプレーンの挙動に特化しております。
VRRPによるネットワーク冗長切り替えに関わるデータプレーンは、スコープ外になります。

 --------------                                           --------------
 |  Ryu       | eth1                                 eth1 |  Ryu       |
 |  VRRP1     | ------------------------------------------|  VRRP2     |
 |            | 192.168.0.2                   192.168.0.3 |            |
 --------------                                           --------------
                              VIP: 192.168.0.1

VRRPを動作させるインタフェースは、"eth1"とします。
必要なアドレス設定を事前に行っておきます。

$ ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default 
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:0c:29:1c:78:07 brd ff:ff:ff:ff:ff:ff
    inet 192.168.195.153/24 brd 192.168.195.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::20c:29ff:fe1c:7807/64 scope link 
       valid_lft forever preferred_lft forever
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:0c:29:1c:78:11 brd ff:ff:ff:ff:ff:ff
    inet 192.168.0.3/24 brd 192.168.0.255 scope global eth1
       valid_lft forever preferred_lft forever
    inet6 fe80::20c:29ff:fe1c:7811/64 scope link 
       valid_lft forever preferred_ft forever

Ryu SDN Frameworkをインストールします。

$ sudo apt-get update
$ sudo apt-get install python-dev
$ sudo apt-get install python-pip
$ sudo apt-get -y install libxml2-dev
$ sudo apt-get -y install python-lxml
$ sudo pip install --upgrade six
$ sudo apt-get install git
$ git clone https://github.com/osrg/ryu.git
$ cd ryu/tools/
$ sudo pip install -r pip-requires
$ cd ..
$ sudo python ./setup.py install

正しく、Ryuがインストールされたことを確認しておきます。

$ ryu-manager --version
rya-manager 3.26

◆ Ryu VRRPサンプルアプリ

Linuxサーバに、おのおのRyu SDN Frameworkをインストールしたら、次のVRRPサンプルアプリを保存します。

(1) RyuVRRP1側のサンプルアプリ

import logging
import datetime
import time
import netaddr

from ryu.base import app_manager
from ryu.lib import hub
from ryu.controller import handler
from ryu.services.protocols.vrrp import api as vrrp_api
from ryu.services.protocols.vrrp import event as vrrp_event


_VRRP_VERSION_V3 = 3
_VRID = 1
_VIRTUAL_IP_ADDRESS = '192.168.0.1'
_PRIMARY_IP_ADDRESS = '192.168.0.2'
_VIRTUAL_MAC_ADDRESS = '00:00:5e:00:01:01'
_PRIORITY = 250
_IFNAME = 'eth1'
_PREEMPT_DELAY = 10

LOG = logging.getLogger('SampleVrrp')
LOG.setLevel(logging.DEBUG)
logging.basicConfig()

class SampleVrrp(app_manager.RyuApp):
    def __init__(self, *args, **kwargs):
        super(SampleVrrp, self).__init__(*args, **kwargs)
        hub.spawn(self._test_senario)

    def _test_senario(self):
        LOG.info("")
        LOG.info("////// 1. Create Vrrp Router  //////")
        vrrp_mgr = self._configure_vrrp_router(_VRRP_VERSION_V3, _PRIORITY,
                      _PRIMARY_IP_ADDRESS, _VIRTUAL_IP_ADDRESS, _IFNAME,
                      _VRID, _PREEMPT_DELAY)
        time.sleep(30)

        LOG.info("")
        LOG.info("////// 2. Change Priority [250] -> [50]  //////")
        self._configure_vrrp_change(_VRID, 50)
        time.sleep(30)

        LOG.info("")
        LOG.info("////// 3. Change Priority [50] -> [250]  //////")
        self._configure_vrrp_change(_VRID, _PRIORITY)
        time.sleep(30)

        LOG.info("")
        LOG.info("////// 4. Shutdown Vrrp Router  //////")
        self._shutdown_vrrp_router(_VRID)

    def _configure_vrrp_change(self, vrid, priority):
        instance_name = self._lookup_instance(vrid)
        if not instance_name:
            raise RPCError('vrid %d is not found' % (vrid))
        vrrp_api.vrrp_config_change(self, instance_name, priority=priority)

    def _shutdown_vrrp_router(self, vrid):
        instance_name = self._lookup_instance(vrid)
        if not instance_name:
            raise RPCError('vrid %d is not found' % (vrid))
        vrrp_api.vrrp_shutdown(self, instance_name)

    def _lookup_instance(self, vrid):
        for instance in vrrp_api.vrrp_list(self).instance_list:
            if vrid == instance.config.vrid:
                return instance.instance_name
        return None

    def _configure_vrrp_router(self, vrrp_version, vrrp_priority,
                               primary_ip_address, virtual_ip_address,
                               ifname, vrid, preempt_delay):
        interface = vrrp_event.VRRPInterfaceNetworkDevice(
            _VIRTUAL_MAC_ADDRESS, primary_ip_address, None, ifname)

        ip_addresses = [virtual_ip_address]
        config = vrrp_event.VRRPConfig(
            version=vrrp_version, vrid=vrid, priority=vrrp_priority,
            ip_addresses=ip_addresses, preempt_delay=preempt_delay)
        config_result = vrrp_api.vrrp_config(self, interface, config)
        return config_result


    @handler.set_ev_cls(vrrp_event.EventVRRPStateChanged)
    def vrrp_state_changed_handler(self, ev):
        old_state = ev.old_state
        new_state = ev.new_state
        now = datetime.datetime.now()
        now_time = now.strftime("%H:%M:%S")
        micro_time = "%03d" % (now.microsecond // 1000)
        LOG.debug("%s.%s: State Changed [%s] -> [%s]"
                 % (now_time, micro_time, old_state, new_state))

(2) RyuVRRP2側のサンプルアプリ

import logging
import datetime

from ryu.base import app_manager
from ryu.lib import hub
from ryu.controller import handler
from ryu.services.protocols.vrrp import api as vrrp_api
from ryu.services.protocols.vrrp import event as vrrp_event


_VRRP_VERSION_V3 = 3
_VRID = 1
_VIRTUAL_IP_ADDRESS = '192.168.0.1'
_PRIMARY_IP_ADDRESS = '192.168.0.3'
_VIRTUAL_MAC_ADDRESS = '00:00:5e:00:01:01'
_PRIORITY = 100
_IFNAME = 'eth1'
_PREEMPT_DELAY = 10

LOG = logging.getLogger('SampleVrrp')
LOG.setLevel(logging.DEBUG)
logging.basicConfig()

class SampleVrrp(app_manager.RyuApp):
    def __init__(self, *args, **kwargs):
        super(SampleVrrp, self).__init__(*args, **kwargs)
        hub.spawn(self._test_senario)

    def _test_senario(self):
        LOG.info("")
        LOG.info("////// 1. Create Vrrp Router  //////")
        vrrp_mgr = self._configure_vrrp_router(_VRRP_VERSION_V3, _PRIORITY,
                      _PRIMARY_IP_ADDRESS, _VIRTUAL_IP_ADDRESS, _IFNAME,
                      _VRID, _PREEMPT_DELAY)


    def _configure_vrrp_router(self, vrrp_version, vrrp_priority,
                               primary_ip_address, virtual_ip_address,
                               ifname, vrid, preempt_delay):
        interface = vrrp_event.VRRPInterfaceNetworkDevice(
            _VIRTUAL_MAC_ADDRESS, primary_ip_address, None, ifname)

        ip_addresses = [virtual_ip_address]
        config = vrrp_event.VRRPConfig(
            version=vrrp_version, vrid=vrid, priority=vrrp_priority,
            ip_addresses=ip_addresses, preempt_delay=preempt_delay)
        rep = vrrp_api.vrrp_config(self, interface, config)
        return rep


    @handler.set_ev_cls(vrrp_event.EventVRRPStateChanged)
    def vrrp_state_changed_handler(self, ev):
        old_state = ev.old_state
        new_state = ev.new_state
        now = datetime.datetime.now()
        now_time = now.strftime("%H:%M:%S")
        micro_time = "%03d" % (now.microsecond // 1000)
        LOG.debug("%s.%s: State Changed [%s] -> [%s]"
                 % (now_time, micro_time, old_state, new_state))

◆ 早速、VRRPサンプルアプリを動かしてみる

まずは、Ryu VRRP2側サンプルアプリを起動したのちに、Ryu VRRP1側サンプルアプリを起動します。
すると、Ryu VRRPサンプルアプリ動作として、以下のテストシナリオが自動で開始されます。
(1) Ryu VRRP1サンプルアプリ動作テストシナリオ
1. VRRP Router起動
2. Priority値を"250"から"50"に変更
3. Priority値を"50"から"250"に変更
4. VRRP Router停止

(2) Ryu VRRP2サンプルアプリ動作テストシナリオ
1. VRRP Router起動

ちなみに、Backup -> Masterへの状態遷移に関わるPreempt modeは有効になっております。[preempt delay: 10秒]
Ryu VRRPサンプルアプリを動作させると、コマンド結果として、いろいろとデバック情報が表示されます。
Ryu VRRP1起動結果

$ sudo ryu-manager sample_vrrp1.py
loading app sample_vrrp1.py
loading app ryu.services.protocols.vrrp.manager
instantiating app sample_vrrp.py of SampleVrrp
instantiating app ryu.services.protocols.vrrp.manager of VRRPManager

////// 1. Create Vrrp Router  //////
instantiating app None of VRRPInterfaceMonitorNetworkDevice
instantiating app None of VRRPRouterV3
14:12:05.593: State Changed [None] -> [Initialize]
14:12:05.594: State Changed [Initialize] -> [Backup]
VRRPV3StateBackup preempt_delay
14:12:15.989: State Changed [Backup] -> [Master]

////// 2. Change Priority [250] -> [50]  //////
VRRPV3StateMaster vrrp_config_change_request
14:12:45.603: State Changed [Master] -> [Backup]

////// 3. Change Priority [50] -> [250]  //////
VRRPV3StateBackup vrrp_config_change_request
VRRPV3StateBackup preempt_delay
14:13:15.645: State Changed [Backup] -> [Master]

////// 4. Shutdown Vrrp Router  //////
14:13:35.600: State Changed [Master] -> [Initialize]

Ryu VRRP2起動結果

$ sudo ryu-manager sample_vrrp2.py
loading app sample_vrrp2.py
loading app ryu.services.protocols.vrrp.manager
instantiating app sample_vrrp.py of SampleVrrp
instantiating app ryu.services.protocols.vrrp.manager of VRRPManager

////// 1. Create Vrrp Router  //////
instantiating app None of VRRPInterfaceMonitorNetworkDevice
instantiating app None of VRRPRouterV3
14:11:58.294: State Changed [None] -> [Initialize]
14:11:58.295: State Changed [Initialize] -> [Backup]
14:12:01.908: State Changed [Backup] -> [Master]
14:12:15.922: State Changed [Master] -> [Backup]
VRRPV3StateBackup preempt_delay
14:12:45.533: State Changed [Backup] -> [Master]
14:13:15.578: State Changed [Master] -> [Backup]
14:13:36.144: State Changed [Backup] -> [Master]

Ryu VRRPサンプルアプリのデバック情報のみだと、本当に、VRRPが正しく動作しているのかを確認するのも、困難だと思います。
そこで、今回の動作テスト時に、Linuxサーバ間でPacketキャプチャしておいて、デバック情報とPacketキャプチャ結果を目視で紐付けてみました。
おかげで、Backup -> Masterへの状態遷移に関わるPreempt delayの挙動と、実際のVRRP Advertisementパケットの送出タイミングの因果関係を理解できるようになりました。以下に、VRRP状態遷移の様子になります。
f:id:ttsubo:20151024160435j:plain

実際、VRRPパケットダンプも貼っておきます。
今回、はじめて気がついたのですが、VRRP Advertisementパケットには、仮想MACアドレス情報は設定されないんですよね。
Ryu VRRPモジュールでの仮想MACアドレスの扱いについて、もう少し、リバースエンジニアリングが必要かもしれません。
f:id:ttsubo:20151024160938p:plain

◆ 終わりに

とりあえず、Ryu SDN FrameworkによるVRRPプロトコル制御の有用性を確認することができました。
ただし、このままでは、VRRPプロトコル知識修得くらいしか活用シーンが思い浮かびません。
今後は、もう一歩、踏み込んで、C/Dプレーン連携を見据えた技術検討を行っていく必要がありそうです。