Docker で Rviz、Gazebo を動かす on Ubuntu

# はじめに
ROS の環境を色々作ってみたい、けれど環境構築が面倒くさいといった方のために、Docker での ROS 環境構築方法について説明します。ホスト OS に直接環境を構築するのに比べて環境構築の手間が省ける、何個も環境を作れるなどのメリットがあります。今回紹介する方法は Docker の知識をあまり必要としないのが良い点です。

# 前提とする PC の環境
以下の2つを満たすこと
- Ubuntu が入っている
- NVIDIA のグラフィックボード、もしくは Intel などの内蔵 GPU [^内蔵GPU] を搭載している
[^内蔵GPU]: "[内蔵GPUとは？外部GPUとの違いや今後の動向について解説！](https://dosparaplus.com/library/details/000742.html)" によると、内蔵 GPU とは Intel などの CPU の内蔵グラフィックスのことを指します。一方で、分離 GPU はグラフィックボードなどのことを指します。

# できるようになること
Docker で立ち上げた Ubuntu から ROS を起動し Rviz、Gazebo の GUI 表示

:::note info
インフォメーション
まずはじめに、使いたい ROS バージョンがサポートされているか以下のサイトからご確認ください。今回はこの GitHub のリポジトリが元となっています。
:::

https://github.com/turlucode/ros-docker-gui#supported-ros-images
# 今回行うこと
- Docker のインストール
- NVIDIA 関連のあれこれ
    - Nouveau の無効化
    - NVIDIA ドライバのインストール
    - NVIDIA Container Toolkit のインストール
- X サーバへのアクセス設定
- Docker イメージの取得
    - Docker ファイルのダウンロード
    - Docker ファイルのビルド
- 動作確認

# Docker のインストール
"[Install Docker Engine on Ubuntu - Install using the Apt repository](https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository)" の通り、ターミナルを開き以下のコマンドを実行してください。
```
$ sudo apt update
$ sudo apt -y install ca-certificates curl gnupg
$ sudo install -m 0755 -d /etc/apt/keyrings
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
$ sudo chmod a+r /etc/apt/keyrings/docker.gpg
$ echo \
  "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
$ sudo apt update
$ sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
```
次に、Docker のコマンドを管理者権限なし (`sudo` なし) で実行できるように変更します。以下のコマンドを実行してください。
```
$ sudo groupadd docker
$ sudo usermod -aG docker $USER
```
手順が完了したか確認します。ターミナルを一度閉じ、再度開いてから以下のコマンドを実行してください。
```
$ docker --rm run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
719385e32844: Pull complete 
Digest: sha256:88ec0acaa3ec199d3b7eaf73588f4518c25f9d34f58ce9a0df68429c5af48e8d
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/
```

# NVIDIA ドライバ関連のあれこれ
この手順は、PC に NVIDIA グラフィックボードを搭載している場合のみ行います。グラフィックボードを搭載しておらず内蔵 GPU [^内蔵GPU] を搭載している場合、この手順はスキップして "[X サーバへのアクセス設定](#x-サーバへのアクセス設定)" の手順に進んでください。

- NVIDIA グラフィックボードを搭載している場合
    まず既に NVIDIA ドライバをインストール済みかどうかを確認します。ターミナルを開き以下のコマンドを実行してください。
    ```
    $ dpkg -l | grep nvidia
    ```
    何も表示されなかった場合はそのまま以下の手順に進みます。何かしらの NVIDIA ドライバの情報が表示された場合は、以下の手順をスキップして "[Docker のインストール](#docker-のインストール)" の手順に進んでください。

    その後、以下の手順を行います。
    - Nouveau の無効化
    - NVIDIA ドライバのインストール
    

