前回までは、OpenDaylight(以下ODL)の構築と基本動作確認を行ってきました。OpenStackとの連携もできているので、使ってみようかなと思う方もおられるかと思います。実際に、システム構成を考えるときにSPOF(単一障害点)を作らないように設計するのは基本かと思いますが、OpenStackはHA構成が組めるのに、ODLがSPOFになってしまっては元も子もありません。そこで今回は、ODLのクラスタを構築してみます。
OpenDaylightのデータストアとクラスタ
ODLのクラスタはMD-SAL Clustering機能として実装されています。ODLの分散データストアを理解するためにはデータストアがどのような構造になっているのかを理解している必要があります。
データストア
OpenDaylightはMD-SALという抽象化レイヤー内に独自のデータストアを持っています。ODLを利用する各アプリケーションはこのデータストアにアクセスすることで必要な情報を保存したり、取り出したりできます。データストアの実体はLevelDB(Key-Value Storage)かSnapshotとなりますが、論理的には以下の図のようなデータツリー構造をしています。

シャード
クラスタを構成した際、データストアに保存されたデータはクラスタ内の多数のノードへシャード単位でレプリケーションされます。シャードとはデータストアのあるひとまとめにしたサブツリーを指します。シャード毎にどのノードへレプリケーションするかを設定できます。

Raft
ODLのクラスタではRaftというコンセンサスアルゴリズムを採用しています。Raftでは各ノードは以下の3つの状態のいずれかを持っています。
- Leader
- Leaderはクラスタ内で1ノードのみ選出され、ODLアプリケーションやFollowerからの全ての要求に応答し、Followerへの要求も行います。
- Follower
- FollowerはLeaderかCandidateのノードからの要求に応答します。自ら要求を行うことはありません。
- Candidate
- Candidateはノードの起動時やLeaderがダウンした時に、新しいLeaderを選出する状態を示しています。
Raftではクラスタ内の過半数以上のノードが稼働していれば全体のフォールトトレラント性が保たれる仕組みになっています。もし、何らかの障害により過半数以上のノードがダウンした場合、Leaderを選出できずクラスタはSuspend状態となります。Raftでは全てのノードがリクエストを受けることができます。Followerが要求を受けるとその要求はLeaderに転送されます。したがって、OpenStackからのリクエストがLeaderではないノードに届いても問題はありません。実際に、今回解説する構成ではHAProxyを使ってロードバランシングしていますので、Followerにもリクエストが飛ぶことになります。
環境・バージョンについて
クラスタの物理構成

今回は3ノードのODLクラスタを構築してみます。これまでと同様にOpenStackと連携させた環境を構築します。OpenStack用の1ノードとODLクラスタ用の3ノードの全てでUbuntuを使用します。ODLへのリクエストをODLクラスタへ分散するためにOpenStackのノードにHAProxyも使用します。それぞれのパッケージで使用するバージョンは以下の表の通りです。
パッケージ |
バージョン |
Ubuntu |
14.04 LTS |
OpenDaylight |
Beryllium SR2 |
OpenStack |
Mitaka |
HAProxy |
1.4.24 |
シャードの構成
今回の構成では以下の図のように、Inventoryサブツリー配下のシャードとnetwork-topologyサブツリー配下のシャード、それら以外のサブツリーであるdefault、toasterのシャードを全てのノードにレプリケーションする設定を行います。

