OpenVNetとDockerを組み合わせてみるデモ


概要
----

* これは、Wakame-VDC / OpenVNet AdventCalendar 2014 12/11(木)の記事。
* OpenVNetをdockerとあわせて動かしてみる。
* とりあえずは外には出ない、仮想ネットワーク内のコンテナ間だけの疎通からはじめてみた。

2015/02/05 コンテナのIP帯と仮想ネットワークIP帯に誤りがあった（同一セグメントなので届いていた）のと、環境を少し変更したため、再編集。

参考ドキュメント
--------------------------
[InstallGuite](https://github.com/axsh/openvnet/blob/master/docs/InstallGuide.md)の、Let's try 1Box OpenVNetのあたり。
db.shが結構前のバージョンのもので、現行バージョンのOpenVNetでは動作しない。

これについては、[OpenVNet開発メンバーのarkyさんのgithubリポジトリにあるリソース](https://github.com/akry/vnet-vagrant/blob/master/config.d/vnmgr_vna1.sh)から、
最新のDBに適応するAPIコールっぽいものを参考にした。

前提
----

### 但し書き

* IPアドレスは静的割り当てにした。

### 環境
OpenVNetは公式で6.4のインストール手順となっているが、~~CentOS6.5で動作した報告があったため、適当に準備したCentOS6.5のVM上で行った。~~CentOS6.6で行った。
Dockerはdocker-ioをepelからインストールし、RDOからiprouteとカーネルを更新した環境（割と前に流行った？環境）を使った。

また、NICは2枚あり、

eth0：ssh、OpenVNetの管理で利用（192.168.122.50）
eth1：OpenVNetでPublic Network用に利用予定（今回は未使用）

となっている。

作業の流れ
---------

今回は[InstallGuite](https://github.com/axsh/openvnet/blob/master/docs/InstallGuide.md) に則って、

* All-in-One構成（vnmgr、webapi、vnaすべてが同じホストにある）
* 4台のVMというかコンテナがあり、2台はvnet1、残り2台はvnet2に所属

で動かす。
ネットワークに関しては、docker0は放っておいてbr0をOVSでつくり、それを利用する形にする。コンテナのNIC追加は、pipeworkは使わず、netnsにvethを放り込む形にする。

### OpenVNetのインストール
OpenVNetのインストールが必要。
せっかくなので、このデモにあわせて[OpenVNetのcookbook](https://github.com/qb0C80aE/openvnet-cookbook/tree/develop)を作った。

これを利用すると、CentOS6.5-minimalにchefdkでもいれておけば簡単にOpenVNetをインストールできる、かもしれない。

### br0の作成
br0作成は、[InstallGuite](https://github.com/axsh/openvnet/blob/master/docs/InstallGuide.md)のとおりで問題ない。OVSのスイッチとして作成する。

### Dockerイメージの準備

コンテナのイメージはcentos:centos6のイメージをベースとして、とりあえず簡単にするために、bashが起動したらsshdが起動し、さらにパスワードなしsshでログインできるものにした。
※下記は手入力だが、Dockerfileがお勧め

```bash
$ docker run -i -t centos:centos6 /bin/bash
---- 以下、コンテナ内部 ----
$ yum -y update
$ yum -y install openssh-server openssh-clients
$ sed -ri 's/^#PermitEmptyPasswords no/PermitEmptyPasswords yes/' /etc/ssh/sshd_config
$ sed -ri 's/^#PermitRootLogin yes/PermitRootLogin yes/' /etc/ssh/sshd_config
$ sed -ri 's/^UsePAM yes/UsePAM no/' /etc/ssh/sshd_config
$ passwd -d root
$ cat > /etc/profile.d/sshd.sh <<EOF
> #!/bin/sh
> service sshd start
> EOF
$
[Ctrl-p, Ctrl-q]
---- 以下、コンテナ外部 ----
$ docker commit <container_id> centos_sshd
```

### コンテナの起動

dockerのコンテナを準備し、OVSにそれぞれのvethの片割れをつなぎ、残った片割れはそれぞれのnetwork namespace内で見えるように設定するシェルスクリプトを書いた。

```bash:openvnet-demo-init.sh
#!/bin/sh

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

rm -f ./netconfig.env
mkdir -p /var/run/netns/

for i in `seq 1 ${#IP_ADDRESSES[@]}`; do
  let address_index=$i-1
  CONTAINER_ID=`docker run --hostname="container${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

  ovs-vsctl add-port ${BRIDGE} veth${i}-1

  hwaddr=`ip netns exec ${BASH_PID} ip link show veth${i}-2 | awk 'NR==2' | awk '{print $2}'`
  ipaddr=`echo "${IP_ADDRESSES[${address_index}]}" | awk -F/ '{print $1}'`
  netmask=`echo "${IP_ADDRESSES[${address_index}]}" | awk -F/ '{print $2}'`
  echo "veth${i}-2 ${IP_ADDRESSES[${address_index}]} ${hwaddr}"
  echo "export mac_veth${i}2=${hwaddr}" >> ./netconfig.env
  echo "export ip_veth${i}2=${ipaddr}" >> ./netconfig.env
  echo "export netmask_veth${i}2=${netmask}" >> ./netconfig.env
done
```

やっていることは、

* centos_sshdのイメージでコンテナを起動(※1)
* 起動したコンテナIDからホスト側で見えるpidを取得
* /var/run/netnsにコンテナのprocに対してリンクをはって、ip netnsで見えるようにする
* vethを起動して、コンテナのnamespaceに片方放り込み、ifupしてipアドレスを設定
* vethの残り片方をOVSにつなぐ(※2)
* 次のシェルに渡すために、起動したコンテナのveth情報をファイル（netconfig.env）に出力しておく

※1：--net="none"を指定してdocker0を使わないようにする。ここでbr0を指定すると挙動がおかしくなるので注意。
※2：本来プロダクト側が提供している機能だろうから不要な気もするが、元々のデモでもVM起動と同時にovs-vsctl add-portでポートを手動設定しているように見える。wakame-vdcの方にある機能なのかも。

となる。実行すると、以下のような結果が出力される。

```bash
$ ./openvnet-demo-init.sh
veth1-2 10.102.0.10/24 ae:89:94:e5:d0:68
veth2-2 10.102.0.10/24 96:a4:c2:09:ed:a3
veth3-2 10.102.0.11/24 7e:ae:29:23:36:9c
veth4-2 10.102.0.11/24 ca:3c:ad:dd:50:a8
```

netconfig.envの中身は、以下のようになる。

```bash:netconfig.env
export mac_veth12=ae:89:94:e5:d0:68
export ip_veth12=10.102.0.10
export netmask_veth12=24
export mac_veth22=96:a4:c2:09:ed:a3
export ip_veth22=10.102.0.10
export netmask_veth22=24
export mac_veth32=7e:ae:29:23:36:9c
export ip_veth32=10.102.0.11
export netmask_veth32=24
export mac_veth42=ca:3c:ad:dd:50:a8
export ip_veth42=10.102.0.11
export netmask_veth42=24
```

上記で構築したコンテナを削除する場合は、以下のスクリプトを実行する。

```bash:openvnet-demo-cleanup.sh
#!/bin/sh
 
BRIDGE=br0
 
ovs-vsctl show | grep "Port \"veth" | awk '{print $2}' | sed -e 's/"//g' | while read port; do
  ovs-vsctl del-port ${BRIDGE} ${port}
done
 
docker ps | awk '{print $1}' | grep -v grep | grep -v CONTAINER | while read container_id; do
  docker kill ${container_id}
done
 
rm -rf /var/run/netns/*
```

### OpenVNetのAPIで仮想ネットワーク設定

webapiを利用して仮想ネットワーク設定を行う。
元々のデモ環境インストール手順のdb.shに似たようなものとして、以下を作った。

```bash:db.sh
#!/bin/sh

set -e
set -x

mysqladmin -f -uroot drop vnet
mysqladmin -uroot create vnet
(cd /opt/axsh/openvnet/vnet; bundle exec rake db:init)

export vnmgr_host=127.0.0.1
export vnmgr_port=9090

# 上記のコンテナを作成するシェルスクリプトで出力される、
# コンテナのNIC情報を出力したファイル
. ./netconfig.env

# datapaths
curl -X POST \
--data-urlencode uuid=dp-node1 \
--data-urlencode display_name="node1" \
--data-urlencode dpid="0x00004e6d2b508f4c" \
--data-urlencode node_id="node1" \
http://${vnmgr_host}:${vnmgr_port}/api/datapaths

# networks
curl -X POST \
--data-urlencode uuid=nw-vnet1 \
--data-urlencode display_name="nw-vnet1" \
--data-urlencode ipv4_network="10.100.0.0" \
--data-urlencode ipv4_prefix="24" \
--data-urlencode network_mode="virtual" \
http://${vnmgr_host}:${vnmgr_port}/api/networks

curl -X POST \
--data-urlencode uuid=nw-vnet2 \
--data-urlencode display_name="nw-vnet2" \
--data-urlencode ipv4_network="10.100.0.0" \
--data-urlencode ipv4_prefix="24" \
--data-urlencode network_mode="virtual" \
http://${vnmgr_host}:${vnmgr_port}/api/networks

# interfaces
curl -X POST \
--data-urlencode uuid="if-veth1" \
--data-urlencode owner_datapath_uuid="dp-node1" \
--data-urlencode mac_address="${mac_veth12}" \
--data-urlencode network_uuid="nw-vnet1" \
--data-urlencode ipv4_address="${ip_veth12}" \
--data-urlencode port_name="veth1-1" \
http://${vnmgr_host}:${vnmgr_port}/api/interfaces

curl -X POST \
--data-urlencode uuid="if-veth2" \
--data-urlencode owner_datapath_uuid="dp-node1" \
--data-urlencode mac_address="${mac_veth22}" \
--data-urlencode network_uuid="nw-vnet2" \
--data-urlencode ipv4_address="${ip_veth22}" \
--data-urlencode port_name="veth2-1" \
http://${vnmgr_host}:${vnmgr_port}/api/interfaces

curl -X POST \
--data-urlencode uuid="if-veth3" \
--data-urlencode owner_datapath_uuid="dp-node1" \
--data-urlencode mac_address="${mac_veth32}" \
--data-urlencode network_uuid="nw-vnet1" \
--data-urlencode ipv4_address="${ip_veth32}" \
--data-urlencode port_name="veth3-1" \
http://${vnmgr_host}:${vnmgr_port}/api/interfaces

curl -X POST \
--data-urlencode uuid="if-veth4" \
--data-urlencode owner_datapath_uuid="dp-node1" \
--data-urlencode mac_address="${mac_veth42}" \
--data-urlencode network_uuid="nw-vnet2" \
--data-urlencode ipv4_address="${ip_veth42}" \
--data-urlencode port_name="veth4-1" \
http://${vnmgr_host}:${vnmgr_port}/api/interfaces

initctl restart vnet-vna
```

ここまでを実行することで、以下のような構成となる。


|コンテナのホスト名|コンテナのNIC|コンテナのIPアドレス|所属するネットワーク|
|:---------------|:-----------|:------------------|:-----------------|
|container1      |veth1-2     |10.102.0.10        |vnet1             |
|container2      |veth2-2     |10.102.0.10        |vnet2             |
|container3      |veth3-2     |10.102.0.11        |vnet1             |
|container4      |veth4-2     |10.102.0.11        |vnet2             |

db.shの実行で「method uuid= doesn't exist.」的なエラーが出る場合、パラメータが足りないのではなく、vnmgrが起動していない可能性があるので調べてみること。

通信テスト
---------

うまく設定されていれば、container1にattachして10.102.0.11にsshしたとき、container3にログインするはず。
また同じく、container2にattachして10.102.0.11にsshしたとき、container4にログインするはずなので、
試しに確認する。

### container1 ⇔ container3

```bash
$ docker ps
CONTAINER ID        IMAGE                COMMAND             CREATED             STATUS              PORTS               NAMES
76e2f829871b        centos_sshd:latest   "/bin/bash"         2 minutes ago       Up 2 minutes                            agitated_jones
62006d5e5cda        centos_sshd:latest   "/bin/bash"         2 minutes ago       Up 2 minutes                            cocky_mclean
2aaf3ca3e452        centos_sshd:latest   "/bin/bash"         2 minutes ago       Up 2 minutes                            sick_mcclintock
20ef16f60760        centos_sshd:latest   "/bin/bash"         2 minutes ago       Up 2 minutes                            trusting_mcclintock
$ docker attach 20ef16f60760
[root@container1 /]# ifconfig
lo        Link encap:Local Loopback
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:0 (0.0 b)  TX bytes:0 (0.0 b)

veth1-2   Link encap:Ethernet  HWaddr AE:89:94:E5:D0:68
          inet addr:10.102.0.10  Bcast:0.0.0.0  Mask:255.255.255.0
          inet6 addr: fe80::ac89:94ff:fee5:d068/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:6 errors:0 dropped:0 overruns:0 frame:0
          TX packets:6 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:468 (468.0 b)  TX bytes:468 (468.0 b)

[root@container1 /]# ssh 10.102.0.11
The authenticity of host '10.102.0.11 (10.102.0.11)' can't be established.
RSA key fingerprint is a7:e0:0d:6f:52:d9:29:bc:5e:4e:56:14:36:e8:02:b6.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '10.102.0.11' (RSA) to the list of known hosts.
[root@container3 ~]# ifconfig
lo        Link encap:Local Loopback
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:0 (0.0 b)  TX bytes:0 (0.0 b)

veth3-2   Link encap:Ethernet  HWaddr 7E:AE:29:23:36:9C
          inet addr:10.102.0.11  Bcast:0.0.0.0  Mask:255.255.255.0
          inet6 addr: fe80::7cae:29ff:fe23:369c/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:42 errors:0 dropped:0 overruns:0 frame:0
          TX packets:31 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:5113 (4.9 KiB)  TX bytes:4635 (4.5 KiB)

[root@container3 ~]#
```

問題なく通信できる。

### container2 ⇔ container4

```bash
$ docker ps
CONTAINER ID        IMAGE                COMMAND             CREATED             STATUS              PORTS               NAMES
76e2f829871b        centos_sshd:latest   "/bin/bash"         4 minutes ago       Up 4 minutes                            agitated_jones
62006d5e5cda        centos_sshd:latest   "/bin/bash"         4 minutes ago       Up 4 minutes                            cocky_mclean
2aaf3ca3e452        centos_sshd:latest   "/bin/bash"         4 minutes ago       Up 4 minutes                            sick_mcclintock
20ef16f60760        centos_sshd:latest   "/bin/bash"         4 minutes ago       Up 4 minutes                            trusting_mcclintock
$ docker attach 2aaf3ca3e452
[root@container2 /]# ifconfig
lo        Link encap:Local Loopback
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:0 (0.0 b)  TX bytes:0 (0.0 b)

veth2-2   Link encap:Ethernet  HWaddr 96:A4:C2:09:ED:A3
          inet addr:10.102.0.10  Bcast:0.0.0.0  Mask:255.255.255.0
          inet6 addr: fe80::94a4:c2ff:fe09:eda3/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:6 errors:0 dropped:0 overruns:0 frame:0
          TX packets:6 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:468 (468.0 b)  TX bytes:468 (468.0 b)

[root@container2 /]# ssh 10.102.0.11
The authenticity of host '10.102.0.11 (10.102.0.11)' can't be established.
RSA key fingerprint is a7:e0:0d:6f:52:d9:29:bc:5e:4e:56:14:36:e8:02:b6.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '10.102.0.11' (RSA) to the list of known hosts.
[root@container4 ~]# ifconfig
lo        Link encap:Local Loopback
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:0 (0.0 b)  TX bytes:0 (0.0 b)

veth4-2   Link encap:Ethernet  HWaddr CA:3C:AD:DD:50:A8
          inet addr:10.102.0.11  Bcast:0.0.0.0  Mask:255.255.255.0
          inet6 addr: fe80::c83c:adff:fedd:50a8/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:42 errors:0 dropped:0 overruns:0 frame:0
          TX packets:31 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:5113 (4.9 KiB)  TX bytes:4635 (4.5 KiB)

[root@container4 ~]#
```

こちらも、問題なく通信できた。

その他メモ
---------

netnsが絡むと、ipアドレス設定含め、通常権限起動のコンテナ内からはinterface関連の操作ができない。(LinuxCapabilities)

```bash
$ ip addr add 192.168.0.100 dev eth0
RTNETLINK answers: Operation not permitted
$ 
```

このあたりをうまくコントロールするサービスがホスト側にいればいい気もする。或いはセキュリティポリシー次第でcap-add。