## Nouveau の無効化
- NVIDIA グラフィックボードを搭載している場合
    まず Nouveau モジュールが動いていることを確認します。ターミナルを開き以下のコマンドを実行してください。
    ```
    $ lsmod | grep nouveau
    nouveau              1403757  0							
    video                  24400  1 nouveau					
    mxm_wmi                13021  1 nouveau					
    i2c_algo_bit           13413  1 nouveau					
    drm_kms_helper        125008  1 nouveau					
    ttm                    93441  1 nouveau					
    drm                   349210  3 ttm,drm_kms_helper,nouveau		
    i2c_core               40582  5							
    drm,i2c_i801,drm_kms_helper,i2c_algo_bit,nouveau			
    wmi                    19070  2 mxm_wmi,nouveau			
    ```
    何かしらの情報が表示され Nouveau モジュールが動いていることを確認した後、 `/etc/modprobe.d` ディレクトリに移動します。TODOd: もしなにも表示されない場合。以下のコマンドを実行してください。
    ```
    $ cd /etc/modprobe.d
    ```
    次に Nouveau モジュールの設定を記述する `blacklist-nouveau.conf` ファイルを作成します。以下のコマンドを実行し、
    ```
    $ sudo nano blacklist-nouveau.conf
    ```
    表示される `blacklist-nouveau.conf` ファイルの内容に以下を書き記し、
    ```conf:blacklist-nouveau.conf
    blacklist nouveau
    options nouveau modeset=0
    ```
    `Control` + `S` により `blacklist-nouveau.conf` ファイルを保存した後、`Control` + `X` で `blacklist-nouveau.conf` ファイルを閉じてください。
    
    最後に、initramfs イメージを更新します [^initramfsイメージ]。以下のコマンドを実行してください。
    [^initramfsイメージ]: TODO
    ```
    $ sudo update-initramfs -u
    ```

## NVIDIA ドライバのインストール
:::note warn
警告
以下の手順は、失敗するとドライバのコンフリクトが生じ、画面がつかない、ログインができないなどのトラブルが発生する可能性があります。十分注意してください。
:::