構築してみよう
全ノード共通の設定
それでは早速構築を始めていきます。先に、全ノードで共通の設定を実施しておきます。
構築を簡単にするために、ファイアーウォールは無効化しておきます。
続いて、ODLの構築に入ります。
ODLの構築
JDKのインストール
ODLはJava言語によって実装されています。そのためJavaの実行環境が必要になりますのでJDK(Java Development Kit)をインストールします。今回インストールするBeryllium SR2ではJDK 8が必要です。
1 | ~$ sudo add-apt-repository ppa:openjdk-r/ppa |
3 | ~$ sudo apt-get install openjdk-8-jre |
OpenDaylightのダウンロード
ODLのパッケージをNexusリポジトリからダウンロードして展開します。
2 | ~$ tar zxvf distribution-karaf-0.4.2-Beryllium-SR2.tar.gz |
3 | ~$ cd distribution-karaf-0.4.2-Beryllium-SR2/ |
OpenDaylightの起動
ODLを以下のstartスクリプトを実行して起動します。
1 | ~/distribution-karaf-0.4.2-Beryllium-SR2$ ./bin/start # OpenDaylightを起動する |
必要な機能のインストール
ODLのコンソールにログインして、必要な機能をインストールしていきます。インストールするのは、OpenStack連携に必要な「odl-ovsdb-openstack」とMD-SALのクラスタリングに必要な「odl-mdsal-clustering」になります。
1 | ~/distribution-karaf-0.4.2-Beryllium-SR2$ ./bin/client -u karaf # OpenDaylight Karaf Shellにログイン |
3 | opendaylight-user@root>feature:install odl-ovsdb-openstack |
4 | opendaylight-user@root>feature:install odl-mdsal-clustering |
5 | opendaylight-user@root>logout |
ここで、一度ODLを停止します。
1 | ~/distribution-karaf-0.4.2-Beryllium-SR2$ ./bin/stop # OpenDaylightを停止する |
ここまでの手順を3台のODLノード全てで実施してください。
クラスタリングの設定
続いて、クラスタリングに必要な設定をしていきます。ODLクラスタリングではakka.confとmodule-shards.confの2つのファイルを設定する必要があります。今回使用する設定ファイルは全てGitHubからダウンロードできます。
まずはODL Controller 1のノードで設定を行います。以下のディレクトリに移動し、akka.confをダウンロードします。
1 | ~/distribution-karaf-0.4.2-Beryllium-SR2$ cd configuration/initial |
ODL Controller 2と3のakka.confは以下のURLからダウンロードして使用してください。
- ODL Controller 2
- ODL Controller 3
ダウンロードしたファイルを開き、hostnameやseed-nodesのIPアドレスをお使いの環境に合わせて編集してください。rolesにはクラスタ内でのメンバー識別子が指定されています。それぞれのメンバーはクラスタ内での識別子を持っている必要があり、そのクラスタ内で一意でなければなりません。ここでは以下のように値を設定しています。
ノード |
メンバー識別子 |
ODL Controller 1 |
member-1 |
ODL Controller 2 |
member-2 |
ODL Controller 3 |
member-3 |
05 | hostname = "192.168.0.11" # 自ノードのIPアドレスへ変更する |
16 | "member-1" # クラスタ内でのメンバー識別子を指定する |
26 | log-remote-lifecycle-events = off |
28 | hostname = "192.168.0.11" # 自ノードのIPアドレスへ変更する |
30 | maximum-frame-size = 419430400 |
31 | send-buffer-size = 52428800 |
32 | receive-buffer-size = 52428800 |
40 | auto-down-unreachable-after = 300s |
次に、module-shards.confをダウンロードします。
module-shards.confではクラスタのメンバー(ODLのノード)とレプリカの設定をします。レプリカのリストの順番に優先順位が設定されます。
ODL Controller 2とODL Controller 3も同様に設定をします。最後に、全てのODLを起動してください。
1 | ~/distribution-karaf-0.4.2-Beryllium-SR2/configuration/initial$ cd ~/distribution-karaf-0.4.2-Beryllium-SR2 |
2 | ~/distribution-karaf-0.4.2-Beryllium-SR2$ ./bin/start # OpenDaylightを起動する |
以上で、ODLの設定は完了です。
HAProxyの設定
今回はODLがクラスタ構成となっていますので、Neutron ML2ドライバからのリクエストをクラスタの各メンバーへ分散するためにHAProxyを使用してみます。OpenStackのノードでインストールと設定を行っていきます。
1 | ~$ sudo apt-get install haproxy |
以下の設定ファイルを開いてください。
1 | ~$ sudo vim /etc/default/haproxy |
ENABLEDが0になっていれば1に変更してください。
続いて、設定ファイルをダウンロードします。
ダウンロードしたhaproxy.cfgを開きbackend_serversに指定されているクラスタのメンバーのIPアドレスへ変更してください。HAProxyの設定は非常に単純で、8181番ポートから入ってきたhttpパケットをバックエンドのODLへラウンドロビンで分散するように設定しています。
03 | log /dev/log local1 notice |
04 | chroot /var/lib/haproxy |
17 | errorfile 400 /etc/haproxy/errors/400.http |
18 | errorfile 403 /etc/haproxy/errors/403.http |
19 | errorfile 408 /etc/haproxy/errors/408.http |
20 | errorfile 500 /etc/haproxy/errors/500.http |
21 | errorfile 502 /etc/haproxy/errors/502.http |
22 | errorfile 503 /etc/haproxy/errors/503.http |
23 | errorfile 504 /etc/haproxy/errors/504.http |
27 | default_backend backend_servers |
30 | backend backend_servers |
32 | server odl1 192.168.0.11:8181 check # member-1のIPアドレスへ変更 |
33 | server odl2 192.168.0.12:8181 check # member-2のIPアドレスへ変更 |
34 | server odl3 192.168.0.13:8181 check # member-3のIPアドレスへ変更 |
最後にHAProxyを再起動します。
1 | ~$ sudo service haproxy restart |
以上で、HAProxyの設定は完了です。
コントローラノードの構築
続いて、OpenStackの構築を行います。DevStackをダウンロードしてきます。
1 | ~$ sudo apt-get install git |
以下のURLからlocal.confのサンプルをダウンロードしてください。
ダウンロードしたlocal.confを開くと以下の様になっています。まず、お使いの環境に合わせてHOST_IPをコントローラノードのIPアドレスに変更してください。
01 | ~/devstack$ vim local.conf |
09 | BRANCH_NAME=stable/beryllium |
15 | ODL_OVS_MANAGERS=192.168.0.11,192.168.0.12,192.168.0.13 |
17 | [[post-config|/etc/neutron/plugins/ml2/ml2_conf.ini]] |
ODL_OVS_MANAGERSには、OpenStackの各OVSが接続する先のODLのIPアドレスを指定します。今回は3台のODLがいるので、3台分のIPアドレスを指定します。本来、ml2_conf.iniのml2_odlセクションにはODLコントローラのIPアドレスをurlとして指定しますが、今回のODLはクラスタ構成なので、HAProxyが動作しているローカルホストを指定しています。
後は、stack.shスクリプトを実行するだけです。以下の様なメッセージが出力されれば終了です。
3 | This is your host IP address: 192.168.0.10 |
4 | This is your host IPv6 address: ::1 |
7 | The default users are: admin and demo |
9 | 2016-07-22 06:47:58.133 | stack.sh completed in 1130 seconds. |
以上で全ての構築は完了です。
動作確認
レプリケーションされているか
機能的には第2回、第3回で構築した環境と変わりません。ここでは、ODLのデータストアが全てのメンバーにレプリケーションできているか確認してみましょう。
ODLにデータを登録するためにOpenStackからネットワークとインスタンスを作成します。コントローラノードで以下のコマンドを実行します。
01 | ~/devstack$ source openrc demo demo # 認証用の環境変数を設定(demoユーザー、demoテナント) |
02 | ~/devstack$ neutron net-create net01 # ネットワークを作成 |
04 | +-------------------------+--------------------------------------+ |
06 | +-------------------------+--------------------------------------+ |
07 | | admin_state_up | True | |
08 | | availability_zone_hints | | |
09 | | availability_zones | | |
10 | | created_at | 2016-07-26T07:43:08 | |
12 | | id | b94883ed-34ad-4a09-ac58-e459b6b77582 | |
13 | | ipv4_address_scope | | |
14 | | ipv6_address_scope | | |
17 | | port_security_enabled | True | |
18 | | router:external | False | |
23 | | tenant_id | aaa0ecf59218482d9e55b75a50f22a5c | |
24 | | updated_at | 2016-07-26T07:43:08 | |
25 | +-------------------------+--------------------------------------+ |
net01というネットワークが作成され、ODLのデータストアに情報が書き込まれました。さっそくデータストアの情報を確認してみましょう。第4回で紹介したRESTCONFを使ってアクセスします。まずはmember-1にアクセスしてneutronの情報を取得します。以下のようなデータが取得できます。
07 | "admin-state-up": true, |
09 | "neutron-L3-ext:external": false, |
10 | "neutron-provider-ext:network-type": "neutron-networks:network-type-vxlan", |
11 | "neutron-provider-ext:segmentation-id": "1048", |
14 | "tenant-id": "aaa0ecf5-9218-482d-9e55-b75a50f22a5c", |
15 | "uuid": "b94883ed-34ad-4a09-ac58-e459b6b77582" |
データ量が多いので一部省略していますが、先ほど作成したネットワークの情報が取得できます。member-2とmember-3に対しても同様にRESTCONFでアクセスしてみてください。同じデータが取得できていればレプリケーションできており、クラスタが正常に動作しています。
障害時の動作
それではクラスタの状態を確認してみます。Javaアプリケーションの統計情報を取得したりモニタリングや管理などを行う時に欠かせないJMXですが、ODLではJMXの情報をREST APIで提供するためのJolokiaを利用できます。まず、Jolokiaをインストールします。
01 | ~/distribution-karaf-0.4.2-Beryllium-SR2$ ./bin/client -u karaf |
02 | client: JAVA_HOME not set; results may vary |
04 | 387 [sshd-SshClient[ed17bee]-nio2-thread-2] WARN org.apache.sshd.client.keyverifier.AcceptAllServerKeyVerifier - Server at [/0.0.0.0:8101, DSA, 75:e8:b1:02:c3:ac:e8:6d:f8:8e:fb:99:a8:b2:1a:8e] presented unverified {} key: {} |
06 | ________ ________ .__ .__ .__ __ |
07 | \_____ \ ______ ____ ____ \______ \ _____ ___.__.| | |__| ____ | |___/ | |
08 | / | \\____ \_/ __ \ / \ | | \\__ \< | || | | |/ ___\| | \ __\ |
09 | / | \ |_> > ___/| | \| ` \/ __ \\___ || |_| / /_/ > Y \ | |
10 | \_______ / __/ \___ >___| /_______ (____ / ____||____/__\___ /|___| /__| |
11 | \/|__| \/ \/ \/ \/\/ /_____/ \/ |
14 | Hit '<tab>' for a list of available commands |
15 | and '[cmd] --help' for help on a specific command. |
16 | Hit '<ctrl-d>' or type 'system:shutdown' or 'logout' to shutdown OpenDaylight. |
18 | opendaylight-user@root>bundle:install -s mvn:org.jolokia/jolokia-osgi/1.3.1 |
20 | opendaylight-user@root>logout |
Jolokiaのインストールを全てのmemberに対して行います。
まずmember-1の状態を取得してみます。
04 | "mbean": "org.opendaylight.controller:Category=Shards,name=member-1-shard-inventory-config,type=DistributedConfigDatastore", |
08 | "timestamp": 1465515563, |
10 | "AbortTransactionsCount": 0, |
12 | "CommittedTransactionsCount": 0, |
14 | "FailedReadTransactionsCount": 0, |
15 | "FailedTransactionsCount": 0, |
19 | "id": "member-2-shard-inventory-config", |
22 | "timeSinceLastActivity": "00:00:00.012" |
26 | "id": "member-3-shard-inventory-config", |
29 | "timeSinceLastActivity": "00:00:00.015" |
32 | "FollowerInitialSyncStatus": true, |
33 | "InMemoryJournalDataSize": 1338, |
34 | "InMemoryJournalLogSize": 1, |
36 | "LastCommittedTransactionTime": "1970-01-01 09:00:00.000", |
38 | "LastLeadershipChangeTime": "2016-06-10 08:37:53.140", |
42 | "Leader": "member-1-shard-inventory-config", |
43 | "LeadershipChangeCount": 3, |
45 | "PendingTxCommitQueueSize": 0, |
46 | "RaftState": "Leader", |
47 | "ReadOnlyTransactionCount": 0, |
48 | "ReadWriteTransactionCount": 0, |
49 | "ReplicatedToAllIndex": 12, |
50 | "ShardName": "member-1-shard-inventory-config", |
51 | "SnapshotCaptureInitiated": false, |
54 | "StatRetrievalError": null, |
55 | "StatRetrievalTime": "1.676 ms", |
56 | "TxCohortCacheSize": 0, |
57 | "VotedFor": "member-1-shard-inventory-config", |
58 | "WriteOnlyTransactionCount": 0 |
取得した結果、"RaftState": "Leader" となっていることが確認できます。続いて、member-2とmember-3でも確認してみてください。"RaftState": "Follower"となっていることが確認できます。
member-1を落としてみる
クラスタが正常に動作していることを確認するために、Leaderとなっているmember-1を停止してmember-2にLeaderが切り替わることを確認します。
1 | ~/distribution-karaf-0.4.2-Beryllium-SR2$ ./bin/stop |
これでmember-1からのレスポンスは返ってこなくなります。この時のmember-2の状態を確認してみます。
04 | "mbean": "org.opendaylight.controller:Category=Shards,name=member-2-shard-inventory-config,type=DistributedConfigDatastore", |
08 | "timestamp": 1465449951, |
10 | "AbortTransactionsCount": 0, |
12 | "CommittedTransactionsCount": 0, |
14 | "FailedReadTransactionsCount": 0, |
15 | "FailedTransactionsCount": 0, |
19 | "id": "member-1-shard-inventory-config", |
22 | "timeSinceLastActivity": "00:00:00.000" |
26 | "id": "member-3-shard-inventory-config", |
29 | "timeSinceLastActivity": "00:00:00.000" |
32 | "FollowerInitialSyncStatus": true, |
33 | "InMemoryJournalDataSize": 1338, |
34 | "InMemoryJournalLogSize": 1, |
36 | "LastCommittedTransactionTime": "1970-01-01 09:00:00.000", |
38 | "LastLeadershipChangeTime": "2016-06-09 14:25:51.866", |
42 | "Leader": "member-2-shard-inventory-config", |
43 | "LeadershipChangeCount": 4, |
45 | "PendingTxCommitQueueSize": 0, |
46 | "RaftState": "Leader", |
47 | "ReadOnlyTransactionCount": 1, |
48 | "ReadWriteTransactionCount": 0, |
49 | "ReplicatedToAllIndex": -1, |
50 | "ShardName": "member-2-shard-inventory-config", |
51 | "SnapshotCaptureInitiated": false, |
54 | "StatRetrievalError": null, |
55 | "StatRetrievalTime": "19.88 ms", |
56 | "TxCohortCacheSize": 0, |
57 | "VotedFor": "member-2-shard-inventory-config", |
58 | "WriteOnlyTransactionCount": 0 |
member-1が停止したことによってLeaderがmember-2に切り替わりました。この状態でもOpenStackのネットワークが作れるか確認します。
01 | ~/devstack$ neutron net-create net02 |
03 | +-------------------------+--------------------------------------+ |
05 | +-------------------------+--------------------------------------+ |
06 | | admin_state_up | True | |
07 | | availability_zone_hints | | |
08 | | availability_zones | | |
09 | | created_at | 2016-07-26T08:01:35 | |
11 | | id | f9867311-98c3-4618-be98-6ce75ff8aa23 | |
12 | | ipv4_address_scope | | |
13 | | ipv6_address_scope | | |
16 | | port_security_enabled | True | |
17 | | router:external | False | |
22 | | tenant_id | aaa0ecf59218482d9e55b75a50f22a5c | |
23 | | updated_at | 2016-07-26T08:01:35 | |
24 | +-------------------------+--------------------------------------+ |
作成できました。member-1を起動してnet02がレプリケーションされることを確認しましょう。
1 | ~/distribution-karaf-0.4.2-Beryllium-SR2$ ./bin/start |
07 | "admin-state-up": true, |
09 | "neutron-L3-ext:external": false, |
10 | "neutron-provider-ext:network-type": "neutron-networks:network-type-vxlan", |
11 | "neutron-provider-ext:segmentation-id": "1048", |
14 | "tenant-id": "aaa0ecf5-9218-482d-9e55-b75a50f22a5c", |
15 | "uuid": "b94883ed-34ad-4a09-ac58-e459b6b77582" |
18 | "admin-state-up": true, |
20 | "neutron-L3-ext:external": false, |
21 | "neutron-provider-ext:network-type": "neutron-networks:network-type-vxlan", |
22 | "neutron-provider-ext:segmentation-id": "1069", |
25 | "tenant-id": "aaa0ecf5-9218-482d-9e55-b75a50f22a5c", |
26 | "uuid": "f9867311-98c3-4618-be98-6ce75ff8aa23" |
再起動したmember-1にも正しくレプリケーションされていることが確認できました。以上で、動作確認は終了です。
今回は、第2回と第3回で解説したOpenStack連携や第4回で解説したRESTCONFなどを取り入れてOpenDaylightのクラスタリングを解説しました。これらのように、OpenDaylightはクライアントアプリケーションにとって汎用性が高く、HAも考慮されている設計になってきています。