Dockerを用いてRaspberryPi3上でROSを使ってみる

## はじめに
Dockerの勉強がしたかったので、ラズパイでDockerとROSを使ってみることにしました。ラズパイにROSをインストールする方法としては、[Raspbian Jessie](http://wiki.ros.org/ROSberryPi/Installing%20ROS%20Kinetic%20on%20the%20Raspberry%20Pi) を使う方法や、[ubuntu MATE](http://wiki.ros.org/kinetic/Installation/Ubuntu) を使う方法がありますが、ここではRaspbian StretchとDockerを使います。

## 環境構築
### Raspbian Stretchのインストール
[Installing operating system images](https://www.raspberrypi.org/documentation/installation/installing-images/README.md) を参考に、SDカードにRaspbianをインストールします。

1. Raspbian Stretch with desktopをダウンロードします。[RaspberryPi公式](https://www.raspberrypi.org/downloads/raspbian/) からダウンロードするとかなり時間がかかりますが、[JAISTのミラー](http://ftp.jaist.ac.jp/pub/raspberrypi/raspbian/images/) からダウンロードすると早く終わります。([参考](https://memoteki.net/archives/1708#Raspbian))
2. ダウンロードしたzipファイルを解凍してSDカードに書き込みます。

### Dockerのインストール
ラズパイを起動し、[Get Docker CE for Debian](https://docs.docker.com/install/linux/docker-ce/debian/) にしたがってDocker CEをインストールします。(CEはCommunity Editionで無償版です。)

1. 通常はリポジトリからインストールするそうですが、Raspbianにはまだ対応していないので[curlでインストールスクリプトを持ってきてインストール](https://docs.docker.com/install/linux/docker-ce/debian/#install-using-the-convenience-script)するそうです。

    ```sh
    $ curl -fsSL https://get.docker.com -o get-docker.sh
    $ sudo sh get-docker.sh
    ```
2. インストールが終わったら、sudoなしでdockerを使用できるように、dockerグループに現在のユーザを追加します。Raspbianの初期設定時のユーザ名はpiなので、次のコマンドを実行します。（ログアウト／ログイン後にusermodの変更が反映されるので、ここで再起動しておきます。）

    ```sh
    $ sudo usermod -aG docker pi
    $ reboot
    ```
3. dockerが正常に動作するか確認するため、Hello Worldを実行します。

    ```sh
    $ docker run hello-world
    ```
    一瞬、「unable to find image 'hello-world:latest' locally」と表示されますが、ローカルにないDockerイメージからコンテナを作成しようとしたためです。ローカルにないイメージは自動的にダウンロードされます。
![2019-01-20-085056_1824x984_scrot.png](https://qiita-image-store.s3.amazonaws.com/0/248231/1896dcfb-e19d-6d03-675e-249e63edbecd.png)
「Hello from Docker!」が表示されました。

## ROSを使ってみる
### ROSイメージの利用
[Getting started with ROS and Docker](http://wiki.ros.org/docker/Tutorials/Docker) を参考にROSを動かしてみます。

1. ```docker pull```コマンドでDockerイメージを持ってきます。ROSのDockerイメージは[DockerHub](https://hub.docker.com/_/ros/?tab=description&page=2)に登録されています。
    また、イメージ名の後ろにコロンと[タグ](https://hub.docker.com/_/ros/?tab=tags)をつけることでROSのディストリビューションなどを指定することができます。今回はKineticを使ってみたいと思います。

    ```sh
    $ docker pull ros:kinetic
    ```

2. ダウンロードが終わったら、```docker run```コマンドでイメージからコンテナを起動します。```-it```オプションをつけると、起動と同時にコンテナ内に入ります。

    ```sh
    $ docker run -it ros:kinetic
    ```
3. コンテナ内で、```roscore```コマンドを実行してROSのmasterを起動します。

    ```sh
    roscore
    ```

4. 先ほど起動したコンテナの名前を取得します（```docker run```を実行するとき```--name```オプションでコンテナ名を指定しないと適当な名前に設定されるそうです）。新しいターミナルを立ち上げて```docker ps```コマンドを実行すれば確認できます（```-l```オプションをつけると最後に起動したコンテナのみを表示します）。

    ```sh
    $ docker ps -l
    ```
    ![2019-01-24-213442_655x394_scrot.png](https://qiita-image-store.s3.amazonaws.com/0/248231/42c79036-9635-5757-bf37-f8ac706e0205.png)
    このコンテナの名前は```jolly_wing```のようです。

5. コンテナ名が分かったので、新しいターミナルからコンテナ内に入ります。```docker exec```は指定したコマンドをコンテナ内で実行するコマンドですが、```-it```オプションをつけて```bash```を実行することで、コンテナ内に入ることができます。

    ```sh
    $ docker exec -it jolly_wing bash
    ```

6. 入った直後に```rostopic list```を実行しようとすると、"bash: rostopic: command not found"のエラーになるので、先に以下のコマンドでROSの環境をセットアップします（もしくは```source /ros_entrypoint.sh```でもセットアップできるようです）。

    ```sh
    source /opt/ros/kinetic/setup.bash
    ```

    こうしてから```rostopic list```を実行すると、次のようにトピックのリストが表示されます。

    ![2019-01-24-221430_655x394_scrot.png](https://qiita-image-store.s3.amazonaws.com/0/248231/7c9b7494-c661-c493-8a3e-6e94c41a61ba.png)

    

7. コンテナから抜けるには、```exit```コマンドを実行します。

    ```sh
    exit
    ```

    ちなみに、```docker exec```した方のターミナルで```exit```コマンドを実行するとコンテナから出た後もコンテナは動いたままです。一方、```docker run```でコンテナを起動したターミナルで```exit```コマンドを実行すると、コンテナも停止します。

8. ```docker rm```コマンドを使えばコンテナを削除できます。まず、現在のコンテナを確認します。```docker ps```コマンドに```-a```オプションをつけると停止したコンテナも含めてすべてのコンテナの一覧が表示されます。

    ```sh
    $ docker ps -a
    ```

    ![2019-02-03-001153_655x394_scrot.png](https://qiita-image-store.s3.amazonaws.com/0/248231/b9d8d15d-0706-b781-8081-717646422c3a.png)


    ```sh
    $ docker rm jolly_wing
    ```

    ![2019-02-03-002354_655x394_scrot.png](https://qiita-image-store.s3.amazonaws.com/0/248231/beb27aa9-ae6e-d1ee-2312-4db26ba1eb2e.png)

    コンテナが削除されました。

## ワークスペースの作成

コンテナ内にROSのワークスペース(catkin_ws)を作り自作のROSパッケージを置きたいところですが、[Dockerfile のベストプラクティス](http://docs.docker.jp/engine/articles/dockerfile_best-practice.html)によるとコンテナはいつでも廃棄できるようにするべきらしく、ソースコードなどはコンテナ内に置かないようです。

[Best practices for getting code into a container](https://forums.docker.com/t/best-practices-for-getting-code-into-a-container-git-clone-vs-copy-vs-data-container/4077)を読む限り、外部のソースコードをコンテナ内に取り込む方法はいくつかあるようですが、とりあえず```docker run```を実行する際に```-v```オプションを使うことで、ホスト側のディレクトリをコンテナと共有できるそうです。

[Docker Hub](https://hub.docker.com/_/ros/)のDeployment suggestionsにも、```-v```オプションを使ってROSのログファイルをホスト側に残す方法が記載されていますので、これを参考にホスト側とコンテナ側で共有するワークスペースを作成します。

1. ホスト側で~/.rosディレクトリと~/catkin_ws/srcディレクトリを作ります。

    ```sh
    $ mkdir ~/.ros
    $ mkdir -p ~/catkin_ws/src
    ```

2. 次のコマンドでコンテナを起動します。```-v```の後に続く```/home/pi/catkin_ws/```がホスト側、```:```の後に続く/root/catkin_ws/```がコンテナ側のワークスペースのディレクトリです。

    ```sh
    $ docker run -it -v "/home/pi/.ros/:/root/.ros/" -v "/home/pi/catkin_ws/:/root/catkin_ws/" ros:kinetic
    ```

3. コンテナの中に入ったら、[Creating a ROS Package](http://wiki.ros.org/ROS/Tutorials/CreatingPackage)を参考にチュートリアル用のROSパッケージを作ります。

    ```sh
    cd ~/catkin_ws/src
    catkin_create_pkg beginner_tutorials std_msgs rospy roscpp
    ```

    ![2019-02-03-005537_655x394_scrot.png](https://qiita-image-store.s3.amazonaws.com/0/248231/84d2972b-3a50-2c66-d669-635e37ec480c.png)



4. パッケージを作成できたので、```catkin_make```でワークスペースをビルドします。

    ```sh
    cd ~/catkin_ws
    catkin_make
    ```

    ビルドが終わるとbuildディレクトリが生成されます。
    ![2019-02-03-004948_655x394_scrot.png](https://qiita-image-store.s3.amazonaws.com/0/248231/0bcfb2af-a8d7-7a8f-9a69-21b8bbcc9203.png)


5. コンテナから抜けて、ホスト側のワークスペースにもROSパッケージとbuildディレクトリが追加されているか確認します。

    ![2019-02-03-005548_655x394_scrot.png](https://qiita-image-store.s3.amazonaws.com/0/248231/252c504a-34db-8b99-1620-f0217c8c6d42.png)

    これで、ホスト側のワークスペースにソースファイルを追加して編集すれば、再度```docker run```を実行したときに、それをコンテナに取り込むことができます。

## おわりに

なんとかラズパイとDockerでROSが動くようにできました。少し手間がかかりますが、DockerfileからDockerイメージを作れるようになればいろいろ便利になりそうです。