- NVIDIA グラフィックボードを搭載している場合 
    まず Ubuntu のグラフィックスドライバを更新します。ターミナルを開き以下のコマンドを実行してください。
    ```
    $ sudo add-apt-repository ppa:graphics-drivers/ppa
    $ sudo apt update
    ```
    次に PC に推奨されるドライバを調べます。以下のコマンドを実行してください。
    ```
    $ ubuntu-drivers devices
    == /sys/devices/pci0000:00/0000:00:01.0/0000:01:00.0 ==
    modalias : pci:v000010DEd00002204sv000019DAsd00001613bc03sc00i00
    vendor   : NVIDIA Corporation
    driver   : nvidia-driver-535 - third-party non-free
    driver   : nvidia-driver-525 - third-party non-free
    driver   : nvidia-driver-525-server - distro non-free
    driver   : nvidia-driver-470-server - distro non-free
    driver   : nvidia-driver-470 - distro non-free
    driver   : nvidia-driver-535-server - distro non-free
    driver   : nvidia-driver-535-server-open - distro non-free recommended
    driver   : nvidia-driver-525-open - distro non-free
    driver   : nvidia-driver-535-open - third-party non-free
    driver   : xserver-xorg-video-nouveau - distro free builtin
    ```
    表示されたリストの中で recommended と書かれている NVIDIA ドライバをインストールします。例えば `nvidia-driver-<XXX>` であった場合、以下のようにコマンドを実行してください。
    ```
    $ sudo apt install nvidia-driver-<XXX>
    ```
    再起動します。以下のコマンドを実行してください。
    ```
    $ sudo reboot
    ```
    ログイン後、まずインストールされた NVIDIA 関連のパッケージを確認します。以下のコマンドを実行してください。
    ```
    $ dpkg -l | grep nvidia
    ii  libnvidia-cfg1-525:amd64                   525.85.12-0ubuntu1                  amd64        NVIDIA binary OpenGL/GLX configuration library
    ii  libnvidia-common-525                       525.85.12-0ubuntu1                  all          Shared files used by the NVIDIA libraries
    ii  libnvidia-compute-525:amd64                525.85.12-0ubuntu1                  amd64        NVIDIA libcompute package
    ii  libnvidia-compute-525:i386                 525.85.12-0ubuntu1                  i386         NVIDIA libcompute package
    ii  libnvidia-container-tools                  1.13.5-1                            amd64        NVIDIA container runtime library (command-line tools)
    ii  libnvidia-container1:amd64                 1.13.5-1                            amd64        NVIDIA container runtime library
    ii  libnvidia-decode-525:amd64                 525.85.12-0ubuntu1                  amd64        NVIDIA Video Decoding runtime libraries
    ii  libnvidia-decode-525:i386                  525.85.12-0ubuntu1                  i386         NVIDIA Video Decoding runtime libraries
    ii  libnvidia-encode-525:amd64                 525.85.12-0ubuntu1                  amd64        NVENC Video Encoding runtime library
    ii  libnvidia-encode-525:i386                  525.85.12-0ubuntu1                  i386         NVENC Video Encoding runtime library
    ii  libnvidia-extra-525:amd64                  525.85.12-0ubuntu1                  amd64        Extra libraries for the NVIDIA driver
    ii  libnvidia-fbc1-525:amd64                   525.85.12-0ubuntu1                  amd64        NVIDIA OpenGL-based Framebuffer Capture runtime library
    ii  libnvidia-fbc1-525:i386                    525.85.12-0ubuntu1                  i386         NVIDIA OpenGL-based Framebuffer Capture runtime library
    ii  libnvidia-gl-525:amd64                     525.85.12-0ubuntu1                  amd64        NVIDIA OpenGL/GLX/EGL/GLES GLVND libraries and Vulkan ICD
    ii  libnvidia-gl-525:i386                      525.85.12-0ubuntu1                  i386         NVIDIA OpenGL/GLX/EGL/GLES GLVND libraries and Vulkan ICD
    ii  nvidia-compute-utils-525                   525.85.12-0ubuntu1                  amd64        NVIDIA compute utilities
    ii  nvidia-container-toolkit                   1.13.5-1                            amd64        NVIDIA Container toolkit
    ii  nvidia-container-toolkit-base              1.13.5-1                            amd64        NVIDIA Container Toolkit Base
    ii  nvidia-dkms-525                            525.85.12-0ubuntu1                  amd64        NVIDIA DKMS package
    ii  nvidia-driver-525                          525.85.12-0ubuntu1                  amd64        NVIDIA driver metapackage
    ii  nvidia-kernel-common-525                   525.85.12-0ubuntu1                  amd64        Shared files used with the kernel module
    ii  nvidia-kernel-source-525                   525.85.12-0ubuntu1                  amd64        NVIDIA kernel source package
    ii  nvidia-modprobe                            525.85.12-0ubuntu1                  amd64        Load the NVIDIA kernel driver and create device files
    ii  nvidia-prime                               0.8.16~0.20.04.2                    all          Tools to enable NVIDIA's Prime
    ii  nvidia-settings                            525.85.12-0ubuntu1                  amd64        Tool for configuring the NVIDIA graphics driver
    ii  nvidia-utils-525                           525.85.12-0ubuntu1                  amd64        NVIDIA driver support binaries
    ii  screen-resolution-extra                    0.18build1                          all          Extension for the nvidia-settings control panel
    ii  xserver-xorg-video-nvidia-525              525.85.12-0ubuntu1                  amd64        NVIDIA binary Xorg driver
    ```
    次に NVIDIA モジュールの状態を確認します。以下のコマンドを実行してください。
    ```
    $ lsmod | grep nvidia
    nvidia_uvm           1388544  0
    nvidia_drm             69632  14
    nvidia_modeset       1216512  20 nvidia_drm
    nvidia              56336384  1168 nvidia_uvm,nvidia_modeset
    drm_kms_helper        307200  1 nvidia_drm
    drm                   618496  18 drm_kms_helper,nvidia,nvidia_drm
    ```
    最後に GPU の状態や利用率を確認します。以下のコマンドを実行してください。
    ```
    $ nvidia-smi
    Sun Oct 15 17:08:11 2023       
    +-----------------------------------------------------------------------------+
    | NVIDIA-SMI 525.85.12    Driver Version: 525.85.12    CUDA Version: 12.0     |
    |-------------------------------+----------------------+----------------------+
    | GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
    | Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
    |                               |                      |               MIG M. |
    |===============================+======================+======================|
    |   0  NVIDIA GeForce ...  On   | 00000000:01:00.0  On |                  N/A |
    |  0%   57C    P8    25W / 350W |    473MiB / 24576MiB |     19%      Default |
    |                               |                      |                  N/A |
    +-------------------------------+----------------------+----------------------+
                                                                                   
    +-----------------------------------------------------------------------------+
    | Processes:                                                                  |
    |  GPU   GI   CI        PID   Type   Process name                  GPU Memory |
    |        ID   ID                                                   Usage      |
    |=============================================================================|
    |    0   N/A  N/A      1140      G   /usr/lib/xorg/Xorg                 35MiB |
    |    0   N/A  N/A      1880      G   /usr/lib/xorg/Xorg                252MiB |
    |    0   N/A  N/A      2004      G   /usr/bin/gnome-shell               26MiB |
    |    0   N/A  N/A      2458      G   ...veSuggestionsOnlyOnDemand       48MiB |
    |    0   N/A  N/A     54442      G   /usr/bin/anydesk                   20MiB |
    |    0   N/A  N/A    624141      G   ...937440421976888396,262144       10MiB |
    |    0   N/A  N/A    728274      G   .../ros/noetic/lib/rviz/rviz        8MiB |
    |    0   N/A  N/A   2923469      G   ...RendererForSitePerProcess       45MiB |
    +-----------------------------------------------------------------------------+
    ```
    以上の手順は "[【Ubuntu + NVIDIA】Ubuntu に NVIDIA ドライバーをインストール](https://tamnology.com/ubuntu-nvidia-driver/)"、"[ubuntuにCUDA、nvidiaドライバをインストールするメモ](https://qiita.com/porizou1/items/74d8264d6381ee2941bd)" を参考にしました。

