MidoNetとDockerをOpenStackなしで組み合わせてみるデモ

# 概要

以前OpenVNetで行った、マルチホスト上のDockerコンテナを仮想ネットワークで繋ぐデモを、MidoNetで行う。

# その前に、MidoNetとは？

ミドクラさんが開発しているネットワーク仮想化ソフトウェア。
[http://www.midokura.com/midonet/](http://www.midokura.com/midonet/)
2014年にオープンソースになり、OpenStack Neutronととても親和性が高く(というより、Neutronバックエンド標準となるべく開発が進められている)、プラグインが提供されている。
サポート、GUI、便利機能などが入った商用のMidokura Enterprise MidoNetが元。

ありがたいことに、[こちら](http://qiita.com/tfukushima/items/c7fb1739f963930da0aa)にとても詳しい記事がある。

# 前提

MidoStackを使う方法は割と見かけるが、**OpenStack（というかneutron）が出てくると、場合によっては一気にややこしく感じる**ことがあるようなので、今回は純粋にMidoNetそのものとdockerだけを使う形で構成する。
keystoneがないので、midonet-apiの認証についてはMockAuthServiceを利用する。

なお、midonet apiはそのうち無くなる可能性があり、neutron api経由だけになる（つまり、MidoNetだけを使うとしてもkeystoneとneutronが必要になる）かもしれないので、neutron慣れしていない場合は、apiが移行する前に慣れておくと良い事がありそう。
また、現在MidoNetはlibnetworkに手を加えて（libnetworkのdriverとして？）dockerに対応する方向で動いているらしいので、こんなことをしなくてもよくなるはず。

# 環境

CentOS 7 minimalのホストを2台（midonet1、midonet2）
冗長化が目的ではないので、midonet1にNSDB、midonet-api、midonet-cli、midolmanを、midonet2にはmidolmanだけをインストールして使う。
実際に利用する際はNSDBのクラスタリングなどをしっかり考慮すること。

## midonet1

* CentOS 7
* メモリ4G
* eth0：192.168.122.111
* zookeeper
* cassandra
* midolman
* midonet-api
* python-midonet-client

## midonet2

* CentOS 7
* メモリ4G
* eth0：192.168.122.112
* midolman

# 参考ドキュメント

MidoNetはドキュメントがとても充実していて、サンプル実行ログ付きで解説されているので、何か試すときに何をすればいいか分からない、ということがあまりない。

* インストール関連
  * [quick-start-guide](http://docs.midonet.org/docs/latest/quick-start-guide/rhel-7_juno-rdo/content/index.html)
* CLIでの設定関連
  * [operation-guide](http://docs.midonet.org/docs/latest/operation-guide/content/index.html)

有識者の方の書かれたslideやblogなども色々ある。

# 事前準備

## SELinux、iptablesの無効化

SELinux、iptablesを無効にしておくとトラブルが少なくて済む。

## yumリポジトリの設定

cassandraのインストールを行うためのリポジトリを設定する。

```bash:datastax.repo（midonet1）
$ cat > /etc/yum.repos.d/datastax.repo <<EOF
[datastax]
name= DataStax Repo for Apache Cassandra
baseurl=http://rpm.datastax.com/community
enabled=1
gpgcheck=0
EOF
```

MidoNetのインストールを行うためのリポジトリを設定する。
OpenStackなしで使ってみるため、openstack-integrationは書かない。

```bash:midonet.repo（midonet1、midonet2）
$ cat > /etc/yum.repos.d/midonet.repo <<EOF
[midonet]
name=MidoNet
baseurl=http://repo.midonet.org/midonet/v2015.01/RHEL/7/stable/
enabled=1
gpgcheck=1
gpgkey=http://repo.midonet.org/RPM-GPG-KEY-midokura

[midonet-misc]
name=MidoNet 3rd Party Tools and Libraries
baseurl=http://repo.midonet.org/misc/RHEL/7/misc/
enabled=1
gpgcheck=1
gpgkey=http://repo.midonet.org/RPM-GPG-KEY-midokura
EOF
```

# 各種インストール

## openjdk-1.7.0のインストール

midolmanはjava7で動かすので、インストールしておく。

```bash:java-1.7.0-openjdkインストール（midonet1、midonet2）
$ sudo yum -y install java-1.7.0-openjdk
```

## zookeeper

nsdb用にzookeeperをインストールする。

```bash:zookeeperインストールと動作確認（midonet1）
$ sudo yum -y install zookeeper
$ mkdir -p /usr/java/default/bin/
$ ln -s /usr/lib/jvm/jre-1.7.0-openjdk/bin/java /usr/java/default/bin/java
$ echo "server.1=192.168.122.111:2888:3888" >> /etc/zookeeper/zoo.cfg
$ echo 1 > /var/lib/zookeeper/data/myid
$ sudo systemctl start zookeeper.service
$ ss -napt | grep 2181
$ sudo yum -y install nc
$ echo ruok | nc 127.0.0.1 2181
```

## cassandra

nsdb用にcassandraをインストールする。

```bash:cassandraインストールと動作確認（midonet1）
$ sudo yum -y install dsc20
$ sed -ri "s/Test Cluster/midonet/g" /etc/cassandra/conf/cassandra.yaml
$ sed -ri "s/localhost/192.168.122.111/g" /etc/cassandra/conf/cassandra.yaml
$ sed -ri "s/127\.0\.0\.1/192.168.122.111/g" /etc/cassandra/conf/cassandra.yaml
$ rm -rf /var/lib/cassandra/data/system/
$ sudo systemctl restart cassandra.service
$ ss -napt | grep 9160
$ cassandra-cli -h 192.168.122.111 -p 9160
```

## midonet-apiのインストールと設定

```bash:midonet-apiのインストール（midonet1）
$ sudo yum -y install midonet-api
```

インストール後、web.xmlを編集する。

```xml:/usr/share/midonet-api/WEB-INF/web.xmlの編集（midonet1）
…
<context-param>
    <param-name>rest_api-base_uri</param-name>
    <param-value>http://192.168.122.111:8080/midonet-api</param-value>
</context-param>
…
<context-param>
    <param-name>zookeeper-zookeeper_hosts</param-name>
    <param-value>192.168.122.111:2181</param-value>
</context-param>
…
<context-param>
    <param-name>midobrain-properties_file</param-name>
    <param-value>/var/lib/tomcat/webapps/host_uuid.properties</param-value>
</context-param>
…
```

[ここ](https://github.com/midonet/midonet/blob/master/docs/rest-api-auth.md)を参考に、今回は認証をMockAuthServiceに設定する。

```xml:/usr/share/midonet-api/WEB-INF/web.xmlの編集（midonet1）
  <context-param>
    <param-name>auth-auth_provider</param-name>
    <param-value>
      org.midonet.api.auth.MockAuthService
    </param-value>
  </context-param>
```

tomcatをインストールする。

```bash:tomcatのインストール（midonet1）
$ yum -y install tomcat
```

インストール後、コンテキスト設定ファイルを編集する。

```xml:/etc/tomcat/Catalina/localhost/midonet-api.xmlの編集（midonet1）
<Context
    path="/midonet-api"
    docBase="/usr/share/midonet-api"
    antiResourceLocking="false"
    privileged="true"
/>
```

編集完了後、midonet-apiを起動する。

```bash:midonet-apiの起動（midonet1）
$ systemctl enable tomcat.service
$ systemctl start tomcat.service
```

## midonet-cliのインストールと設定

epelをいれないとpython-httplib2とpython-eventletがみつからないため、epelを入れた上でpython-midonetclientをインストールする。

```bash:epel-release、python-midonetclientのインストール（midonet1）
$ sudo yum -y install epel-release
$ sudo yum -y install python-midonetclient
```

CLIが読み込む設定ファイルを編集する。

```bash:~/.midonetrcの編集（midonet1）
[cli]
api_url = http://192.168.122.111:8080/midonet-api
username = admin
password = ADMIN_PASS
project_id = admin
```

# midolmanのインストールと設定

各ホストにmidolmanをインストールする。

```bash
$ sudo yum install -y midolman
```

```ini:/etc/midolman/midolman.conf（midonet1、midonet2）
…
[zookeeper]
zookeeper_hosts = 192.168.122.111:2181
…
[cassandra]
servers = 192.168.122.111
replication_factor = 1
cluster = midonet
…
```
```bash:midolman起動
$ systemctl start midolman.service
```

この時点でmidonet datapathができている。

```bash:datapathの確認
$ ip a
…
3: midonet: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN 
    link/ether 5e:e6:d8:3f:59:52 brd ff:ff:ff:ff:ff:ff
```

# Dockerコンテナの起動

## dockerインストール

dockerをインストールする。

```bash:dockerインストール
$ yum -y install docker
```

## コンテナ起動


コンテナを起動し、netns内外に伸びるvethを生やした状態にする。


```bash:コンテナ作成shell（init.sh：midonet1）
#!/bin/sh

DOCKER_PID=`ps -ef | grep docker | grep -v grep |  awk '{print $2}'`
DOCKER_IMAGE="centos_sshd"
IP_ADDRESSES=("10.0.0.10/24")

for i in `seq 1 ${#IP_ADDRESSES[@]}`; do
  let address_index=$i-1
  CONTAINER_ID=`docker run --hostname="container1${i}" --net="none" -i -t -d ${DOCKER_IMAGE} /bin/bash`
  BASH_PID=`docker inspect --format {{.State.Pid}} ${CONTAINER_ID}`

  ln -s /proc/${BASH_PID}/ns/net /var/run/netns/${BASH_PID}

  ip link add veth${i}-1 type veth peer name veth${i}-2
  ip link set veth${i}-1 up
  ip link set veth${i}-2 netns ${BASH_PID}
  ip netns exec ${BASH_PID} ip link set veth${i}-2 up
  ip netns exec ${BASH_PID} ip addr add ${IP_ADDRESSES[${address_index}]} dev veth${i}-2
done
```

```bash:コンテナ作成shell（init.sh：midonet2）
#!/bin/sh

DOCKER_PID=`ps -ef | grep docker | grep -v grep |  awk '{print $2}'`
DOCKER_IMAGE="centos_sshd"
IP_ADDRESSES=("10.0.0.11/24")

for i in `seq 1 ${#IP_ADDRESSES[@]}`; do
  let address_index=$i-1
  CONTAINER_ID=`docker run --hostname="container2${i}" --net="none" -i -t -d ${DOCKER_IMAGE} /bin/bash`
  BASH_PID=`docker inspect --format {{.State.Pid}} ${CONTAINER_ID}`

  ln -s /proc/${BASH_PID}/ns/net /var/run/netns/${BASH_PID}

  ip link add veth${i}-1 type veth peer name veth${i}-2
  ip link set veth${i}-1 up
  ip link set veth${i}-2 netns ${BASH_PID}
  ip netns exec ${BASH_PID} ip link set veth${i}-2 up
  ip netns exec ${BASH_PID} ip addr add ${IP_ADDRESSES[${address_index}]} dev veth${i}-2
done
```

上記shellを実行し、コンテナを作成。

```bash:コンテナ作成とコンテナ内外のvethの確認（midonet1）
# ./init.sh
$ ip a
…
3: midonet: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN 
    link/ether 02:1a:d8:e1:e3:75 brd ff:ff:ff:ff:ff:ff
…
8: veth1-1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 65000 qdisc pfifo_fast master midonet state UP qlen 1000
    link/ether 8e:22:8d:91:0e:af brd ff:ff:ff:ff:ff:ff
    inet6 fe80::8c22:8dff:fe91:eaf/64 scope link 
       valid_lft forever preferred_lft forever
$ docker ps
CONTAINER ID        IMAGE                COMMAND             CREATED             STATUS              PORTS               NAMES
5ecaa2bc140c        centos_sshd:latest   "/bin/bash"         5 hours ago         Up 5 hours                              compassionate_darwin   
$ docker exec -it 5ec ip a
…
7: veth1-2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 72:e7:96:78:cf:09 brd ff:ff:ff:ff:ff:ff
    inet 10.0.0.10/24 scope global veth1-2
       valid_lft forever preferred_lft forever
    inet6 fe80::70e7:96ff:fe78:cf09/64 scope link 
       valid_lft forever preferred_lft forever
```

```bash:コンテナ作成とコンテナ内外のvethの確認（midonet2）
# ./init.sh
$ ip a
…
3: midonet: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN 
    link/ether c2:65:9e:ca:30:9e brd ff:ff:ff:ff:ff:ff
…
14: veth1-1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 65000 qdisc pfifo_fast master midonet state UP qlen 1000
    link/ether a6:0d:94:11:98:de brd ff:ff:ff:ff:ff:ff
    inet6 fe80::a40d:94ff:fe11:98de/64 scope link 
       valid_lft forever preferred_lft forever
$ docker ps
CONTAINER ID        IMAGE                COMMAND             CREATED             STATUS              PORTS               NAMES
efb4579d636a        centos_sshd:latest   "/bin/bash"         5 hours ago         Up 5 hours                              ecstatic_hopper     
$ docker exec -it efb ip a
…
13: veth1-2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 02:29:49:b5:34:30 brd ff:ff:ff:ff:ff:ff
    inet 10.0.0.11/24 scope global veth1-2
       valid_lft forever preferred_lft forever
    inet6 fe80::29:49ff:feb5:3430/64 scope link 
       valid_lft forever preferred_lft forever
```

# 仮想ネットワーク設定

コンテナが繋ぐ仮想ネットワークを作成する。

## tunnel zoneの作成

まず最初にtunnel zoneを作る。tunnel zoneとは、MidoNetは離れたcompute node上のインタフェースとはflow-based tunnelingによって通信するが、トンネルの終端がある人であれば誰でも通信できてしまうため、勝手に知らない人と接続しないようにする機構。

```bash:tunnel-zone作成
$ midonet-cli 
midonet> create tunnel-zone name testtz type gre
tzone0
midonet> list tunnel-zone
tzone tzone0 name testtz type gre
```

## tunnel zoneにホスト(midolman)を登録

tunnel zoneに参加できるmidolmanを登録する。

```bash:tunnel-zoneにホストを登録
midonet> list host
host host0 name midonet1 alive true
host host1 name midonet2 alive true
midonet> tunnel-zone tzone0 add member host host0 address 192.168.122.111
zone tzone0 host host0 address 192.168.122.111
midonet> tunnel-zone tzone0 add member host host1 address 192.168.122.112
zone tzone0 host host1 address 192.168.122.112
```

## 仮想bridgeの作成

仮想的なbridgeを作成する。

```bash:仮想bridgeの作成
midonet> bridge create name testbridge
bridge0
midonet> list bridge
bridge bridge0 name testbridge state up
```

## 仮想bridgeにポートを作成

作った仮想bridgeにポートを作成する。

```bash:仮想bridgeにポートを作成
midonet> bridge bridge0 add port
bridge0:port0
midonet> bridge bridge0 add port
bridge0:port1
midonet> bridge bridge0 list port
port port1 device bridge0 state up
port port0 device bridge0 state up
```

## 作成したportに実際のインタフェースをbind

仮想的なportがどのVMのportに該当するのかを定義する。
２つマッピングすれば、その時点で２つのVMが仮想スイッチにつながっていることになり、通信可能になる。

```bash:作成したportに実際のインタフェースをbind
midonet> host host0 list interface
…
iface midonet host_id host0 status 0 addresses [] mac 02:1a:d8:e1:e3:75 mtu 1500 type Virtual endpoint DATAPATH
…
iface veth1-1 host_id host0 status 3 addresses [u'fe80:0:0:0:8c22:8dff:fe91:eaf'] mac 8e:22:8d:91:0e:af mtu 65000 type Virtual endpoint DATAPATH
…
midonet> host host1 list interface
…
iface midonet host_id host1 status 0 addresses [] mac c2:65:9e:ca:30:9e mtu 1500 type Virtual endpoint DATAPATH
…
iface veth1-1 host_id host1 status 3 addresses [u'fe80:0:0:0:a40d:94ff:fe11:98de'] mac a6:0d:94:11:98:de mtu 65000 type Virtual endpoint DATAPATH
…
midonet> 
midonet> host host0 add binding port bridge0:port0 interface veth1-1
host host0 interface veth1-1 port bridge0:port0
midonet> host host1 add binding port bridge0:port1 interface veth1-1
host host1 interface veth1-1 port bridge0:port1
midonet> host host0 list binding
host host0 interface veth1-1 port bridge0:port0
midonet> host host1 list binding
host host1 interface veth1-1 port bridge0:port1
```

## veth1-1がmidonet datapathに追加されているのを確認

```bash:veth1-1がmidonet　datapathに追加されているのを確認
mm-dpctl --show-dp midonet
Datapath name   : midonet
Datapath index : 3
Datapath Stats: 
  Flows :0
  Hits  :225
  Lost  :3
  Misses:39
Port #0 "midonet"  Internal  Stats{rxPackets=0, txPackets=0, rxBytes=0, txBytes=0, rxErrors=0, txErrors=0, rxDropped=0, txDropped=0}
Port #1 "tngre-overlay"  Gre  Stats{rxPackets=23, txPackets=30, rxBytes=4459, txBytes=4273, rxErrors=0, txErrors=0, rxDropped=0, txDropped=0}
Port #2 "tnvxlan-overlay"  VXLan  Stats{rxPackets=0, txPackets=0, rxBytes=0, txBytes=0, rxErrors=0, txErrors=0, rxDropped=0, txDropped=0}
Port #3 "tnvxlan-vtep"  VXLan  Stats{rxPackets=0, txPackets=0, rxBytes=0, txBytes=0, rxErrors=0, txErrors=0, rxDropped=0, txDropped=0}
Port #4 "veth1-1"  NetDev  Stats{rxPackets=31, txPackets=23, rxBytes=4099, txBytes=4459, rxErrors=0, txErrors=0, rxDropped=0, txDropped=0}
```

# 疎通確認

ここまでで各ホストのコンテナのvethが仮想ブリッジbridge0に接続されたので、試しにコンテナ内から隣のコンテナに接続してみる。

```bash:container1からcontainer2にssh
$ docker ps
CONTAINER ID        IMAGE                COMMAND             CREATED             STATUS              PORTS               NAMES
5ecaa2bc140c        centos_sshd:latest   "/bin/bash"         4 hours ago         Up 4 hours                              compassionate_darwin   
$ docker exec -it 5ec bash
[root@container11 /]# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN 
    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
7: veth1-2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 72:e7:96:78:cf:09 brd ff:ff:ff:ff:ff:ff
    inet 10.0.0.10/24 scope global veth1-2
       valid_lft forever preferred_lft forever
    inet6 fe80::70e7:96ff:fe78:cf09/64 scope link 
       valid_lft forever preferred_lft forever
[root@container11 /]# ssh 10.0.0.11
The authenticity of host '10.0.0.11 (10.0.0.11)' can't be established.
RSA key fingerprint is 62:07:3d:c7:f4:3f:bb:45:48:e8:45:85:42:33:39:28.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '10.0.0.11' (RSA) to the list of known hosts.
[root@container21 ~]# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN 
    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
13: veth1-2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 02:29:49:b5:34:30 brd ff:ff:ff:ff:ff:ff
    inet 10.0.0.11/24 scope global veth1-2
       valid_lft forever preferred_lft forever
    inet6 fe80::29:49ff:feb5:3430/64 scope link 
       valid_lft forever preferred_lft forever
[root@container21 ~]# 
```

問題なく接続できた。

# tips（多少上でも書いてるところがある）

* midonet-cliで、正しい構文で実行しているのに「Syntax Error」と言われることがある？
  * これはmidonet-cliが自分から状態を取得しにいっているわけではないためらしく、一回list系で取得してから再度設定系コマンドを実行すると直ったりする。
* tunnel zone？
  * MidoNetは離れたcompute node上のインタフェースとはflow-based tunnelingによって通信するが、トンネルの終端がある人であれば誰でも通信できてしまうため、勝手に知らない人と接続しないようにする機構がtunnel zone。
* GREがdropされる環境ではどうすれば？
  * tunnel zoneにはvxlanも利用できるので、vxlanを使ってみると良い。
* python-midonet-clientのインストールでエラーになる
  * epelが有効になっていないとエラーになるので、その可能性をあたってみる。
* zookeeperはDataStaxのものを入れる？
  * midonet-miscから入るのが正解。
* java8でも動く？
  * midolmanは起動時にjava7を要求しているので、7を入れること。
  * cassandraやzookeeperと共存環境にするとjava7と8が同時に入ったりするが、midolmanは7である必要がある。
* midolmanが起動しない？
  * メモリ不足でcassandraが動いていない可能性がある。MidoNetは全体で結構なメモリを消費するので、この点は注意。
  * lsmodでopenvswitchが存在しない場合はmodprobeする。
  * それぞれのmidolmanのバージョンが違うと起動に失敗するので、ホスト間でバージョンは合わせる。
* midolmanがNAT下の場合、Private IPをhostとして登録すると、Public IPである相手のtxDropが増加して返答返ってこないし、NATされたあとのPublic IPをhostとして登録すると送出時にtxErrorが出るんだけど？
  * そもそもそういう使い方をするケースがあんまりない気がするが、Private IPで登録しておいて、相手の方でPrivate IPに送出（返送）する場合はPublic IPにDNAT、とすれば疎通はできるようになる(SoftLayerのPublic IPとAWS VPCのPrivate IPの間で確認済み)。

# その他

Docker自身もリポジトリを見ている限りではおそらく1.9？でsocketplaneをベースにした仮想ネットワーク機能が利用可能になりそうだが、socketplaneなどコンテナ付属の仮想ネットワークは仮想トポロジ構築が中心で、ネットワーク機能はMidoNetなどのSDNプロダクトに比べて弱い可能性があり、今後の動向が気になるところ。