1. はじめに
ROS 2のDockerコンテナ間通信をする方法を検索すると「docker run
オプションに--net=host --ipc=host
を付けよう」という言及がWeb上の記事で散見されます。一方、このオプションを付ける理由まで書かれているものが非常に少ないです。理由がよくわからないと非常に気持ち悪いのでもう少し掘り下げて整理していきます。
2. シチュエーション別おすすめdocker runオプション
ユースケースにもよると思うので、本記事ではDDSそのものの比較には言及しないこととします。気になる方はhttps://discourse.ros.org/t/ros-2-alternative-middleware-report/33771を読んでみてください。
本記事では基本的にROS 2 HumbleのデフォルトであるFast DDSを使うとして、シチュエーション別docker run
オプションを記載します。
ここでは例として--shm-size=512mb
として、共有メモリサイズを512MBとしていますが、ご自身のユースケースに応じて適宜調整してください。
3. 動作確認条件
以下に動作確認で用いた環境、動作確認内容について列挙します。
3.1 動作確認に用いた環境
- ホストOS:Ubuntu 22.04(x86_64)
- Docker CLI
- docker-ce 24.0.6
- ROS 2公式Dockerイメージ ros:humble-ros-core を用いた
- ROS 2ディストリビューション:Humble
- DDS
- Fast DDS
- ros-humble-fastrtps 2.6.8
- ros-humble-rmw-fastrtps-cpp 6.2.6
- ros-humble-rmw-fastrtps-shared-cpp 6.2.6
- Cyclone DDS
- ros-humble-cyclonedds 0.10.4
- ros-humble-rmw-cyclonedds-cpp 1.3.4
- Fast DDS
3.2 動作確認内容
- 同一マシン上で2つのDockerコンテナを起動し、片方のコンテナでpub、もう片方のコンテナでsubを行った
- pubコマンドは以下の通り
ros2 topic pub --rate 1 /turtle1/cmd_vel geometry_msgs/msg/Twist "{linear: {x: 2.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 1.8}}"
- subコマンドは以下の通り
ros2 topic echo /turtle1/cmd_vel
3.3 docker runオプション
今回、以下のパターンで検証していきます。
- --net=bridge
- --net=host
- --net=host --ipc=host
詳細なオプションは以降に記載します。
3.3.1 net=bridge
--net
オプションのデフォルト値である、--net=bridge
を指定します。
docker run --rm -it \
ros:humble-ros-core \
bash
3.3.2 net=host
--net=host
を指定します。
docker run --rm -it \
--net=host \
ros:humble-ros-core \
bash
3.3.3 net=host ipc=host
--net=host
、--ipc=host
を指定します。
docker run --rm -it \
--net=host \
--ipc=host \
ros:humble-ros-core \
bash
4. 動作確認結果
同一マシン上の2つのコンテナ間でROS 2のpub/subができるかどうかの確認結果を下表に示します。ここでは比較対象としてCyclone DDSの場合の結果も併せて記載しています。
DDS\docker runオプション | net=bridge | net=host | net=host ipc=host |
---|---|---|---|
Fast DDS | OK | NG | OK |
Cyclone DDS | OK | OK | OK |
- OK:同一マシン上の2つのコンテナ間でROS 2のpub/subができる
- NG:同一マシン上の2つのコンテナ間でROS 2のpub/subができない
- ただし、
ros2 topic list
でトピック一覧には表示される
- ただし、
この結果からFast DDSかつnet=host
オプションのみの場合を除いて、同一PC上でROS 2のDockerコンテナを複数起動して、コンテナ間でROS 2のpub/subができていることがわかります。
さて、docker run
オプションやDDSの違いでなぜこのような挙動の差異が生じるのでしょうか?もう少し掘り下げて見ていきましょう。
5. 前提知識
ここでは詳細な確認をする前に必要な前提知識をおさらいしていきます。
5.1 docker runオプション
5.1.1 netオプション
docker run
のnetオプションでネットワークドライバを指定できます(https://docs.docker.com/network/drivers/参照)。https://zenn.dev/suiudou/articles/14249a918ec93b#hostネットワークの図がわかりやすいでしょう。
- --net=bridge
- --net=host
-
https://docs.docker.com/network/drivers/host/に説明があります。つまり、
--net=host
ではホストと同一のネットワークインターフェースを使用することになります。
-
https://docs.docker.com/network/drivers/host/に説明があります。つまり、
5.1.2 ipcオプション
docker run
のipcオプションでDockerコンテナにおけるプロセス間通信(Inter-Process Communication)の名前空間を変更することができます。https://docs.docker.com/reference/cli/docker/container/run/#ipcに以下の説明があります。
Value | Description |
---|---|
"" | Use daemon's default. |
"none" | Own private IPC namespace, with /dev/shm not mounted. |
"private" | Own private IPC namespace. |
"shareable" | Own private IPC namespace, with a possibility to share it with other containers. |
"container:" | Join another ("shareable") container's IPC namespace. |
"host" | Use the host system's IPC namespace. |
--ipc=host
の場合、プロセス間通信においてホストと同一の名前空間が用いられることになります。また、ipcオプションを明示的に指定しない場合についてはhttps://docs.docker.com/reference/cli/docker/container/run/#ipcに以下の記載があります。
If not specified, daemon default is used, which can either be "private" or "shareable", depending on the daemon version and configuration.
デーモンのバージョン、コンフィグレーションにもよりますが、デフォルト値となるprivate
、shareable
のいずれかが使用されるとあります。そこでhttps://docs.docker.jp/engine/reference/commandline/dockerd.htmlを読むと
--default-ipc-mode string
Default mode for containers ipc ("shareable" | "private") (default "private")
とあり、デフォルトのIPCモードはprivate
とあります。そのため、IPCの名前空間はコンテナ毎に独立していることになります。
5.2 Fast DDS
ここではFast DDSのTransportについて簡単におさらいしていきます。Fast DDSでは以下のTransportが実装されています。
https://fast-dds.docs.eprosima.com/en/latest/fastdds/transport/transport.html
- UDPv4 Transport
- UDPv6 Transport
- TCPv4 Transport
- TCPv6 Transport
- Shared Memory Transport
Fast DDSのTransport実装を図示したものを以下に示します(https://fast-dds.docs.eprosima.com/en/latest/_images/transport_comparison.svgから引用)。
本記事ではFast DDSにてデフォルトで有効になっている以下のTransportに着目します。
- UDPv4 Transport
- Shared Memory Transport
5.2.1 UDPv4 Transport
- IPv4経由のUDPデータグラム通信
- 特定のトランスポート構成が指定されていない場合、デフォルトで新しいDomainParticipantにこのトランスポートが作成される
- Fast DDSではUDPv4 Transportがデフォルトで有効になっている
- 詳細はhttps://fast-dds.docs.eprosima.com/en/latest/fastdds/transport/udp/udp.html参照
5.2.2 Shared Memory Transport
- 同一PC内の通信でOSが提供する共有メモリを介してデータを転送
- 特定のトランスポート構成が指定されていない場合、デフォルトで新しいDomainParticipantにこのトランスポートが作成される
- Fast DDSではShared Memory Transportがデフォルトで有効になっている
- 詳細はhttps://fast-dds.docs.eprosima.com/en/latest/fastdds/transport/shared_memory/shared_memory.html参照
また、同時に複数のtransportが有効になっている場合の挙動についてはhttps://fast-dds.docs.eprosima.com/en/latest/fastdds/transport/shared_memory/shared_memory.htmlに記載があります。
In case that several transports are enabled, the discovery traffic is always performed using the UDP/TCP transport, even if the SHM transport is enabled in both participants running in the same machine. This may cause discovery issues if one or several of the participants only has SHM enabled and other participants use some other transport at the same time. Also, when two participants on the same machine have SHM transport enabled, the user data communication between them is automatically performed by SHM transport only. The rest of the enabled transports are not used between those two participants.
6. 調査
ここでは「4. 動作確認結果」で得られた結果とDocker、DDSの処理の対応関係についてもう少し掘り下げて見ていきます。
6.1 同一PC上で-net=hostのみだとコンテナ間でpub/subできないのはなぜ?(Fast DDS)
https://fast-dds.docs.eprosima.com/en/latest/fastdds/transport/shared_memory/shared_memory.htmlに
–ipc=host: This option shares the host’s shared memory mechanism with the containers. Without it, Fast DDS will identify both containers as the same host, but since they will have separate shared memory spaces, they will not be able to communicate with one another.
という説明があります。--ipc
オプションを明示的に指定しない場合、コンテナ間で共有メモリの名前空間が別になります。そのため、コンテナ間でShared Memory Transportを使って通信できない状態になっており、コンテナ間でROS 2のpub/subできなくなっています。
6.2 同一PC上で-net=hostのみだと別コンテナのトピックは見えるのはなぜ?(Fast DDS)
「4. 動作確認結果」で
- NG:同一マシン上の2つのコンテナ間でROS 2のpub/subができない
- ただし、
ros2 topic list
でトピック一覧には表示される
とありました。「ros2 topic list
でトピック一覧には表示される」のはなぜでしょうか?これは「5.2.2 Shared Memory Transport」で説明した以下の処理に起因しています。
In case that several transports are enabled, the discovery traffic is always performed using the UDP/TCP transport, even if the SHM transport is enabled in both participants running in the same machine.
Fast DDSではUDPv4 Transport、Shared Memory Transportがデフォルトで有効になっています。また、このケースではdiscovery trafficはUDPv4 Transportで行われているため、コンテナ間で共有メモリの名前空間が同じかどうかは関係ありません。そのため、ros2 topic list
でトピック一覧には表示されることになります。
6.3 同一PC上で--net=hostのみでもコンテナ間でpub/subできるのはなぜ?(Cyclone DDS)
https://github.com/ros2/rmw_cyclonedds/blob/rolling/shared_memory_support.mdに以下の説明があります。
This rmw_cyclonedds implementation uses cyclonedds which includes support for fast Shared Memory data transfer based on iceoryx. Shared Memory is disabled by default but can be enabled easily by providing a cyclonedds.xml configuration file.
Cyclone DDSでは共有メモリを使ったデータ転送はデフォルトで無効化されているため、--ipc=host
を付けなくてもコンテナ間でROS 2のpub/subができています。
6.4 同一PC上で--ipc=hostなしでもShared Memory Transportを使ってコンテナ間でpub/subできるか?(Fast DDS)
オプションは以下の通りです。前述した通り、コンテナ間で共有メモリの名前空間が別になっていたため、Fast DDSではコンテナ間でROS 2のpub/subができていませんでした。
docker run --rm -it \
--net=host \
ros:humble-ros-core \
bash
そのため、docker run
オプションに--volume=/dev/shm:/dev/shm:rw
、共有メモリのデバイスファイルをマウントすることでコンテナ間でROS 2のpub/subができるようになります。
docker run --rm -it \
--volume=/dev/shm:/dev/shm:rw \
--net=host \
ros:humble-ros-core \
bash
6.5 net=bridgeのみだとpub/subできていたのはなぜ?(Fast DDS)
《こちらは調査中です》
「4. 動作確認結果」で--net=bridge
のとき、IPC関連のオプションなしでもpub/subできていました。この挙動の理由について調べてみます。
6.5.1 IPC名前空間を調べる
--net=bridge
でコンテナを2つ起動してIPC名前空間を調べてみます。コンテナ(1)の結果は以下の通りです。
$ ls -l /proc/self/ns/ipc
lrwxrwxrwx 1 humble humble 0 Jul 21 04:37 /proc/self/ns/ipc -> 'ipc:[4026533262]'
コンテナ(2)の結果は以下の通りです。
$ ls -l /proc/self/ns/ipc
lrwxrwxrwx 1 humble humble 0 Jul 21 04:37 /proc/self/ns/ipc -> 'ipc:[4026533374]'
2つのコンテナでIPC名前空間は同一にはなっていないようです。
6.5.2 /dev/shmをマウントしない
docker run
オプションで--ipc=none
として/dev/shm
をマウントしないようにしてコンテナ間でpub/subを確認してみます。
$ ros2 topic echo /turtle1/cmd_vel
2024-07-21 04:32:47.964 [RTPS_TRANSPORT_SHM Error] Failed to create segment 15f520708af5b59c: No such file or directory -> Function compute_per_allocation_extra_size
2024-07-21 04:32:47.964 [RTPS_TRANSPORT_SHM Error] Failed to create Shared Memory Manager for domain fastrtps: No such file or directory -> Function create
2024-07-21 04:32:47.964 [RTPS_PARTICIPANT Error] Unable to Register SHM Transport. SHM Transport is not supported in the current platform. -> Function RTPSParticipantImpl
linear:
x: 2.0
y: 0.0
z: 0.0
angular:
x: 0.0
y: 0.0
z: 1.8
Shared Memory Transportの初期化で失敗していますが、UDPv4 Transportでpub/subできているようです。
6.5.3 FASTDDS_BUILTIN_TRANSPORTSを変更する
https://fast-dds.docs.eprosima.com/en/latest/fastdds/env_vars/env_vars.html#fastdds-builtin-transportsを参考にして環境変数FASTDDS_BUILTIN_TRANSPORTSを変更してコンテナ間のpub/sub成否を確認します。結果は以下の通りです。
FASTDDS_BUILTIN_TRANSPORTS | コンテナ間のpub/sub成否 |
---|---|
DEFAULT | OK |
UDPv4 | OK |
SHM | NG |
この結果からUDPv4 Transportでpub/subできているようです。
6.6 同一PC上で--ipc=hostなしでもコンテナ間でpub/subできるか?(Fast DDS)
同一PC上のコンテナ間でFast DDS、Shared Memory Transportを使うために--ipc=host
が必要です。逆に言うとShared Memory Transportを使わないようにし、UDP Transportのみを使うようにすれば--ipc=host
を付けなくてもよくなります。
https://fast-dds.docs.eprosima.com/en/latest/fastdds/env_vars/env_vars.html#fastdds-builtin-transportsを参考に以下のコマンドを実行してUDPv4 Transportを使うようにします。
※https://tech.aptpod.co.jp/entry/2023/09/25/170000にあるようにUDP Transportを使うようなコンフィグレーションファイル (fastrtps-profile.xml
)を用いる方法もあります。
export FASTDDS_BUILTIN_TRANSPORTS=UDPv4
このようにすることで--ipc=host
なしでもコンテナ間でROS 2のpub/subができるようになります。
6.7 --net=hostオプションは要らない?
ここまでの説明で--ipc=host
だけ付けていればよくて--net=host
は要らないのでは?と思われる方が居るかもしれません。ROS 2でpub/subするコンテナが同一マシン内だけであれば--net=host
は要らない(=デフォルトの--net=bridge
を使う)ので問題ありませんが、ROS 2でpub/subするコンテナが同一ネットワーク上の異なるPC間にある場合、--net=bridge
ではpub/subできません。このような使い方をする場合は--net=host
が必要になります。
6.8 Dockerコンテナで共有メモリの割り当てはどうなっているの?
Dockerドキュメントのhttps://docs.docker.jp/engine/reference/commandline/dockerd.htmlで
--default-shm-size bytes
Default shm size for containers (default 64MiB)
とあり、デフォルトは共有メモリのサイズは64MiBとなっています。そのため、このサイズだと不十分なユースケースであればdocker run
の--shm-size
オプションで共有メモリの割り当てを増やすと良いでしょう。
https://docs.docker.com/engine/reference/run/#runtime-constraints-on-resources
--shm-size
オプションで共有メモリを1GB割り当てるオプションを以下に示します。
--shm-size=1gb
参考URL
- https://fast-dds.docs.eprosima.com/en/latest/docker/shm_docker.html
- https://github.com/docker/for-linux/issues/637
- https://github.com/eProsima/Fast-DDS/issues/2956
- https://stackoverflow.com/questions/65900201/troubles-communicating-with-ros2-node-in-docker-container
- https://fast-dds.docs.eprosima.com/en/latest/fastdds/ros2/ros2_configure.html
- https://fast-dds.docs.eprosima.com/en/latest/fastdds/transport/shared_memory/shared_memory.html
- https://cyclonedds.io/docs/cyclonedds/latest/shared_memory/shared_memory.html