## NVIDIA Container Toolkit のインストール
- NVIDIA グラフィックボードを搭載している場合
    まず必要なリポジトリと GPG 鍵を設定します。ターミナルを開き以下のコマンドを実行してください。
    ```
    $ distribution=$(. /etc/os-release;echo $ID$VERSION_ID) \
          && curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg \
          && curl -s -L https://nvidia.github.io/libnvidia-container/$distribution/libnvidia-container.list | \
                sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
                sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
    ```
    NVIDIA Container Toolkit をインストールします。以下のコマンドを実行してください。
    ```
    $ sudo apt update && sudo apt install -y nvidia-container-toolkit
    ```
    次に Docker デーモンが NVIDIA Container Runtime を認識するよう設定をします。以下のコマンドを実行してください。
    ```
    $ sudo nvidia-ctk runtime configure --runtime=docker
    ```
    Docker デーモンを再起動します。以下のコマンドを実行してください。
    ```
    $ sudo systemctl restart docker
    ```
    最後に NVIDIA Container Toolkit が正しくインストールされたか確認します。以下のコマンドを実行してください。
    ```
    $ docker run --rm --gpus all nvcr.io/nvidia/k8s/cuda-sample:nbody nbody -gpu -benchmark -numbodies=640000
    ```
    以上の手順は "[UbuntuにDockerをインストールしてGPUを認識させる - NVIDIA Container Toolkitのインストール](https://zenn.dev/usagi1975/articles/2022-09-05-000000_docker_gpu#nvidia-container-toolkit%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB)"、"[Installing the NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html)" を参考にしました。

# X サーバへのアクセス設定
X サーバへのアクセスを許可するよう設定します。これにより、X Window System (X11) の GUI が起動できるようになり、これから作成する Docker コンテナを使用することができます。ターミナルを開き以下のコマンドを実行してください。
```
$ xhost local:user
```
以下で取得する Docker イメージからコンテナを作成、利用する際は、毎度この手順を実行してから行ってください。

# Docker イメージの取得
以下の手順を行います。
- Docker ファイルの複製
- Docker ファイルのビルド

## Docker ファイルの複製
GitHub のリポジトリを任意のディレクトリに複製します。例えばホームディレクトリ (`~/`) に複製します。ターミナルを開き以下のコマンドを実行してください。
```
$ cd
$ git clone https://github.com/turlucode/ros-docker-gui.git
```

