Docker v1.9のマルチホストネットワーク機能を使ったROS通信が可能か検証してみた

本記事は[TIS Engineer Advent Calendar 2015](http://qiita.com/advent-calendar/2015/tis)の21日目の記事です。

#やったこと
Docker v1.9から、Dockerネイティブでマルチホストネットワーキングに対応しました。そこで、２台のホストでROSの入ったコンテナを用意して、ROS通信が可能かどうか検証しました。

ROSの通信プロトコルはXML-RPCとTCP(+UDP)なのでコンテナ間通信自体は問題ないはずなのですが、今後、ROSの分散処理にもDockerの新しいネットワーク機能が使える可能性があるため、その手順を最初からまとめてみようと思います。

# 検証の概要
概要は下図のようになります。  

![image](https://qiita-image-store.s3.amazonaws.com/0/60470/e8235d70-5697-79bf-9abd-972eca3657c0.png)

検証のために、AWS上に２つのインスタンスを使用します。
両方にDockerを入れ、片方はROSのみ、もう一方はROS＋Caffeが入ったDockerコンテナを立ち上げます。
クライアントからサーバへ画像を送り、サーバではCaffeで画像識別（分類）し、識別候補をクライアントに返し、クライアント側で表示する、ということをやります。   


ちなみにCaffeとは、主に画像分類でよく用いられるディープラーニングフレームワークです。今回は[Caffenet](https://github.com/BVLC/caffe/tree/master/models/bvlc_reference_caffenet)という学習済みモデルを使用しています。学習データは[こちら](http://image-net.org/challenges/LSVRC/2012/browse-synsets)にあり、オブジェクトやシーンなど１０００カテゴリに分類することができます。

#環境
各インスタンスは以下のように設定しました。説明のために、各インスタンスの役割も載せています。

|インスタンス名|役割|インスタンスタイプ|ストレージ（GB）|セキュリティグループ|プライベートIP（例）|
|-----|-----|-----|-----|-----|-----|-----|
|インスタンスA|Caffeで画像分類（サーバ）|t2.medium|12|すべてのトラフィック|172.31.2.38|
|インスタンスB|事前に録画しておいた動画の再生、結果表示（クライアント）|t2.micro|8|すべてのトラフィック|172.31.11.163|

#手順
大きく分けて、前後半に分かれます
- 前半：Dockerの環境構築～コンテナのオーバレイネットワークの作成
- 後半：二つのコンテナ起動と、コンテナ間の接続検証、画像分類

Docker v1.9のマルチホストネットワークについてはこちらが詳しいです。[Docker1.9のマルチホストネットワーク](https://thinkit.co.jp/article/8414)

以下の手順は、こちらのページを参考にしました。
[Docker : マルチホスト間での仮想ネットワーク](http://qiita.com/Arturias/items/dd7f9e2365626dd4ad51)

## （前半）EC2インスタンスに接続
sshでインスタンスに接続します。インスタンスA,Bそれぞれ以下のようにして接続してください。

```bash
ssh -i "キー（.pem）のファイル名" ubuntu@接続先のパブリックIPアドレス
```

#Dockerのインストール
まずはDocker v1.9をそれぞれのインスタンス上にインストールします。Docker v1.9はカーネルが「3.16」以上が必要ですが、デフォルトでは3.13だったので、アップグレードします。

```bash
sudo apt-get update

# カーネルアップグレード
sudo apt-get install linux-image-3.19.0-39-generic

# 再起動
sudo shutdown -r now
```

再起動後、もう一度EC2インスタンスにsshで接続します。その後Dockerをインストールします。

```bash
# Docker v1.9のインストール
# 公式サイト：https://docs.docker.com/engine/installation/ubuntulinux/

# キーを追加する
sudo apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D

# /etc/apt/sources.list.d/docker.listを作成・更新
sudo touch /etc/apt/sources.list.d/docker.list
sudo sh -c 'echo "deb https://apt.dockerproject.org/repo ubuntu-trusty main" >> /etc/apt/sources.list.d/docker.list'

# パッケージリストの更新
sudo apt-get update

# Dockerのインストール
sudo apt-get install docker-engine

# Dockerデーモン起動
service docker start

# バージョンの確認
docker --version

# Dockerデーモン停止
service docker stop
```

#Consulのインストール

```bash
# wget, unzipのインストール
sudo apt-get install wget unzip

# インストーラのダウンロード
wget https://dl.bintray.com/mitchellh/consul/0.5.0_linux_amd64.zip

# 解凍
unzip 0.5.0_linux_amd64.zip

# インストール
sudo mv consul /usr/local/bin/
```

#コンテナ間のネットワーク設定
では、まずは両インスタンスでConsulを起動します。このとき、インスタンスA側をサーバ、インスタンスB側をクライアントとしてクラスタを組みます。

```bash:インスタンスA
# コンサル起動（サーバ）
# 例）consul agent -server -bootstrap-expect 1 -data-dir /tmp/consul　-bind 172.31.2.38
$ consul agent -server -bootstrap-expect 1 -data-dir /tmp/consul　-bind サーバのeth0のIPアドレス
```

```bash:インスタンスB
# コンサル起動（クライアント）
# 例）consul agent -data-dir /tmp/consul -bind 172.31.11.163 -join 172.31.2.38
$ consul agent -data-dir /tmp/consul -bind クライアントのeth0のIPアドレス -join サーバのeth0のIPアドレス

```

次に、Dockerデーモンをそれぞれ起動します。

```bash:インスタンスA
# Dockerデーモン起動（サーバ）
# 例）docker daemon --cluster-store=consul://localhost:8500 --cluster-advertise=172.31.2.38:2376
$ docker daemon --cluster-store=consul://localhost:8500 --cluster-advertise=サーバのeth0のIPアドレス:2376
```

```bash:インスタンスB
# Dockerデーモン起動（クライアント）
# 例）docker daemon --cluster-store=consul://localhost:8500 --cluster-advertise=172.31.11.163:2376
$ docker daemon --cluster-store=consul://localhost:8500 --cluster-advertise=クライアントのeth0のIPアドレス:2376
```

ではネットワークを作成する準備が整いました。Dockerネットワークを作成しましょう。TestNetworkという名前のオーバレイネットワークを作成します。

```bash
# Dockerネットワーク作成（サーバまたはクライアントどちらか一方でよい）
docker network create -d overlay TestNetwork
```

では作成されているか確認しましょう。

```bash
# サーバ、クライアント両方でネットワークが表示されることを確認する
docker network ls

NETWORK ID          NAME                DRIVER
81adcdfa8a2c        TestNetwork         overlay             
1841a25b8ac4        docker_gwbridge     bridge              
e56f819e681e        bridge              bridge              
a0bae70d0364        none                null                
97a57a078ed1        host                host
```

## （後半）サーバコンテナを起動する
サーバコンテナでは、画像をクライアントコンテナから受け取り、Caffeで画像識別して結果をターミナルに表示します。そのために、ROSとCaffe本体、CaffeのROSブリッジが入ったDockerイメージを使います。

では、コンテナを起動してゆきます。

```bash
# ROSフルパッケージ ＋ Caffe（CPUビルド済み）＋ CaffeのROSブリッジが入ったDockerイメージをダウンロード
docker pull ykoga/ros_caffe_cpu

# コンテナ起動（サーバコンテナ）
docker run -it --rm \
               --net=TestNetwork \
               --name caffe \
               --hostname caffe \
               --env ROS_HOSTNAME=caffe \
               --env ROS_MASTER_URI=http://caffe:11311 \
               ykoga/ros_caffe_cpu \
               /bin/bash
```

中に入ったら、一応ifconfigでネットワーク情報を確認しておきます。
おそらく、"eth数字  10.0.0.x" というネットワークインターフェースが存在すると思います。これが、ネットワークオーバーレイで生成されたネットワークです。

ではROSマスターとCaffeノードを起動します。

```bash:サーバコンテナ内
# ROSマスターの起動
roscore &

# クライアントから画像を受け取ってCaffeで識別する
roslaunch ros_caffe ros_caffe.launch image:=/image_raw
```

##クライアントコンテナを起動する
クライアントコンテナでは、テスト用に事前にROS bagファイルにカメラ画像を録画しておいたファイルがあるので、それを使ってカメラ画像をPublishします。

では、コンテナを起動してゆきます。まずはROSの基本機能のみが入ったコンテナを起動します。

```bash
# ROSの基本イメージをダウンロード
docker pull ros:indigo-ros-base

# コンテナ起動（クライアント）
docker run -it --rm \
               --net=TestNetwork \
               --name image_pub \
               --hostname image_pub \
               --env ROS_HOSTNAME=image_pub \
               --env ROS_MASTER_URI=http://caffe:11311 \
               ros:indigo-ros-base \
               /bin/bash
```

今回の検証で使うために、事前にROS bagファイルをダウンロードしておきます。

bagファイルには、事前にWebカメラで撮った動画が入っています。中身は下図のようなものです。（もし手元にROS環境がローカルPCにもある方ならrosrun rqt_image_view rqt_image_viewでビューアを起動すれば確認できます）
![caffe_result_image.png](https://qiita-image-store.s3.amazonaws.com/0/60470/3b07efe5-5e09-c9be-00ce-a1a0708fa051.png)


ではbagファイルをダウンロード、再生します。

```bash:クライアントコンテナ内
apt-get update
apt-get install wget

# bagファイルのダウンロード
wget https://raw.githubusercontent.com/ykoga-kyutech/ros_caffe_cpu/master/test_webcam.bag

# 識別結果を表示
rostopic echo /ros_caffe/predictions &

# ROS bagファイル（録画した動画）の再生
rosbag play --loop test_webcam.bag
```

最後の行まで打ち込むと、Caffeの識別が始まり、クライアントコンテナ側に識別結果が表示されます。結果は例えば次のようになります。

![client-result.png](https://qiita-image-store.s3.amazonaws.com/0/60470/b0b09595-d640-fb7a-efde-fea5ebd7d0e0.png)


結果の見方ですが、結果は上位５件ずつ候補が表示されます。スコアは０～１をとり、１のものほど確信度が高いことを示しています。この例だと、notebook, notebook computerが最も確信度の高い識別結果ということになります。

# まとめ
Docker v1.9からの新機能であるマルチホストネットワーク機能を使って２台のインスタンス間で画像のやりとりを行うことで、ROS通信が問題なくできることを確認しました。


#わからなかったこと
- 上記のやり方でAWSインスタンス間での通信はOKだったが、AWSインスタンスと自宅PC間のROS通信はできなかった。というかConsulでクラスタを組む所からできなかった。VPNとかで両者を接続しないとだめなのかな。
- ROSの通信プロトコルはXML-RPCとTCP(+UDP)のはずなので、セキュリティグループを色々いじってみたが、結局、設定方法が「すべてのトラフィック」しか通信できなかった。ROS通信でのセキュリティグループの設定が不明。