## Docker ファイルのビルド
まず Docker ファイルが置かれたディレクトリに移動します。例えば (`~/ros-docker-gui`) に移動します。ターミナルを開き以下のコマンドを実行してください。
```
$ cd ~/ros-docker-gui
```
その後ビルドします。以下のコマンドを実行してください。例えば利用したい ROS バージョンを `<YYYY>` とします。
- PC にグラフィックボードを搭載している場合
    ```
    $ make ros_<YYYY>
    ```
- PC にグラフィックボードを搭載しておらず内蔵 GPU [^内蔵GPU] を搭載している場合
    ```
    $ make cpu_ros_<YYYY>
    ```

# 動作確認
コンテナを作成してログインします。ターミナルを開き以下のコマンドを実行してください。
- NVIDIA グラフィックボードを搭載している場合
    ```
    $ docker run -it --runtime=nvidia --privileged --net=host --ipc=host -v /tmp/.X11-unix:/tmp/.X11-unix -e DISPLAY=$DISPLAY -v $HOME/.Xauthority:/home/$(id -un)/.Xauthority -e XAUTHORITY=/home/$(id -un)/.Xauthority -e ROS_IP=127.0.0.1 turlucode/ros-<YYYY>:<ZZZZ>
    ```
- グラフィックボードを搭載しておらず内蔵 GPU [^内蔵GPU] を搭載している場合
    ```
    $ docker run -it --privileged --net=host --ipc=host --device=/dev/dri:/dev/dri -v /tmp/.X11-unix:/tmp/.X11-unix -e DISPLAY=$DISPLAY -v $HOME/.Xauthority:/home/$(id -un)/.Xauthority -e XAUTHORITY=/home/$(id -un)/.Xauthority -e ROS_IP=127.0.0.1 turlucode/ros-<YYYY>:cpu
    ```
上記のコマンドを実行したら、X Window System のターミナル (Terminator [^Terminator]) が開かれます。
[^Terminator]: "[ターミナル環境の構築: Terminatorのインストール](https://ryonakagami.github.io/2020/12/22/ubuntu-terminator/)" によると、Terminator (Terminal emulator) とはデフォルトでインストールされているターミナル (Gnome Terminal) の代わりに利用できるというターミナルのことです。

roscore を立ち上げます。Terminator 上で以下のコマンドを実行してください。
```
$ roscore
```
Rviz を起動します。Terminator 上で以下のコマンドを実行してください。
```
$ rviz
```
Terminator 上で `Ctrl` + `C` を押して Rviz を終了してください。

Gazebo を起動します。Terminator 上で以下のコマンドを実行してください。
```
$ gazebo
```
Terminator 上で `Ctrl` + `C` を押して Gazebo を終了してください。

最後にコンテナをログアウトして停止します。Terminator を閉じてください。

# おわりに
今回紹介したコンテナの中でさらにコードを書いたりする際は、Docker ファイルにある VSCode インストール済みのイメージ、もしくは VSCode の拡張機能 (Remote Explorer) を使うのが便利です。


# 参考文献
- https://github.com/turlucode/ros-docker-gui
- [内蔵GPUとは？外部GPUとの違いや今後の動向について解説！](https://dosparaplus.com/library/details/000742.html)
- [【Ubuntu + NVIDIA】Ubuntu に NVIDIA ドライバーをインストール](https://tamnology.com/ubuntu-nvidia-driver/)
- [ubuntuにCUDA、nvidiaドライバをインストールするメモ](https://qiita.com/porizou1/items/74d8264d6381ee2941bd)
- [Install Docker Engine on Ubuntu - Install using the Apt repository](https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository)
- [Linux post-installation steps for Docker Engine - Manage Docker as a non-root user](https://docs.docker.com/engine/install/linux-postinstall/#manage-docker-as-a-non-root-user)
- [UbuntuにDockerをインストールしてGPUを認識させる - NVIDIA Container Toolkitのインストール](https://zenn.dev/usagi1975/articles/2022-09-05-000000_docker_gpu#nvidia-container-toolkit%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB)
- [Installing the NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html)
- [ターミナル環境の構築: Terminatorのインストール
](https://ryonakagami.github.io/2020/12/22/ubuntu-terminator/)